Merge "Reapply "The meet of precise types is only defined for identical types.""
diff --git a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
index 1d6e678..99c97a4 100644
--- a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
@@ -29,6 +29,10 @@
 @KeepForSubclassing
 public class ArchiveProgramResourceProvider implements ProgramResourceProvider {
 
+  interface ArchiveEntryConsumer {
+    void accept(ArchiveEntryOrigin entry, InputStream stream) throws IOException;
+  }
+
   @KeepForSubclassing
   public interface ZipFileSupplier {
     ZipFile open() throws IOException;
@@ -83,48 +87,51 @@
     this.include = include;
   }
 
-  private List<ProgramResource> readArchive() throws IOException {
-    List<ProgramResource> dexResources = new ArrayList<>();
-    List<ProgramResource> classResources = new ArrayList<>();
+  void readArchive(ArchiveEntryConsumer consumer) throws IOException {
     try (ZipFile zipFile = supplier.open()) {
       final Enumeration<? extends ZipEntry> entries = zipFile.entries();
       while (entries.hasMoreElements()) {
         ZipEntry entry = entries.nextElement();
         try (InputStream stream = zipFile.getInputStream(entry)) {
-          String name = entry.getName();
-          Origin entryOrigin = new ArchiveEntryOrigin(name, origin);
-          if (include.test(name)) {
-            if (ZipUtils.isDexFile(name)) {
-              dexResources.add(
-                  ProgramResource.fromBytes(
-                      entryOrigin, Kind.DEX, ByteStreams.toByteArray(stream), null));
-            } else if (ZipUtils.isClassFile(name)) {
-              String descriptor = DescriptorUtils.guessTypeDescriptor(name);
-              classResources.add(
-                  ProgramResource.fromBytes(
-                      entryOrigin,
-                      Kind.CF,
-                      ByteStreams.toByteArray(stream),
-                      Collections.singleton(descriptor)));
-            }
-          }
+          consumer.accept(new ArchiveEntryOrigin(entry.getName(), origin), stream);
         }
       }
     } catch (ZipException e) {
       throw new CompilationError("Zip error while reading archive" + e.getMessage(), e, origin);
     }
-    if (!dexResources.isEmpty() && !classResources.isEmpty()) {
-      throw new CompilationError(
-          "Cannot create android app from an archive containing both DEX and Java-bytecode content",
-          origin);
-    }
-    return !dexResources.isEmpty() ? dexResources : classResources;
   }
 
   @Override
   public Collection<ProgramResource> getProgramResources() throws ResourceException {
     try {
-      return readArchive();
+      List<ProgramResource> dexResources = new ArrayList<>();
+      List<ProgramResource> classResources = new ArrayList<>();
+      readArchive(
+          (entry, stream) -> {
+            String name = entry.getEntryName();
+            if (include.test(name)) {
+              if (ZipUtils.isDexFile(name)) {
+                dexResources.add(
+                    ProgramResource.fromBytes(
+                        entry, Kind.DEX, ByteStreams.toByteArray(stream), null));
+              } else if (ZipUtils.isClassFile(name)) {
+                String descriptor = DescriptorUtils.guessTypeDescriptor(name);
+                classResources.add(
+                    ProgramResource.fromBytes(
+                        entry,
+                        Kind.CF,
+                        ByteStreams.toByteArray(stream),
+                        Collections.singleton(descriptor)));
+              }
+            }
+          });
+      if (!dexResources.isEmpty() && !classResources.isEmpty()) {
+        throw new CompilationError(
+            "Cannot create android app from an archive containing both DEX and Java-bytecode "
+                + "content",
+            origin);
+      }
+      return !dexResources.isEmpty() ? dexResources : classResources;
     } catch (IOException e) {
       throw new ResourceException(origin, e);
     }
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 5980a0c..1a59ef8 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -136,6 +136,12 @@
       mode = defaultCompilationMode();
     }
 
+    // Internal constructor for testing.
+    Builder(AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
+      super(AndroidApp.builder(app, new Reporter(diagnosticsHandler)));
+      mode = defaultCompilationMode();
+    }
+
     /**
      * Get current compilation mode.
      */
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 327d206..9f12853 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.CommandLineOrigin;
@@ -159,6 +160,7 @@
       options.enableMinification = false;
       options.enableInlining = false;
       options.enableClassInlining = false;
+      options.enableClassStaticizer = false;
       options.outline.enabled = false;
 
       DexApplication app = new ApplicationReader(inputApp, options, timing).read(executor);
@@ -191,6 +193,7 @@
               options,
               marker == null ? null : ImmutableList.copyOf(markers),
               null,
+              GraphLense.getIdentityLense(),
               NamingLens.getIdentityLens(),
               null,
               null)
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 8c6c02b..99b0a1e 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -237,6 +237,8 @@
     internal.enableInlining = false;
     assert internal.enableClassInlining;
     internal.enableClassInlining = false;
+    assert internal.enableClassStaticizer;
+    internal.enableClassStaticizer = false;
     assert internal.enableSwitchMapRemoval;
     internal.enableSwitchMapRemoval = false;
     assert internal.outline.enabled;
diff --git a/src/main/java/com/android/tools/r8/DataEntryResource.java b/src/main/java/com/android/tools/r8/DataEntryResource.java
index aa9c73f..6b8f9c5 100644
--- a/src/main/java/com/android/tools/r8/DataEntryResource.java
+++ b/src/main/java/com/android/tools/r8/DataEntryResource.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.origin.ArchiveEntryOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -21,6 +22,10 @@
   /** Get the bytes of the data entry resource. */
   InputStream getByteStream() throws ResourceException;
 
+  static DataEntryResource fromBytes(byte[] bytes, String name, Origin origin) {
+    return new ByteDataEntryResource(bytes, name, origin);
+  }
+
   static DataEntryResource fromFile(Path dir, Path file) {
     return new LocalDataEntryResource(dir.resolve(file).toFile(),
         file.toString().replace(File.separatorChar, SEPARATOR));
@@ -30,6 +35,38 @@
     return new ZipDataEntryResource(zip, entry);
   }
 
+  default DataEntryResource withName(String name) {
+    return new NestedDataEntryResource(name, null, this);
+  }
+
+  class ByteDataEntryResource implements DataEntryResource {
+
+    private final byte[] bytes;
+    private final String name;
+    private final Origin origin;
+
+    public ByteDataEntryResource(byte[] bytes, String name, Origin origin) {
+      this.bytes = bytes;
+      this.name = name;
+      this.origin = origin;
+    }
+
+    @Override
+    public InputStream getByteStream() {
+      return new ByteArrayInputStream(bytes);
+    }
+
+    @Override
+    public String getName() {
+      return name;
+    }
+
+    @Override
+    public Origin getOrigin() {
+      return origin;
+    }
+  }
+
   class ZipDataEntryResource implements DataEntryResource {
     private final ZipFile zip;
     private final ZipEntry entry;
@@ -91,4 +128,35 @@
       }
     }
   }
+
+  /**
+   * A resource that has the same contents as another resource, but with a different name or origin.
+   */
+  class NestedDataEntryResource implements DataEntryResource {
+
+    private final String name;
+    private final Origin origin;
+    private final DataEntryResource resource;
+
+    public NestedDataEntryResource(String name, Origin origin, DataEntryResource resource) {
+      this.name = name;
+      this.origin = origin;
+      this.resource = resource;
+    }
+
+    @Override
+    public InputStream getByteStream() throws ResourceException {
+      return resource.getByteStream();
+    }
+
+    @Override
+    public String getName() {
+      return name != null ? name : resource.getName();
+    }
+
+    @Override
+    public Origin getOrigin() {
+      return origin != null ? origin : resource.getOrigin();
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index 5987b99..a26924e 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.ExceptionUtils;
@@ -91,8 +92,17 @@
         List<Marker> markers = app.dexItemFactory.extractMarkers();
 
         assert !options.hasMethodsFilter();
-        new ApplicationWriter(app, options, markers, null, NamingLens.getIdentityLens(), null, null)
-            .write(executor);
+        ApplicationWriter writer =
+            new ApplicationWriter(
+                app,
+                options,
+                markers,
+                null,
+                GraphLense.getIdentityLense(),
+                NamingLens.getIdentityLens(),
+                null,
+                null);
+        writer.write(executor);
         options.printWarnings();
       } catch (ExecutionException e) {
         R8.unwrapExecutionException(e);
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index 3e299f2..68dc702 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.ExceptionUtils;
@@ -96,6 +97,7 @@
                   options,
                   markers,
                   null,
+                  GraphLense.getIdentityLense(),
                   NamingLens.getIdentityLens(),
                   null,
                   null,
diff --git a/src/main/java/com/android/tools/r8/DiagnosticsHandler.java b/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
index 74a9a96..666e358 100644
--- a/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
+++ b/src/main/java/com/android/tools/r8/DiagnosticsHandler.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 
 /**
  * A DiagnosticsHandler can be provided to customize handling of diagnostics information.
@@ -20,7 +21,11 @@
    */
   default void error(Diagnostic error) {
     if (error.getOrigin() != Origin.unknown()) {
-      System.err.print("Error in " + error.getOrigin() + ":\n  ");
+      System.err.print("Error in " + error.getOrigin());
+      if (error.getPosition() != Position.UNKNOWN) {
+        System.err.print(" at " + error.getPosition().getDescription());
+      }
+      System.err.println(":");
     } else {
       System.err.print("Error: ");
     }
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 26097c1..fe946da 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.MainDexListBuilder;
@@ -41,7 +42,7 @@
     AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
     RootSet mainDexRootSet =
         new RootSetBuilder(appInfo, application, options.mainDexKeepRules, options).run(executor);
-    Enqueuer enqueuer = new Enqueuer(appInfo, options, true);
+    Enqueuer enqueuer = new Enqueuer(appInfo, GraphLense.getIdentityLense(), options, true);
     AppInfoWithLiveness mainDexAppInfo = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
     // LiveTypes is the result.
     Set<DexType> mainDexClasses =
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index 782c91d..4410d8a 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.RootSetBuilder;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
@@ -84,7 +85,7 @@
           new RootSetBuilder(
                   appInfo, application, options.proguardConfiguration.getRules(), options)
               .run(executor);
-      Enqueuer enqueuer = new Enqueuer(appInfo, options, false);
+      Enqueuer enqueuer = new Enqueuer(appInfo, GraphLense.getIdentityLense(), options, false);
       appInfo = enqueuer.traceApplication(rootSet, executor, timing);
       RootSetBuilder.writeSeeds(
           appInfo.withLiveness(),
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 805bdf7..215ced9 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -176,6 +176,7 @@
       ExecutorService executorService,
       DexApplication application,
       String deadCode,
+      GraphLense graphLense,
       NamingLens namingLens,
       String proguardSeedsData,
       InternalOptions options,
@@ -188,6 +189,7 @@
                 application,
                 options,
                 deadCode,
+                graphLense,
                 namingLens,
                 proguardSeedsData,
                 proguardMapSupplier)
@@ -198,6 +200,7 @@
                 options,
                 marker == null ? null : Collections.singletonList(marker),
                 deadCode,
+                graphLense,
                 namingLens,
                 proguardSeedsData,
                 proguardMapSupplier)
@@ -235,7 +238,7 @@
       System.setOut(new PrintStream(ByteStreams.nullOutputStream()));
     }
     // TODO(b/65390962): Remove this warning once the CF backend is complete.
-    if (options.isGeneratingClassFiles()) {
+    if (options.isGeneratingClassFiles() && !options.testing.suppressExperimentalCfBackendWarning) {
       options.reporter.warning(new StringDiagnostic(
           "R8 support for generating Java classfiles is incomplete and experimental. "
               + "Even if R8 appears to succeed, the generated output is likely incorrect."));
@@ -292,6 +295,7 @@
         Enqueuer enqueuer =
             new Enqueuer(
                 appView.getAppInfo(),
+                appView.getGraphLense(),
                 options,
                 options.forceProguardCompatibility,
                 compatibility,
@@ -369,7 +373,7 @@
         if (options.enableClassMerging && options.enableInlining) {
           timing.begin("ClassMerger");
           VerticalClassMerger classMerger =
-              new VerticalClassMerger(application, appViewWithLiveness, timing);
+              new VerticalClassMerger(application, appViewWithLiveness, executorService, timing);
           appView.setGraphLense(classMerger.run());
           timing.end();
           application = application.asDirect().rewrittenWithLense(appView.getGraphLense());
@@ -380,10 +384,9 @@
                   .rewrittenWithLense(application.asDirect(), appView.getGraphLense()));
         }
         // Collect switch maps and ordinals maps.
+        appViewWithLiveness.setAppInfo(new SwitchMapCollector(appViewWithLiveness, options).run());
         appViewWithLiveness.setAppInfo(
-            new SwitchMapCollector(appViewWithLiveness.getAppInfo(), options).run());
-        appViewWithLiveness.setAppInfo(
-            new EnumOrdinalMapCollector(appViewWithLiveness.getAppInfo(), options).run());
+            new EnumOrdinalMapCollector(appViewWithLiveness, options).run());
 
         // TODO(b/79143143): re-enable once fixed.
         // graphLense = new BridgeMethodAnalysis(graphLense, appInfo.withLiveness()).run();
@@ -396,6 +399,7 @@
             new IRConverter(
                 appView.getAppInfo(), options, timing, printer, appView.getGraphLense());
         application = converter.optimize(application, executorService);
+        appView.setGraphLense(converter.getGraphLense());
       } finally {
         timing.end();
       }
@@ -419,7 +423,8 @@
 
       if (!options.mainDexKeepRules.isEmpty()) {
         appView.setAppInfo(new AppInfoWithSubtyping(application));
-        Enqueuer enqueuer = new Enqueuer(appView.getAppInfo(), options, true);
+        Enqueuer enqueuer =
+            new Enqueuer(appView.getAppInfo(), appView.getGraphLense(), options, true);
         // Lets find classes which may have code executed before secondary dex files installation.
         RootSet mainDexRootSet =
             new RootSetBuilder(appView.getAppInfo(), application, options.mainDexKeepRules, options)
@@ -443,7 +448,11 @@
         timing.begin("Post optimization code stripping");
         try {
           Enqueuer enqueuer =
-              new Enqueuer(appView.getAppInfo(), options, options.forceProguardCompatibility);
+              new Enqueuer(
+                  appView.getAppInfo(),
+                  appView.getGraphLense(),
+                  options,
+                  options.forceProguardCompatibility);
           appView.setAppInfo(enqueuer.traceApplication(rootSet, executorService, timing));
 
           AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
@@ -485,6 +494,7 @@
         ClassNameMapper classNameMapper =
             LineNumberOptimizer.run(
                 application,
+                appView.getGraphLense(),
                 namingLens,
                 options.lineNumberOptimization == LineNumberOptimization.IDENTITY_MAPPING);
         timing.end();
@@ -507,6 +517,7 @@
           executorService,
           application,
           application.deadCode,
+          appView.getGraphLense(),
           namingLens,
           proguardSeedsData,
           options,
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 584b06d..e3904fb 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -83,6 +83,10 @@
       super(app);
     }
 
+    private Builder(AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
+      super(app, diagnosticsHandler);
+    }
+
     // Internal
 
     void internalForceProguardCompatibility() {
@@ -455,6 +459,11 @@
     return new Builder(app);
   }
 
+  // Internal builder to start from an existing AndroidApp.
+  static Builder builder(AndroidApp app, DiagnosticsHandler diagnosticsHandler) {
+    return new Builder(app, diagnosticsHandler);
+  }
+
   /**
    * Parse the R8 command-line.
    *
@@ -571,6 +580,7 @@
       // TODO(zerny): Should we support inlining in debug mode? b/62937285
       internal.enableInlining = false;
       internal.enableClassInlining = false;
+      internal.enableClassStaticizer = false;
       // TODO(zerny): Should we support outlining in debug mode? b/62937285
       internal.outline.enabled = false;
     }
@@ -618,6 +628,13 @@
     internal.proguardCompatibilityRulesOutput = proguardCompatibilityRulesOutput;
     internal.dataResourceConsumer = internal.programConsumer.getDataResourceConsumer();
 
+    // Default is to remove Java assertion code as Dalvik and Art does not reliable support
+    // Java assertions. When generation class file output always keep the Java assertions code.
+    assert internal.disableAssertions;
+    if (internal.isGeneratingClassFiles()) {
+      internal.disableAssertions = false;
+    }
+
     // EXPERIMENTAL flags.
     assert !internal.forceProguardCompatibility;
     internal.forceProguardCompatibility = forceProguardCompatibility;
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index b7fc6b3..6f9464c 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.3.7-dev";
+  public static final String LABEL = "1.3.14-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/bisect/Bisect.java b/src/main/java/com/android/tools/r8/bisect/Bisect.java
index 7ef43a3..60167c5 100644
--- a/src/main/java/com/android/tools/r8/bisect/Bisect.java
+++ b/src/main/java/com/android/tools/r8/bisect/Bisect.java
@@ -177,7 +177,8 @@
       throws IOException, ExecutionException {
     InternalOptions options = new InternalOptions();
     AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
-    ApplicationWriter writer = new ApplicationWriter(app, options, null, null, null, null, null);
+    ApplicationWriter writer =
+        new ApplicationWriter(app, options, null, null, null, null, null, null);
     writer.write(executor);
     compatSink.build().writeToDirectory(output, OutputMode.DexIndexed);
   }
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index 54ae1c6..b2114b3 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -13,7 +13,7 @@
 
 public class CfConstString extends CfInstruction {
 
-  private final DexString string;
+  private DexString string;
 
   public CfConstString(DexString string) {
     this.string = string;
@@ -23,6 +23,10 @@
     return string;
   }
 
+  public void setString(DexString string) {
+    this.string = string;
+  }
+
   @Override
   public void write(MethodVisitor visitor, NamingLens lens) {
     visitor.visitLdcInsn(string.toString());
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index e1905ff..f9e95ec 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -33,15 +34,18 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.ObjectArrays;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
@@ -51,6 +55,7 @@
 
   public final DexApplication application;
   public final String deadCode;
+  public final GraphLense graphLense;
   public final NamingLens namingLens;
   public final String proguardSeedsData;
   public final InternalOptions options;
@@ -120,6 +125,7 @@
       InternalOptions options,
       List<Marker> markers,
       String deadCode,
+      GraphLense graphLense,
       NamingLens namingLens,
       String proguardSeedsData,
       ProguardMapSupplier proguardMapSupplier) {
@@ -128,6 +134,7 @@
         options,
         markers,
         deadCode,
+        graphLense,
         namingLens,
         proguardSeedsData,
         proguardMapSupplier,
@@ -139,6 +146,7 @@
       InternalOptions options,
       List<Marker> markers,
       String deadCode,
+      GraphLense graphLense,
       NamingLens namingLens,
       String proguardSeedsData,
       ProguardMapSupplier proguardMapSupplier,
@@ -154,6 +162,7 @@
       }
     }
     this.deadCode = deadCode;
+    this.graphLense = graphLense;
     this.namingLens = namingLens;
     this.proguardSeedsData = proguardSeedsData;
     this.proguardMapSupplier = proguardMapSupplier;
@@ -258,7 +267,13 @@
       options.reporter.failIfPendingErrors();
       // Supply info to all additional resource consumers.
       supplyAdditionalConsumers(
-          application, namingLens, options, deadCode, proguardMapSupplier, proguardSeedsData);
+          application,
+          graphLense,
+          namingLens,
+          options,
+          deadCode,
+          proguardMapSupplier,
+          proguardSeedsData);
     } finally {
       application.timing.end();
     }
@@ -266,6 +281,7 @@
 
   public static void supplyAdditionalConsumers(
       DexApplication application,
+      GraphLense graphLense,
       NamingLens namingLens,
       InternalOptions options,
       String deadCode,
@@ -296,28 +312,38 @@
     }
     DataResourceConsumer dataResourceConsumer = options.dataResourceConsumer;
     if (dataResourceConsumer != null) {
-
       List<DataResourceProvider> dataResourceProviders = application.programResourceProviders
           .stream()
           .map(ProgramResourceProvider::getDataResourceProvider)
           .filter(Objects::nonNull)
           .collect(Collectors.toList());
 
+      ResourceAdapter resourceAdapter =
+          new ResourceAdapter(application.dexItemFactory, graphLense, namingLens, options);
+      Set<String> generatedResourceNames = new HashSet<>();
+
       for (DataResourceProvider dataResourceProvider : dataResourceProviders) {
         try {
-          dataResourceProvider.accept(new Visitor() {
-            @Override
-            public void visit(DataDirectoryResource directory) {
-              dataResourceConsumer.accept(directory, options.reporter);
-              options.reporter.failIfPendingErrors();
-            }
+          dataResourceProvider.accept(
+              new Visitor() {
+                @Override
+                public void visit(DataDirectoryResource directory) {
+                  dataResourceConsumer.accept(directory, options.reporter);
+                  options.reporter.failIfPendingErrors();
+                }
 
-            @Override
-            public void visit(DataEntryResource file) {
-              dataResourceConsumer.accept(file, options.reporter);
-              options.reporter.failIfPendingErrors();
-            }
-          });
+                @Override
+                public void visit(DataEntryResource file) {
+                  DataEntryResource adapted = resourceAdapter.adaptIfNeeded(file);
+                  if (generatedResourceNames.add(adapted.getName())) {
+                    dataResourceConsumer.accept(adapted, options.reporter);
+                  } else {
+                    options.reporter.warning(
+                        new StringDiagnostic("Resource '" + file.getName() + "' already exists."));
+                  }
+                  options.reporter.failIfPendingErrors();
+                }
+              });
         } catch (ResourceException e) {
           throw new CompilationError(e.getMessage(), e);
         }
diff --git a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
index 519bd58..b151ef8 100644
--- a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
+++ b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
@@ -123,7 +123,7 @@
     DexCode code = method.getCode().asDexCode();
     // As we have rewritten the code, we now know that its highest string index that is not
     // a jumbo-string is firstJumboString (actually the previous string, but we do not have that).
-    method.setDexCode(new DexCode(
+    method.setCode(new DexCode(
         code.registerSize,
         code.incomingRegisterSize,
         code.outgoingRegisterSize,
diff --git a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
new file mode 100644
index 0000000..673fa6e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
@@ -0,0 +1,376 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.dex;
+
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.ProguardPathFilter;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.io.ByteStreams;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntStack;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+
+public class ResourceAdapter {
+
+  private final DexItemFactory dexItemFactory;
+  private final GraphLense graphLense;
+  private final NamingLens namingLense;
+  private final InternalOptions options;
+
+  public ResourceAdapter(
+      DexItemFactory dexItemFactory,
+      GraphLense graphLense,
+      NamingLens namingLense,
+      InternalOptions options) {
+    this.dexItemFactory = dexItemFactory;
+    this.graphLense = graphLense;
+    this.namingLense = namingLense;
+    this.options = options;
+  }
+
+  public DataEntryResource adaptIfNeeded(DataEntryResource file) {
+    // Adapt name, if needed.
+    ProguardPathFilter adaptResourceFileNamesFilter =
+        options.proguardConfiguration.getAdaptResourceFilenames();
+    String name =
+        adaptResourceFileNamesFilter.isEnabled()
+                && !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
+                && adaptResourceFileNamesFilter.matches(file.getName())
+            ? adaptFilename(file)
+            : file.getName();
+    assert name != null;
+    // Adapt contents, if needed.
+    ProguardPathFilter adaptResourceFileContentsFilter =
+        options.proguardConfiguration.getAdaptResourceFileContents();
+    byte[] contents =
+        adaptResourceFileContentsFilter.isEnabled()
+                && !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
+                && adaptResourceFileContentsFilter.matches(file.getName())
+            ? adaptFileContents(file)
+            : null;
+    // Return a new resource if the name or contents changed. Otherwise return the original
+    // resource as it was.
+    if (contents != null) {
+      // File contents was adapted. Return a new resource that has the new contents, and a new name,
+      // if the filename was adapted.
+      return DataEntryResource.fromBytes(contents, name, file.getOrigin());
+    }
+    if (!name.equals(file.getName())) {
+      // File contents was not adapted, but filename was.
+      return file.withName(name);
+    }
+    // Neither file contents nor filename was adapted.
+    return file;
+  }
+
+  private String adaptFilename(DataEntryResource file) {
+    FilenameAdapter adapter = new FilenameAdapter(file.getName());
+    if (adapter.run()) {
+      return adapter.getResult();
+    }
+    return file.getName();
+  }
+
+  // According to the Proguard documentation, the resource files should be parsed and written using
+  // the platform's default character set.
+  private byte[] adaptFileContents(DataEntryResource file) {
+    try (InputStream in = file.getByteStream()) {
+      byte[] bytes = ByteStreams.toByteArray(in);
+      String contents = new String(bytes, Charset.defaultCharset());
+
+      FileContentsAdapter adapter = new FileContentsAdapter(contents);
+      if (adapter.run()) {
+        return adapter.getResult().getBytes(Charset.defaultCharset());
+      }
+    } catch (ResourceException e) {
+      options.reporter.error(
+          new StringDiagnostic("Failed to open input: " + e.getMessage(), file.getOrigin()));
+    } catch (Exception e) {
+      options.reporter.error(new ExceptionDiagnostic(e, file.getOrigin()));
+    }
+    // Return null to signal that the file contents did not change. Otherwise we would have to copy
+    // the original file for no reason.
+    return null;
+  }
+
+  private abstract class StringAdapter {
+
+    protected final String contents;
+    private final StringBuilder result = new StringBuilder();
+
+    // If any type names in `contents` have been updated. If this flag is still true in the end,
+    // then we can simply use the resource as it was.
+    private boolean changed = false;
+    private int outputFrom = 0;
+    private int position = 0;
+
+    // When renaming Java type names, the adapter always looks for the longest name to rewrite.
+    // For example, if there is a resource with the name "foo/bar/C$X$Y.txt", then the adapter will
+    // check if there is a renaming for the type "foo.bar.C$X$Y". If there is no such renaming, then
+    // -adaptresourcefilenames works in such a way that "foo/bar/C$X" should be rewritten if there
+    // is a renaming for the type "foo.bar.C$X". Therefore, when scanning forwards to read the
+    // substring "foo/bar/C$X$Y", this adapter records the positions of the two '$' characters in
+    // the stack `prefixEndPositionsExclusive`, such that it can easily backtrack to the previously
+    // valid, but shorter Java type name.
+    //
+    // Note that there is no backtracking for -adaptresourcefilecontents.
+    private final IntStack prefixEndPositionsExclusive;
+
+    public StringAdapter(String contents) {
+      this.contents = contents;
+      this.prefixEndPositionsExclusive = allowRenamingOfPrefixes() ? new IntArrayList() : null;
+    }
+
+    public boolean run() {
+      do {
+        handleMisc();
+        handleJavaType();
+      } while (!eof());
+      if (changed) {
+        // At least one type was renamed. We need to flush all characters in `contents` that follow
+        // the last type that was renamed.
+        outputRangeFromInput(outputFrom, contents.length());
+      } else {
+        // No types were renamed. In this case the adapter should simply have scanned through
+        // `contents`, without outputting anything to `result`.
+        assert outputFrom == 0;
+        assert result.toString().isEmpty();
+      }
+      return changed;
+    }
+
+    public String getResult() {
+      assert changed;
+      return result.toString();
+    }
+
+    // Forwards the cursor until the current character is a Java identifier part.
+    private void handleMisc() {
+      while (!eof() && !Character.isJavaIdentifierPart(contents.charAt(position))) {
+        position++;
+      }
+    }
+
+    // Reads a Java type from the current position in `contents`, and then checks if the given
+    // type has been renamed.
+    private void handleJavaType() {
+      if (eof()) {
+        return;
+      }
+
+      assert !allowRenamingOfPrefixes() || prefixEndPositionsExclusive.isEmpty();
+
+      assert Character.isJavaIdentifierPart(contents.charAt(position));
+      int start = position++;
+      while (!eof()) {
+        char currentChar = contents.charAt(position);
+        if (Character.isJavaIdentifierPart(currentChar)) {
+          if (allowRenamingOfPrefixes()
+              && shouldRecordPrefix(currentChar)
+              && isRenamingCandidate(start, position)) {
+            prefixEndPositionsExclusive.push(position);
+          }
+          position++;
+          continue;
+        }
+        if (currentChar == getClassNameSeparator()
+            && !eof(position + 1)
+            && Character.isJavaIdentifierPart(contents.charAt(position + 1))) {
+          if (allowRenamingOfPrefixes()
+              && shouldRecordPrefix(currentChar)
+              && isRenamingCandidate(start, position)) {
+            prefixEndPositionsExclusive.push(position);
+          }
+          // Consume the dot and the Java identifier part that follows the dot.
+          position += 2;
+          continue;
+        }
+
+        // Not a valid extension of the type name.
+        break;
+      }
+
+      boolean renamingSucceeded =
+          isRenamingCandidate(start, position) && renameJavaTypeInRange(start, position);
+      if (!renamingSucceeded && allowRenamingOfPrefixes()) {
+        while (!prefixEndPositionsExclusive.isEmpty() && !renamingSucceeded) {
+          int prefixEndExclusive = prefixEndPositionsExclusive.popInt();
+          assert isRenamingCandidate(start, prefixEndExclusive);
+          renamingSucceeded = handlePrefix(start, prefixEndExclusive);
+        }
+      }
+
+      if (allowRenamingOfPrefixes()) {
+        while (!prefixEndPositionsExclusive.isEmpty()) {
+          prefixEndPositionsExclusive.popInt();
+        }
+      }
+    }
+
+    // Returns true if the Java type in the range [from; toExclusive[ was renamed.
+    protected boolean renameJavaTypeInRange(int from, int toExclusive) {
+      String javaType = contents.substring(from, toExclusive);
+      if (getClassNameSeparator() != '.') {
+        javaType = javaType.replace(getClassNameSeparator(), '.');
+      }
+      DexString descriptor =
+          dexItemFactory.lookupString(
+              DescriptorUtils.javaTypeToDescriptorIgnorePrimitives(javaType));
+      DexType dexType = descriptor != null ? dexItemFactory.lookupType(descriptor) : null;
+      if (dexType != null) {
+        DexString renamedDescriptor = namingLense.lookupDescriptor(graphLense.lookupType(dexType));
+        if (!descriptor.equals(renamedDescriptor)) {
+          String renamedJavaType =
+              DescriptorUtils.descriptorToJavaType(renamedDescriptor.toSourceString());
+          // Need to flush all changes up to and excluding 'from', and then output the renamed
+          // type.
+          outputRangeFromInput(outputFrom, from);
+          outputJavaType(
+              getClassNameSeparator() != '.'
+                  ? renamedJavaType.replace('.', getClassNameSeparator())
+                  : renamedJavaType);
+          outputFrom = toExclusive;
+          changed = true;
+          return true;
+        }
+      }
+      return false;
+    }
+
+    // Returns true if the Java package in the range [from; toExclusive[ was renamed.
+    protected boolean renameJavaPackageInRange(int from, int toExclusive) {
+      String javaPackage = contents.substring(from, toExclusive);
+      if (getClassNameSeparator() != '/') {
+        javaPackage = javaPackage.replace(getClassNameSeparator(), '/');
+      }
+      String minifiedJavaPackage = namingLense.lookupPackageName(javaPackage);
+      if (!javaPackage.equals(minifiedJavaPackage)) {
+        outputRangeFromInput(outputFrom, from);
+        outputJavaType(
+            getClassNameSeparator() != '/'
+                ? minifiedJavaPackage.replace('/', getClassNameSeparator())
+                : minifiedJavaPackage);
+        outputFrom = toExclusive;
+        changed = true;
+        return true;
+      }
+      return false;
+    }
+
+    protected abstract char getClassNameSeparator();
+
+    protected abstract boolean allowRenamingOfPrefixes();
+
+    protected abstract boolean shouldRecordPrefix(char c);
+
+    protected abstract boolean handlePrefix(int from, int toExclusive);
+
+    protected abstract boolean isRenamingCandidate(int from, int toExclusive);
+
+    private void outputRangeFromInput(int from, int toExclusive) {
+      if (from < toExclusive) {
+        result.append(contents.substring(from, toExclusive));
+      }
+    }
+
+    private void outputJavaType(String s) {
+      result.append(s);
+    }
+
+    protected boolean eof() {
+      return eof(position);
+    }
+
+    protected boolean eof(int position) {
+      return position == contents.length();
+    }
+  }
+
+  private class FileContentsAdapter extends StringAdapter {
+
+    public FileContentsAdapter(String fileContents) {
+      super(fileContents);
+    }
+
+    @Override
+    public char getClassNameSeparator() {
+      return '.';
+    }
+
+    @Override
+    public boolean allowRenamingOfPrefixes() {
+      return false;
+    }
+
+    @Override
+    public boolean shouldRecordPrefix(char c) {
+      throw new Unreachable();
+    }
+
+    @Override
+    protected boolean handlePrefix(int from, int toExclusive) {
+      throw new Unreachable();
+    }
+
+    @Override
+    public boolean isRenamingCandidate(int from, int toExclusive) {
+      // If the Java type starts with '-' or '.', it should not be renamed.
+      return (from <= 0 || !isDashOrDot(contents.charAt(from - 1)))
+          && (eof(toExclusive) || !isDashOrDot(contents.charAt(toExclusive)));
+    }
+
+    private boolean isDashOrDot(char c) {
+      return c == '.' || c == '-';
+    }
+  }
+
+  private class FilenameAdapter extends StringAdapter {
+
+    public FilenameAdapter(String filename) {
+      super(filename);
+    }
+
+    @Override
+    public char getClassNameSeparator() {
+      return '/';
+    }
+
+    @Override
+    public boolean allowRenamingOfPrefixes() {
+      return true;
+    }
+
+    @Override
+    public boolean shouldRecordPrefix(char c) {
+      return !Character.isLetterOrDigit(c);
+    }
+
+    @Override
+    protected boolean handlePrefix(int from, int toExclusive) {
+      assert !eof(toExclusive);
+      if (contents.charAt(toExclusive) == '/') {
+        return renameJavaPackageInRange(from, toExclusive);
+      }
+      return renameJavaTypeInRange(from, toExclusive);
+    }
+
+    @Override
+    public boolean isRenamingCandidate(int from, int toExclusive) {
+      return from == 0 && !eof(toExclusive);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
index b0a5a3c..1956e8e 100644
--- a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
+++ b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
@@ -10,19 +10,29 @@
 import com.android.tools.r8.DexSplitterHelper;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.AbortException;
 import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.FeatureClassMapping;
 import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
 import com.android.tools.r8.utils.OptionsParsing;
 import com.android.tools.r8.utils.OptionsParsing.ParseContext;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.ZipUtils;
 import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Enumeration;
 import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
 
 @Keep
 public final class DexSplitter {
@@ -61,7 +71,18 @@
       }
       return featureName;
     }
+  }
 
+  private static class ZipFileOrigin extends PathOrigin {
+
+    public ZipFileOrigin(Path path) {
+      super(path);
+    }
+
+    @Override
+    public String part() {
+      return "splitting of file '" + super.part() + "'";
+    }
   }
 
   @Keep
@@ -73,6 +94,7 @@
     private String output = DEFAULT_OUTPUT_DIR;
     private String featureSplitMapping;
     private String proguardMap;
+    private boolean splitNonClassResources = false;
 
     public DiagnosticsHandler getDiagnosticsHandler() {
       return diagnosticsHandler;
@@ -126,6 +148,10 @@
       featureJars.add(new FeatureJar(jar, outputName));
     }
 
+    public void setSplitNonClassResources(boolean value) {
+      splitNonClassResources = value;
+    }
+
     public ImmutableList<String> getInputArchives() {
       return ImmutableList.copyOf(inputArchives);
     }
@@ -193,6 +219,11 @@
         options.setFeatureSplitMapping(featureSplit);
         continue;
       }
+      Boolean b = OptionsParsing.tryParseBoolean(context, "--split-non-class-resources");
+      if (b != null) {
+        options.setSplitNonClassResources(b);
+        continue;
+      }
       throw new RuntimeException(String.format("Unknown options: '%s'.", context.head()));
     }
     return options;
@@ -246,6 +277,39 @@
 
     DexSplitterHelper.run(
         builder.build(), featureClassMapping, options.getOutput(), options.getProguardMap());
+
+    if (options.splitNonClassResources) {
+      splitNonClassResources(options, featureClassMapping);
+    }
+  }
+
+  private static void splitNonClassResources(Options options,
+      FeatureClassMapping featureClassMapping) {
+    for (String s : options.inputArchives) {
+      try (ZipFile zipFile = new ZipFile(s, StandardCharsets.UTF_8)) {
+        Enumeration<? extends ZipEntry> entries = zipFile.entries();
+        while (entries.hasMoreElements()) {
+          ZipEntry entry = entries.nextElement();
+          String name = entry.getName();
+          if (!ZipUtils.isDexFile(name) && !ZipUtils.isClassFile(name)) {
+            String feature = featureClassMapping.featureForNonClass(name);
+            Path outputDir = Paths.get(options.getOutput()).resolve(feature);
+            try (InputStream stream = zipFile.getInputStream(entry)) {
+              Path outputFile = outputDir.resolve(name);
+              Path parent = outputFile.getParent();
+              if (parent != null) {
+                Files.createDirectories(parent);
+              }
+              Files.copy(stream, outputFile);
+            }
+          }
+        }
+      } catch (IOException e) {
+        options.getDiagnosticsHandler().error(
+            new ExceptionDiagnostic(e, new ZipFileOrigin(Paths.get(s))));
+        throw new AbortException();
+      }
+    }
   }
 
   public static void main(String[] args) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 55c4134..c29453a 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -489,17 +489,22 @@
   }
 
   public boolean canTriggerStaticInitializer(DexType type, boolean ignoreTypeItself) {
+    DexClass clazz = definitionFor(type);
+    assert clazz != null;
+    return canTriggerStaticInitializer(clazz, ignoreTypeItself);
+  }
+
+  public boolean canTriggerStaticInitializer(DexClass clazz, boolean ignoreTypeItself) {
     Set<DexType> knownInterfaces = Sets.newIdentityHashSet();
 
     // Process superclass chain.
-    DexType clazz = type;
-    while (clazz != null && clazz != dexItemFactory.objectType) {
-      DexClass definition = definitionFor(clazz);
-      if (canTriggerStaticInitializer(definition) && (!ignoreTypeItself || clazz != type)) {
+    DexClass current = clazz;
+    while (current != null && current.type != dexItemFactory.objectType) {
+      if (canTriggerStaticInitializer(current) && (!ignoreTypeItself || current != clazz)) {
         return true;
       }
-      knownInterfaces.addAll(Arrays.asList(definition.interfaces.values));
-      clazz = definition.superType;
+      knownInterfaces.addAll(Arrays.asList(current.interfaces.values));
+      current = current.superType != null ? definitionFor(current.superType) : null;
     }
 
     // Process interfaces.
@@ -523,7 +528,7 @@
     return false;
   }
 
-  private static boolean canTriggerStaticInitializer(DexClass clazz) {
+  public static boolean canTriggerStaticInitializer(DexClass clazz) {
     // Assume it *may* trigger if we didn't find the definition.
     return clazz == null || clazz.hasClassInitializer();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 776cc42..d588ab5 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -201,27 +201,35 @@
 
   @Override
   public IRCode buildIR(
-      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
-    return internalBuild(encodedMethod, appInfo, options, null, null, origin);
+      DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
+      GraphLense graphLense,
+      InternalOptions options,
+      Origin origin) {
+    assert getOwner() == encodedMethod;
+    return internalBuild(encodedMethod, appInfo, graphLense, options, null, null, origin);
   }
 
   @Override
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
       AppInfo appInfo,
+      GraphLense graphLense,
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin) {
+    assert getOwner() == encodedMethod;
     assert valueNumberGenerator != null;
     assert callerPosition != null;
     return internalBuild(
-        encodedMethod, appInfo, options, valueNumberGenerator, callerPosition, origin);
+        encodedMethod, appInfo, graphLense, options, valueNumberGenerator, callerPosition, origin);
   }
 
   private IRCode internalBuild(
       DexEncodedMethod encodedMethod,
       AppInfo appInfo,
+      GraphLense graphLense,
       InternalOptions options,
       ValueNumberGenerator generator,
       Position callerPosition,
@@ -235,14 +243,11 @@
         new CfSourceCode(
             this,
             encodedMethod,
+            graphLense.getOriginalMethodSignature(encodedMethod.method),
             callerPosition,
             origin,
             options.lineNumberOptimization == LineNumberOptimization.ON);
-    IRBuilder builder =
-        (generator == null)
-            ? new IRBuilder(encodedMethod, appInfo, source, options)
-            : new IRBuilder(encodedMethod, appInfo, source, options, generator);
-    return builder.build();
+    return new IRBuilder(encodedMethod, appInfo, source, options, generator).build();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/ClassKind.java b/src/main/java/com/android/tools/r8/graph/ClassKind.java
index 94b2076..92ae40e 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassKind.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassKind.java
@@ -1,3 +1,7 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.ProgramResource;
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 60a5ef5..187756a 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -16,12 +16,29 @@
 
 public abstract class Code extends CachedHashValueDexItem {
 
+  private DexEncodedMethod owner;
+
+  public void setOwner(DexEncodedMethod encodedMethod) {
+    // When this Code is un/linked to DexEncodedMethod, the ownership should be updated accordingly.
+    owner = encodedMethod;
+  }
+
+  public DexEncodedMethod getOwner() {
+    // build*IR() will check if the current Code belongs to the given DexEncodedMethod.
+    return owner;
+  }
+
   public abstract IRCode buildIR(
-      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin);
+      DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
+      GraphLense graphLense,
+      InternalOptions options,
+      Origin origin);
 
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
       AppInfo appInfo,
+      GraphLense graphLense,
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 64c85f3..fc1ef7b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.kotlin.KotlinInfo;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.Iterators;
 import java.util.Arrays;
@@ -138,16 +137,6 @@
     }
   }
 
-  public <E extends Throwable> void forEachMethodThrowing(
-      ThrowingConsumer<DexEncodedMethod, E> consumer) throws E {
-    for (DexEncodedMethod method : directMethods()) {
-      consumer.accept(method);
-    }
-    for (DexEncodedMethod method : virtualMethods()) {
-      consumer.accept(method);
-    }
-  }
-
   public DexEncodedMethod[] allMethodsSorted() {
     int vLen = virtualMethods.length;
     int dLen = directMethods.length;
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index b1b5f8a..d5f8001 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -62,6 +62,17 @@
     hashCode();  // Cache the hash code eagerly.
   }
 
+  public DexCode withoutThisParameter() {
+    // Note that we assume the original code has a register associated with 'this'
+    // argument of the (former) instance method. We also assume (but do not check)
+    // that 'this' register is never used, so when we decrease incoming register size
+    // by 1, it becomes just a regular register which is never used, and thus will be
+    // gone when we build an IR from this code. Rebuilding IR for methods 'staticized'
+    // this way is highly recommended to improve register allocation.
+    return new DexCode(registerSize, incomingRegisterSize - 1, outgoingRegisterSize,
+        instructions, tries, handlers, debugInfoWithoutFirstParameter(), highestSortingString);
+  }
+
   @Override
   public boolean isDexCode() {
     return true;
@@ -107,6 +118,19 @@
     return new DexDebugInfo(debugInfo.startLine, newParameters, debugInfo.events);
   }
 
+  public DexDebugInfo debugInfoWithoutFirstParameter() {
+    if (debugInfo == null) {
+      return null;
+    }
+    DexString[] parameters = debugInfo.parameters;
+    if(parameters.length == 0) {
+      return debugInfo;
+    }
+    DexString[] newParameters = new DexString[parameters.length - 1];
+    System.arraycopy(parameters, 1, newParameters, 0, parameters.length - 1);
+    return new DexDebugInfo(debugInfo.startLine, newParameters, debugInfo.events);
+  }
+
   public int codeSizeInBytes() {
     Instruction last = instructions[instructions.length - 1];
     return last.getOffset() + last.getSize();
@@ -164,10 +188,19 @@
 
   @Override
   public IRCode buildIR(
-      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
+      DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
+      GraphLense graphLense,
+      InternalOptions options,
+      Origin origin) {
+    assert getOwner() == encodedMethod;
     DexSourceCode source =
         new DexSourceCode(
-            this, encodedMethod, null, options.lineNumberOptimization == LineNumberOptimization.ON);
+            this,
+            encodedMethod,
+            graphLense.getOriginalMethodSignature(encodedMethod.method),
+            null,
+            options.lineNumberOptimization == LineNumberOptimization.ON);
     IRBuilder builder = new IRBuilder(encodedMethod, appInfo, source, options);
     return builder.build();
   }
@@ -176,14 +209,17 @@
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
       AppInfo appInfo,
+      GraphLense graphLense,
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin) {
+    assert getOwner() == encodedMethod;
     DexSourceCode source =
         new DexSourceCode(
             this,
             encodedMethod,
+            graphLense.getOriginalMethodSignature(encodedMethod.method),
             callerPosition,
             options.lineNumberOptimization == LineNumberOptimization.ON);
     IRBuilder builder =
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java b/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
index a02e546..e17446d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugPositionState.java
@@ -1,3 +1,7 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.graph.DexDebugEvent.AdvanceLine;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 3586d6d..c540d24 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.JumboStringRewriter;
 import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo.ResolutionResult;
 import com.android.tools.r8.graph.ParameterUsagesInfo.ParameterUsage;
 import com.android.tools.r8.ir.code.IRCode;
@@ -32,7 +33,7 @@
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
@@ -57,6 +58,7 @@
    * <p>
    * We also use this enum to encode under what constraints a method may be inlined.
    */
+  // TODO(b/111080693): Need to extend this to a state with the context.
   public enum CompilationState {
     /**
      * Has not been processed, yet.
@@ -72,7 +74,7 @@
      */
     PROCESSED_INLINING_CANDIDATE_ANY,
     /**
-     * Code also contains instructions that access protected entities that reside in a differnt
+     * Code also contains instructions that access protected entities that reside in a different
      * package and hence require subclass relationship to be visible.
      */
     PROCESSED_INLINING_CANDIDATE_SUBCLASS,
@@ -96,6 +98,8 @@
   public DexAnnotationSet annotations;
   public ParameterAnnotationsList parameterAnnotationsList;
   private Code code;
+  // TODO(b/111080693): towards finer-grained inlining constraints,
+  //   we need to maintain a set of states with (potentially different) contexts.
   private CompilationState compilationState = CompilationState.NOT_PROCESSED;
   private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT;
   private int classFileVersion = -1;
@@ -112,6 +116,7 @@
     this.parameterAnnotationsList = parameterAnnotationsList;
     this.code = code;
     assert code == null || !accessFlags.isAbstract();
+    setCodeOwnership();
   }
 
   public DexEncodedMethod(
@@ -208,6 +213,7 @@
       assert isInliningCandidate(containerType, Reason.SIMPLE, appInfo);
       return true;
     }
+    // TODO(b/111080693): inlining candidate should satisfy all states if multiple states are there.
     switch (compilationState) {
       case PROCESSED_INLINING_CANDIDATE_ANY:
         return true;
@@ -222,9 +228,9 @@
     }
   }
 
-  public boolean markProcessed(Constraint state) {
+  public boolean markProcessed(ConstraintWithTarget state) {
     CompilationState prevCompilationState = compilationState;
-    switch (state) {
+    switch (state.constraint) {
       case ALWAYS:
         compilationState = PROCESSED_INLINING_CANDIDATE_ANY;
         break;
@@ -248,27 +254,32 @@
     compilationState = CompilationState.NOT_PROCESSED;
   }
 
-  public IRCode buildIR(AppInfo appInfo, InternalOptions options, Origin origin) {
-    return code == null ? null : code.buildIR(this, appInfo, options, origin);
+  public IRCode buildIR(
+      AppInfo appInfo, GraphLense graphLense, InternalOptions options, Origin origin) {
+    return code == null ? null : code.buildIR(this, appInfo, graphLense, options, origin);
   }
 
   public IRCode buildInliningIRForTesting(
       InternalOptions options, ValueNumberGenerator valueNumberGenerator) {
-    return buildInliningIR(null, options, valueNumberGenerator, null, Origin.unknown());
+    return buildInliningIR(
+        null, GraphLense.getIdentityLense(), options, valueNumberGenerator, null, Origin.unknown());
   }
 
   public IRCode buildInliningIR(
       AppInfo appInfo,
+      GraphLense graphLense,
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin) {
     return code.buildInliningIR(
-        this, appInfo, options, valueNumberGenerator, callerPosition, origin);
+        this, appInfo, graphLense, options, valueNumberGenerator, callerPosition, origin);
   }
 
   public void setCode(Code code) {
+    voidCodeOwnership();
     this.code = code;
+    setCodeOwnership();
   }
 
   public void setCode(
@@ -276,7 +287,7 @@
       RegisterAllocator registerAllocator,
       InternalOptions options) {
     final DexBuilder builder = new DexBuilder(ir, registerAllocator, options);
-    code = builder.build(method.getArity());
+    setCode(builder.build(method.getArity()));
   }
 
   @Override
@@ -308,12 +319,21 @@
     return code;
   }
 
-  public void setDexCode(DexCode code) {
-    this.code = code;
+  public void removeCode() {
+    voidCodeOwnership();
+    code = null;
   }
 
-  public void removeCode() {
-    code = null;
+  private void setCodeOwnership() {
+    if (code != null) {
+      code.setOwner(this);
+    }
+  }
+
+  private void voidCodeOwnership() {
+    if (code != null) {
+      code.setOwner(null);
+    }
   }
 
   public boolean hasDebugPositions() {
@@ -381,6 +401,7 @@
 
   public DexEncodedMethod toAbstractMethod() {
     accessFlags.setAbstract();
+    voidCodeOwnership();
     this.code = null;
     return this;
   }
@@ -534,6 +555,14 @@
     return builder.build();
   }
 
+  public DexEncodedMethod toStaticMethodWithoutThis() {
+    assert !accessFlags.isStatic();
+    Builder builder = builder(this);
+    builder.setStatic();
+    builder.withoutThisParameter();
+    return builder.build();
+  }
+
   /**
    * Rewrites the code in this method to have JumboString bytecode if required by mapping.
    * <p>
@@ -634,6 +663,7 @@
     // class inliner, null value indicates that the method is not eligible.
     private ClassInlinerEligibility classInlinerEligibility = null;
     private TrivialInitializer trivialInitializerInfo = null;
+    private boolean initializerEnablingJavaAssertions = false;
     private ParameterUsagesInfo parametersUsages = null;
     private BitSet kotlinNotNullParamHints = null;
 
@@ -650,6 +680,7 @@
       forceInline = template.forceInline;
       useIdentifierNameString = template.useIdentifierNameString;
       checksNullReceiverBeforeAnySideEffect = template.checksNullReceiverBeforeAnySideEffect;
+      trivialInitializerInfo = template.trivialInitializerInfo;
     }
 
     public void setParameterUsages(ParameterUsagesInfo parametersUsages) {
@@ -705,6 +736,14 @@
       return this.trivialInitializerInfo;
     }
 
+    private void setInitializerEnablingJavaAssertions() {
+      this.initializerEnablingJavaAssertions = true;
+    }
+
+    public boolean isInitializerEnablingJavaAssertions() {
+      return initializerEnablingJavaAssertions;
+    }
+
     public long getReturnedConstant() {
       assert returnsConstant();
       return returnedConstant;
@@ -754,6 +793,10 @@
       forceInline = true;
     }
 
+    private void unsetForceInline() {
+      forceInline = false;
+    }
+
     private void markPublicized() {
       publicized = true;
     }
@@ -831,10 +874,18 @@
     ensureMutableOI().setTrivialInitializer(info);
   }
 
+  synchronized public void setInitializerEnablingJavaAssertions() {
+    ensureMutableOI().setInitializerEnablingJavaAssertions();
+  }
+
   synchronized public void markForceInline() {
     ensureMutableOI().markForceInline();
   }
 
+  public synchronized void unsetForceInline() {
+    ensureMutableOI().unsetForceInline();
+  }
+
   synchronized public void markPublicized() {
     ensureMutableOI().markPublicized();
   }
@@ -900,6 +951,19 @@
       this.method = method;
     }
 
+    public void setStatic() {
+      this.accessFlags.setStatic();
+    }
+
+    public void withoutThisParameter() {
+      assert code != null;
+      if (code.isDexCode()) {
+        code = code.asDexCode().withoutThisParameter();
+      } else {
+        throw new Unreachable("Code " + code.getClass().getSimpleName() + " is not supported.");
+      }
+    }
+
     public void setCode(Code code) {
       this.code = code;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexField.java b/src/main/java/com/android/tools/r8/graph/DexField.java
index 98c523d..dcab5ae 100644
--- a/src/main/java/com/android/tools/r8/graph/DexField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexField.java
@@ -117,6 +117,10 @@
     return clazz;
   }
 
+  public String qualifiedName() {
+    return clazz + "." + name;
+  }
+
   @Override
   public String toSmaliString() {
     return clazz.toSmaliString() + "->" + name + ":" + type.toSmaliString();
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 91b1dc7..3f02009 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -573,6 +573,10 @@
     return canonicalize(strings, new DexString(source));
   }
 
+  public DexString lookupString(String source) {
+    return strings.get(new DexString(source));
+  }
+
   // TODO(b/67934123) Unify into one method,
   public DexItemBasedString createItemBasedString(DexType type) {
     assert !sorted;
@@ -635,7 +639,7 @@
     return createType(createString(descriptor));
   }
 
-  synchronized public DexType lookupType(DexString descriptor) {
+  public DexType lookupType(DexString descriptor) {
     return types.get(descriptor);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 02d2898..006da92 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -118,6 +118,10 @@
     return this == other || isStrictSubtypeOf(other, appInfo);
   }
 
+  public boolean hasSubtypes() {
+    return !directSubtypes.isEmpty();
+  }
+
   public boolean isStrictSubtypeOf(DexType other, AppInfo appInfo) {
     if (this == other) {
       return false;
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 8de4a6e..9b90df2 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.ir.code.Invoke.Type;
+import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableSet;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
@@ -78,7 +79,8 @@
       if (typeMap.isEmpty() && methodMap.isEmpty() && fieldMap.isEmpty()) {
         return previousLense;
       }
-      return new NestedGraphLense(typeMap, methodMap, fieldMap, previousLense, dexItemFactory);
+      return new NestedGraphLense(
+          typeMap, methodMap, fieldMap, null, null, previousLense, dexItemFactory);
     }
 
   }
@@ -87,6 +89,29 @@
     return new Builder();
   }
 
+  public abstract DexField getOriginalFieldSignature(DexField field);
+
+  public abstract DexMethod getOriginalMethodSignature(DexMethod method);
+
+  public abstract DexField getRenamedFieldSignature(DexField originalField);
+
+  public abstract DexMethod getRenamedMethodSignature(DexMethod originalMethod);
+
+  public final DexEncodedMethod mapDexEncodedMethod(
+      AppInfo appInfo, DexEncodedMethod originalEncodedMethod) {
+    DexMethod newMethod = getRenamedMethodSignature(originalEncodedMethod.method);
+    if (newMethod != originalEncodedMethod.method) {
+      // We can't directly use AppInfo#definitionFor(DexMethod) since definitions may not be
+      // updated either yet.
+      DexClass newHolder = appInfo.definitionFor(newMethod.holder);
+      assert newHolder != null;
+      DexEncodedMethod newEncodedMethod = newHolder.lookupMethod(newMethod);
+      assert newEncodedMethod != null;
+      return newEncodedMethod;
+    }
+    return originalEncodedMethod;
+  }
+
   public abstract DexType lookupType(DexType type);
 
   // This overload can be used when the graph lense is known to be context insensitive.
@@ -134,7 +159,7 @@
     return this instanceof IdentityGraphLense;
   }
 
-  public boolean assertNotModified(Iterable<DexItem> items) {
+  public <T extends DexItem> boolean assertNotModified(Iterable<T> items) {
     for (DexItem item : items) {
       if (item instanceof DexClass) {
         DexType type = ((DexClass) item).type;
@@ -156,6 +181,26 @@
   private static class IdentityGraphLense extends GraphLense {
 
     @Override
+    public DexField getOriginalFieldSignature(DexField field) {
+      return field;
+    }
+
+    @Override
+    public DexMethod getOriginalMethodSignature(DexMethod method) {
+      return method;
+    }
+
+    @Override
+    public DexField getRenamedFieldSignature(DexField originalField) {
+      return originalField;
+    }
+
+    @Override
+    public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+      return originalMethod;
+    }
+
+    @Override
     public DexType lookupType(DexType type) {
       return type;
     }
@@ -197,16 +242,65 @@
     protected final Map<DexMethod, DexMethod> methodMap;
     protected final Map<DexField, DexField> fieldMap;
 
-    public NestedGraphLense(Map<DexType, DexType> typeMap, Map<DexMethod, DexMethod> methodMap,
-        Map<DexField, DexField> fieldMap, GraphLense previousLense, DexItemFactory dexItemFactory) {
+    // Maps that store the original signature of fields and methods that have been affected by
+    // vertical class merging. Needed to generate a correct Proguard map in the end.
+    private final BiMap<DexField, DexField> originalFieldSignatures;
+    private final BiMap<DexMethod, DexMethod> originalMethodSignatures;
+
+    public NestedGraphLense(
+        Map<DexType, DexType> typeMap,
+        Map<DexMethod, DexMethod> methodMap,
+        Map<DexField, DexField> fieldMap,
+        BiMap<DexField, DexField> originalFieldSignatures,
+        BiMap<DexMethod, DexMethod> originalMethodSignatures,
+        GraphLense previousLense,
+        DexItemFactory dexItemFactory) {
       this.typeMap = typeMap.isEmpty() ? null : typeMap;
       this.methodMap = methodMap;
       this.fieldMap = fieldMap;
+      this.originalFieldSignatures = originalFieldSignatures;
+      this.originalMethodSignatures = originalMethodSignatures;
       this.previousLense = previousLense;
       this.dexItemFactory = dexItemFactory;
     }
 
     @Override
+    public DexField getOriginalFieldSignature(DexField field) {
+      DexField originalField =
+          originalFieldSignatures != null
+              ? originalFieldSignatures.getOrDefault(field, field)
+              : field;
+      return previousLense.getOriginalFieldSignature(originalField);
+    }
+
+    @Override
+    public DexMethod getOriginalMethodSignature(DexMethod method) {
+      DexMethod originalMethod =
+          originalMethodSignatures != null
+              ? originalMethodSignatures.getOrDefault(method, method)
+              : method;
+      return previousLense.getOriginalMethodSignature(originalMethod);
+    }
+
+    @Override
+    public DexField getRenamedFieldSignature(DexField originalField) {
+      DexField renamedField =
+          originalFieldSignatures != null
+              ? originalFieldSignatures.inverse().getOrDefault(originalField, originalField)
+              : originalField;
+      return previousLense.getRenamedFieldSignature(renamedField);
+    }
+
+    @Override
+    public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+      DexMethod renamedMethod =
+          originalMethodSignatures != null
+              ? originalMethodSignatures.inverse().getOrDefault(originalMethod, originalMethod)
+              : originalMethod;
+      return previousLense.getRenamedMethodSignature(renamedMethod);
+    }
+
+    @Override
     public DexType lookupType(DexType type) {
       if (type.isArrayType()) {
         synchronized (this) {
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 64ba508..e998206 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -27,15 +27,21 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.FieldSignatureEquivalence;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.base.Equivalence.Wrapper;
 import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import org.objectweb.asm.AnnotationVisitor;
@@ -148,8 +154,10 @@
     private List<DexAnnotationElement> defaultAnnotations = null;
     private final List<DexEncodedField> staticFields = new ArrayList<>();
     private final List<DexEncodedField> instanceFields = new ArrayList<>();
+    private final Set<Wrapper<DexField>> fieldSignatures = new HashSet<>();
     private final List<DexEncodedMethod> directMethods = new ArrayList<>();
     private final List<DexEncodedMethod> virtualMethods = new ArrayList<>();
+    private final Set<Wrapper<DexMethod>> methodSignatures = new HashSet<>();
 
     public CreateDexClassVisitor(
         Origin origin,
@@ -380,13 +388,20 @@
     public void visitEnd() {
       FieldAccessFlags flags = FieldAccessFlags.fromCfAccessFlags(cleanAccessFlags(access));
       DexField dexField = parent.application.getField(parent.type, name, desc);
-      DexAnnotationSet annotationSet = createAnnotationSet(annotations);
-      DexValue staticValue = flags.isStatic() ? getStaticValue(value, dexField.type) : null;
-      DexEncodedField field = new DexEncodedField(dexField, flags, annotationSet, staticValue);
-      if (flags.isStatic()) {
-        parent.staticFields.add(field);
+      Wrapper<DexField> signature = FieldSignatureEquivalence.get().wrap(dexField);
+      if (parent.fieldSignatures.add(signature)) {
+        DexAnnotationSet annotationSet = createAnnotationSet(annotations);
+        DexValue staticValue = flags.isStatic() ? getStaticValue(value, dexField.type) : null;
+        DexEncodedField field = new DexEncodedField(dexField, flags, annotationSet, staticValue);
+        if (flags.isStatic()) {
+          parent.staticFields.add(field);
+        } else {
+          parent.instanceFields.add(field);
+        }
       } else {
-        parent.instanceFields.add(field);
+        parent.application.options.reporter.warning(
+            new StringDiagnostic(
+                String.format("Field `%s` has multiple definitions", dexField.toSourceString())));
       }
     }
 
@@ -603,10 +618,20 @@
               annotationsList,
               code,
               parent.version);
-      if (flags.isStatic() || flags.isConstructor() || flags.isPrivate()) {
-        parent.directMethods.add(dexMethod);
+      Wrapper<DexMethod> signature = MethodSignatureEquivalence.get().wrap(method);
+      if (parent.methodSignatures.add(signature)) {
+        if (flags.isStatic() || flags.isConstructor() || flags.isPrivate()) {
+          parent.directMethods.add(dexMethod);
+        } else {
+          parent.virtualMethods.add(dexMethod);
+        }
       } else {
-        parent.virtualMethods.add(dexMethod);
+        internalOptions.reporter.warning(
+            new StringDiagnostic(
+                String.format(
+                    "Ignoring an implementation of the method `%s` because it has multiple "
+                        + "definitions",
+                    method.toSourceString())));
       }
       if (defaultAnnotation != null) {
         parent.addDefaultAnnotation(name, defaultAnnotation);
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 2c37c6a..629017b 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.JarSourceCode;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.jar.InliningConstraintVisitor;
 import com.android.tools.r8.jar.JarRegisterEffectsVisitor;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -108,59 +108,70 @@
 
   @Override
   public IRCode buildIR(
-      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
+      DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
+      GraphLense graphLense,
+      InternalOptions options,
+      Origin origin) {
+    assert getOwner() == encodedMethod;
     triggerDelayedParsingIfNeccessary();
     return options.debug
-        ? internalBuildWithLocals(encodedMethod, appInfo, options, null, null)
-        : internalBuild(encodedMethod, appInfo, options, null, null);
+        ? internalBuildWithLocals(encodedMethod, appInfo, graphLense, options, null, null)
+        : internalBuild(encodedMethod, appInfo, graphLense, options, null, null);
   }
 
   @Override
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
       AppInfo appInfo,
+      GraphLense graphLense,
       InternalOptions options,
       ValueNumberGenerator generator,
       Position callerPosition,
       Origin origin) {
+    assert getOwner() == encodedMethod;
     assert generator != null;
     triggerDelayedParsingIfNeccessary();
     return options.debug
-        ? internalBuildWithLocals(encodedMethod, appInfo, options, generator, callerPosition)
-        : internalBuild(encodedMethod, appInfo, options, generator, callerPosition);
+        ? internalBuildWithLocals(
+            encodedMethod, appInfo, graphLense, options, generator, callerPosition)
+        : internalBuild(encodedMethod, appInfo, graphLense, options, generator, callerPosition);
   }
 
   private IRCode internalBuildWithLocals(
       DexEncodedMethod encodedMethod,
       AppInfo appInfo,
+      GraphLense graphLense,
       InternalOptions options,
       ValueNumberGenerator generator,
       Position callerPosition) {
     try {
-      return internalBuild(encodedMethod, appInfo, options, generator, callerPosition);
+      return internalBuild(encodedMethod, appInfo, graphLense, options, generator, callerPosition);
     } catch (InvalidDebugInfoException e) {
       options.warningInvalidDebugInfo(encodedMethod, origin, e);
       node.localVariables.clear();
-      return internalBuild(encodedMethod, appInfo, options, generator, callerPosition);
+      return internalBuild(encodedMethod, appInfo, graphLense, options, generator, callerPosition);
     }
   }
 
   private IRCode internalBuild(
       DexEncodedMethod encodedMethod,
       AppInfo appInfo,
+      GraphLense graphLense,
       InternalOptions options,
       ValueNumberGenerator generator,
       Position callerPosition) {
     if (!options.debug) {
       node.localVariables.clear();
     }
-    JarSourceCode source = new JarSourceCode(
-        method.getHolder(), node, application, encodedMethod.method, callerPosition);
-    IRBuilder builder =
-        (generator == null)
-            ? new IRBuilder(encodedMethod, appInfo, source, options)
-            : new IRBuilder(encodedMethod, appInfo, source, options, generator);
-    return builder.build();
+    JarSourceCode source =
+        new JarSourceCode(
+            method.getHolder(),
+            node,
+            application,
+            graphLense.getOriginalMethodSignature(encodedMethod.method),
+            callerPosition);
+    return new IRBuilder(encodedMethod, appInfo, source, options, generator).build();
   }
 
   @Override
@@ -173,7 +184,7 @@
             DescriptorUtils.getDescriptorFromClassBinaryName(tryCatchBlockNode.type))));
   }
 
-  public Constraint computeInliningConstraint(
+  public ConstraintWithTarget computeInliningConstraint(
       DexEncodedMethod encodedMethod,
       AppInfoWithLiveness appInfo,
       GraphLense graphLense,
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index afc2969..f1c1700 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -146,6 +146,8 @@
     assert this.context != null;
     this.code = code;
     this.context = null;
+    // Propagate the ownership of LazyCfCode to CfCode.
+    code.setOwner(this.getOwner());
   }
 
   @Override
@@ -175,20 +177,34 @@
 
   @Override
   public IRCode buildIR(
-      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
-    return asCfCode().buildIR(encodedMethod, appInfo, options, origin);
+      DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
+      GraphLense graphLense,
+      InternalOptions options,
+      Origin origin) {
+    assert getOwner() == encodedMethod;
+    return asCfCode().buildIR(encodedMethod, appInfo, graphLense, options, origin);
   }
 
   @Override
   public IRCode buildInliningIR(
       DexEncodedMethod encodedMethod,
       AppInfo appInfo,
+      GraphLense graphLense,
       InternalOptions options,
       ValueNumberGenerator valueNumberGenerator,
       Position callerPosition,
       Origin origin) {
-    return asCfCode().buildInliningIR(
-        encodedMethod, appInfo, options, valueNumberGenerator, callerPosition, origin);
+    assert getOwner() == encodedMethod;
+    return asCfCode()
+        .buildInliningIR(
+            encodedMethod,
+            appInfo,
+            graphLense,
+            options,
+            valueNumberGenerator,
+            callerPosition,
+            origin);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
index 7f7ec36..024c0c9 100644
--- a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
@@ -1,3 +1,7 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.dex.ApplicationReader;
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
index 6948a08..20d7bf4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 
@@ -58,7 +58,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forAlwaysMaterializingUser();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index cff655a..c8240a0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 
@@ -76,7 +76,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forArgument();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index 01101f3..758252b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -21,7 +21,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import java.util.Arrays;
@@ -133,7 +133,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forArrayGet();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index cc358f0..445c9ed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import java.util.function.Function;
@@ -90,7 +90,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forArrayLength();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 023afce..0282ffa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.utils.InternalOptions;
@@ -154,7 +154,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forArrayPut();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Binop.java b/src/main/java/com/android/tools/r8/ir/code/Binop.java
index e5190bb..30fdc59 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Binop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Binop.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import java.util.function.Function;
@@ -124,7 +124,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forBinop();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index dba575f..cee01eb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.function.Function;
 
@@ -113,7 +113,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forCheckCast(type, invocationContext);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 3041aaf..c5a0a11 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.function.Function;
@@ -99,7 +99,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forConstClass(clazz, invocationContext);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstInstruction.java b/src/main/java/com/android/tools/r8/ir/code/ConstInstruction.java
index f6e212d..a6ac01d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstInstruction.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 
 public abstract class ConstInstruction extends Instruction {
@@ -29,7 +29,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forConstInstruction();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
index 637faf7..9c6b342 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 
@@ -60,7 +60,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forDebugLocalRead();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
index 53791e5..52c8dc6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
@@ -100,7 +100,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forDebugLocalsChange();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index aed2128..5a611aa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 
@@ -57,7 +57,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forDebugPosition();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Goto.java b/src/main/java/com/android/tools/r8/ir/code/Goto.java
index cd5fcc8..80a76fe 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Goto.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Goto.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.utils.CfgPrinter;
 import java.util.List;
+import java.util.ListIterator;
 
 public class Goto extends JumpInstruction {
 
@@ -103,6 +104,18 @@
     // Nothing to do.
   }
 
+  public boolean isTrivialGotoToTheNextBlock(IRCode code) {
+    BasicBlock thisBlock = getBlock();
+    ListIterator<BasicBlock> blockIterator = code.blocks.listIterator();
+    while (blockIterator.hasNext()) {
+      BasicBlock block = blockIterator.next();
+      if (thisBlock == block) {
+        return blockIterator.hasNext() && blockIterator.next() == getTarget();
+      }
+    }
+    return false;
+  }
+
   @Override
   public void buildCf(CfBuilder builder) {
     builder.add(new CfGoto(builder.getLabel(getTarget())));
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 1083669..50fc2ed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -22,7 +22,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.function.Function;
 import org.objectweb.asm.Opcodes;
@@ -114,7 +114,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forInstanceGet(field, invocationContext);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index 53fd31c..7a973f8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.function.Function;
 
@@ -81,7 +81,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forInstanceOf(type, invocationContext);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 3585348..e8d917d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -19,7 +19,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.Arrays;
 import org.objectweb.asm.Opcodes;
@@ -113,7 +113,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forInstancePut(field, invocationContext);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index eb85222..deae051 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.utils.CfgPrinter;
@@ -266,6 +266,14 @@
     block = null;
   }
 
+  public void removeOrReplaceByDebugLocalRead() {
+    getBlock().listIterator(this).removeOrReplaceByDebugLocalRead();
+  }
+
+  public void replace(Instruction newInstruction) {
+    getBlock().listIterator(this).replaceCurrentInstruction(newInstruction);
+  }
+
   /**
    * Returns true if the instruction is in the IR and therefore has a block.
    */
@@ -1059,7 +1067,7 @@
    *
    * <p>The type is used to judge visibility constraints and also for dispatch decisions.
    */
-  public abstract Constraint inliningConstraint(
+  public abstract ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext);
 
   public abstract void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index a6e6fd1..c12333a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.List;
 
@@ -101,7 +101,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forInvokeCustom();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index 88716dc..46d2e5a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.Collection;
@@ -113,7 +113,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forInvokeDirect(getInvokedMethod(), invocationContext);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index 06d368d..5f49322 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.Collection;
@@ -95,7 +95,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forInvokeInterface(getInvokedMethod(), invocationContext);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index e34d25f..821d707 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.List;
 import java.util.function.Function;
@@ -69,7 +69,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forInvokeMultiNewArray(type, invocationContext);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index a2a9c36..17f36df 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.List;
 import java.util.function.Function;
@@ -99,7 +99,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forInvokeNewArray(type, invocationContext);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index 8f90630..b45e984 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -126,7 +126,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forInvokePolymorphic(getInvokedMethod(), invocationContext);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index d2183c0..93660c7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -103,7 +103,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forInvokeStatic(getInvokedMethod(), invocationContext);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
index 920d94b..141d4d9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.Collection;
@@ -112,7 +112,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forInvokeSuper(getInvokedMethod(), invocationContext);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index f55a407..51bed08 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.Collection;
@@ -95,7 +95,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forInvokeVirtual(getInvokedMethod(), invocationContext);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
index 6096292..ded6002 100644
--- a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.List;
@@ -47,7 +47,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forJumpInstruction();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Load.java b/src/main/java/com/android/tools/r8/ir/code/Load.java
index 32654b8..5981249 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Load.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Load.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 
 public class Load extends Instruction {
@@ -51,7 +51,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forLoad();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Monitor.java b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
index 6b3c89c..70af068 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Monitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 
 public class Monitor extends Instruction {
@@ -34,6 +34,14 @@
     return inValues.get(0);
   }
 
+  public boolean isEnter() {
+    return type == Type.ENTER;
+  }
+
+  public boolean isExit() {
+    return type == Type.EXIT;
+  }
+
   @Override
   public void buildDex(DexBuilder builder) {
     // If the monitor object is an argument, we use the argument register for all the monitor
@@ -89,7 +97,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forMonitor();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index e474bb8..6223501 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.function.Function;
 
@@ -100,7 +100,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forMove();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index eabf7b2..e7d6e63 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.HashSet;
@@ -75,7 +75,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forMoveException();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index edca979..69867e6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.function.Function;
 
@@ -84,7 +84,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forNewArrayEmpty(type, invocationContext);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index ac46cc7..8dac404 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.Arrays;
@@ -112,7 +112,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forNewArrayFilledData();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 75931ad..92e7f3e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.function.Function;
 
@@ -81,7 +81,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forNewInstance(clazz, invocationContext);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NonNull.java b/src/main/java/com/android/tools/r8/ir/code/NonNull.java
index 462af06..d909144 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NonNull.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NonNull.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.function.Function;
 
@@ -85,7 +85,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forNonNull();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index 0fb1949..8ea55dd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 
@@ -50,7 +50,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forPop();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index 37cfaf0..bb750c2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 
 public class Return extends JumpInstruction {
@@ -116,7 +116,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forReturn();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 486f9e2..108beee 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -21,7 +21,7 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.function.Function;
 import org.objectweb.asm.Opcodes;
@@ -109,7 +109,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forStaticGet(field, invocationContext);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index f910ff6..8824d54 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -18,7 +18,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import org.objectweb.asm.Opcodes;
 
@@ -107,7 +107,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forStaticPut(field, invocationContext);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index b81af69..cec5501 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.utils.InternalOptions;
 
@@ -53,7 +53,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forStore();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Throw.java b/src/main/java/com/android/tools/r8/ir/code/Throw.java
index 4295dbe..7bda1c8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Throw.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Throw.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 
 public class Throw extends JumpInstruction {
@@ -65,7 +65,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forThrow();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Unop.java b/src/main/java/com/android/tools/r8/ir/code/Unop.java
index 2f6ed7f..b29afd0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Unop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Unop.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import java.util.function.Function;
 
@@ -48,7 +48,7 @@
   }
 
   @Override
-  public Constraint inliningConstraint(
+  public ConstraintWithTarget inliningConstraint(
       InliningConstraints inliningConstraints, DexType invocationContext) {
     return inliningConstraints.forUnop();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 650757e..382dd64 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -15,16 +16,23 @@
 import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.ThrowingBiConsumer;
+import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.Deque;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -56,7 +64,7 @@
     this.shuffle = options.testing.irOrdering;
   }
 
-  private static class Node {
+  public static class Node {
 
     public final DexEncodedMethod method;
     private int invokeCount = 0;
@@ -68,7 +76,7 @@
     // Incoming calls to this method.
     private final Set<Node> callers = new LinkedHashSet<>();
 
-    private Node(DexEncodedMethod method) {
+    public Node(DexEncodedMethod method) {
       this.method = method;
     }
 
@@ -76,19 +84,20 @@
       return method.accessFlags.isBridge();
     }
 
-    private void addCallee(Node method) {
+    public void addCallee(Node method) {
       callees.add(method);
+      method.callers.add(this);
     }
 
-    private void addCaller(Node method) {
-      callers.add(method);
+    public boolean hasCallee(Node method) {
+      return callees.contains(method);
     }
 
     boolean isSelfRecursive() {
       return isSelfRecursive;
     }
 
-    boolean isLeaf() {
+    public boolean isLeaf() {
       return callees.isEmpty();
     }
 
@@ -96,7 +105,7 @@
     public String toString() {
       StringBuilder builder = new StringBuilder();
       builder.append("MethodNode for: ");
-      builder.append(method.qualifiedName());
+      builder.append(method.toSourceString());
       builder.append(" (");
       builder.append(callees.size());
       builder.append(" callees, ");
@@ -114,7 +123,7 @@
         builder.append("Callees:\n");
         for (Node call : callees) {
           builder.append("  ");
-          builder.append(call.method.qualifiedName());
+          builder.append(call.method.toSourceString());
           builder.append("\n");
         }
       }
@@ -122,7 +131,7 @@
         builder.append("Callers:\n");
         for (Node caller : callers) {
           builder.append("  ");
-          builder.append(caller.method.qualifiedName());
+          builder.append(caller.method.toSourceString());
           builder.append("\n");
         }
       }
@@ -136,8 +145,12 @@
   private final Set<DexEncodedMethod> singleCallSite = Sets.newIdentityHashSet();
   private final Set<DexEncodedMethod> doubleCallSite = Sets.newIdentityHashSet();
 
-  public static CallGraph build(DexApplication application, AppInfoWithLiveness appInfo,
-      GraphLense graphLense, InternalOptions options) {
+  public static CallGraph build(
+      DexApplication application,
+      AppInfoWithLiveness appInfo,
+      GraphLense graphLense,
+      InternalOptions options,
+      Timing timing) {
     CallGraph graph = new CallGraph(options);
     DexClass[] classes = application.classes().toArray(new DexClass[application.classes().size()]);
     Arrays.sort(classes, (DexClass a, DexClass b) -> a.type.slowCompareTo(b.type));
@@ -149,8 +162,13 @@
       }
     }
     assert allMethodsExists(application, graph);
-    graph.breakCycles();
-    assert graph.breakCycles() == 0;  // This time the cycles should be gone.
+
+    timing.begin("Cycle elimination");
+    CycleEliminator cycleEliminator = new CycleEliminator(graph.nodes.values(), options);
+    cycleEliminator.breakCycles();
+    timing.end();
+    assert cycleEliminator.breakCycles() == 0; // This time the cycles should be gone.
+
     graph.fillCallSiteSets(appInfo);
     return graph;
   }
@@ -196,9 +214,10 @@
 
   /**
    * Extract the next set of leaves (nodes with an call (outgoing) degree of 0) if any.
-   * <p>
-   * All nodes in the graph are extracted if called repeatedly until null is returned.
-   * Please note that there are no cycles in this graph (see {@link #breakCycles}).
+   *
+   * <p>All nodes in the graph are extracted if called repeatedly until null is returned. Please
+   * note that there are no cycles in this graph (see {@link CycleEliminator#breakCycles}).
+   *
    * <p>
    */
   private Set<DexEncodedMethod> extractLeaves() {
@@ -215,48 +234,197 @@
         .collect(Collectors.toCollection(LinkedHashSet::new)));
   }
 
-  private int traverse(Node node, Set<Node> stack, Set<Node> marked) {
-    int numberOfCycles = 0;
-    if (!marked.contains(node)) {
-      assert !stack.contains(node);
-      stack.add(node);
-      ArrayList<Node> toBeRemoved = null;
-      // Sort the callees before calling traverse recursively.
-      // This will ensure cycles are broken the same way across
-      // multiple invocations of the R8 compiler.
+  public static class CycleEliminator {
+
+    public static final String CYCLIC_FORCE_INLINING_MESSAGE =
+        "Unable to satisfy force inlining constraints due to cyclic force inlining";
+
+    private static class CallEdge {
+
+      private final Node caller;
+      private final Node callee;
+
+      public CallEdge(Node caller, Node callee) {
+        this.caller = caller;
+        this.callee = callee;
+      }
+    }
+
+    private final Collection<Node> nodes;
+    private final InternalOptions options;
+
+    // DFS stack.
+    private Deque<Node> stack = new ArrayDeque<>();
+
+    // Set of nodes on the DFS stack.
+    private Set<Node> stackSet = Sets.newIdentityHashSet();
+
+    // Set of nodes that have been visited entirely.
+    private Set<Node> marked = Sets.newIdentityHashSet();
+
+    private int numberOfCycles = 0;
+
+    public CycleEliminator(Collection<Node> nodes, InternalOptions options) {
+      this.options = options;
+
+      // Call to reorderNodes must happen after assigning options.
+      this.nodes =
+          options.testing.nondeterministicCycleElimination
+              ? reorderNodes(new ArrayList<>(nodes))
+              : nodes;
+    }
+
+    public int breakCycles() {
+      // Break cycles in this call graph by removing edges causing cycles.
+      for (Node node : nodes) {
+        traverse(node);
+      }
+      int result = numberOfCycles;
+      reset();
+      return result;
+    }
+
+    private void reset() {
+      assert stack.isEmpty();
+      assert stackSet.isEmpty();
+      marked.clear();
+      numberOfCycles = 0;
+    }
+
+    private void traverse(Node node) {
+      if (marked.contains(node)) {
+        // Already visited all nodes that can be reached from this node.
+        return;
+      }
+
+      push(node);
+
+      // Sort the callees before calling traverse recursively. This will ensure cycles are broken
+      // the same way across multiple invocations of the R8 compiler.
       Node[] callees = node.callees.toArray(new Node[node.callees.size()]);
       Arrays.sort(callees, (Node a, Node b) -> a.method.method.slowCompareTo(b.method.method));
+      if (options.testing.nondeterministicCycleElimination) {
+        reorderNodes(Arrays.asList(callees));
+      }
+
       for (Node callee : callees) {
-        if (stack.contains(callee)) {
-          if (toBeRemoved == null) {
-            toBeRemoved = new ArrayList<>();
+        if (stackSet.contains(callee)) {
+          // Found a cycle that needs to be eliminated.
+          numberOfCycles++;
+
+          if (edgeRemovalIsSafe(node, callee)) {
+            // Break the cycle by removing the edge node->callee.
+            callee.callers.remove(node);
+            node.callees.remove(callee);
+
+            if (Log.ENABLED) {
+              Log.info(
+                  CallGraph.class,
+                  "Removed call edge from method '%s' to '%s'",
+                  node.method.toSourceString(),
+                  callee.method.toSourceString());
+            }
+          } else {
+            // The cycle has a method that is marked as force inline.
+            LinkedList<Node> cycle = extractCycle(callee);
+
+            if (Log.ENABLED) {
+              Log.info(
+                  CallGraph.class, "Extracted cycle to find an edge that can safely be removed");
+            }
+
+            // Break the cycle by finding an edge that can be removed without breaking force
+            // inlining. If that is not possible, this call fails with a compilation error.
+            CallEdge edge = findCallEdgeForRemoval(cycle);
+
+            // The edge will be null if this cycle has already been eliminated as a result of
+            // another cycle elimination.
+            if (edge != null) {
+              assert edgeRemovalIsSafe(edge.caller, edge.callee);
+
+              // Break the cycle by removing the edge caller->callee.
+              edge.caller.callees.remove(edge.callee);
+              edge.callee.callers.remove(edge.caller);
+
+              if (Log.ENABLED) {
+                Log.info(
+                    CallGraph.class,
+                    "Removed call edge from force inlined method '%s' to '%s' to ensure that "
+                        + "force inlining will succeed",
+                    node.method.toSourceString(),
+                    callee.method.toSourceString());
+              }
+            }
+
+            // Recover the stack.
+            recoverStack(cycle);
           }
-          // We have a cycle; break it by removing node->callee.
-          toBeRemoved.add(callee);
-          callee.callers.remove(node);
         } else {
-          numberOfCycles += traverse(callee, stack, marked);
+          traverse(callee);
         }
       }
-      if (toBeRemoved != null) {
-        numberOfCycles += toBeRemoved.size();
-        node.callees.removeAll(toBeRemoved);
-      }
-      stack.remove(node);
+      pop(node);
       marked.add(node);
     }
-    return numberOfCycles;
-  }
 
-  private int breakCycles() {
-    // Break cycles in this call graph by removing edges causing cycles.
-    int numberOfCycles = 0;
-    Set<Node> stack = Sets.newIdentityHashSet();
-    Set<Node> marked = Sets.newIdentityHashSet();
-    for (Node node : nodes.values()) {
-      numberOfCycles += traverse(node, stack, marked);
+    private void push(Node node) {
+      stack.push(node);
+      boolean changed = stackSet.add(node);
+      assert changed;
     }
-    return numberOfCycles;
+
+    private void pop(Node node) {
+      Node popped = stack.pop();
+      assert popped == node;
+      boolean changed = stackSet.remove(node);
+      assert changed;
+    }
+
+    private LinkedList<Node> extractCycle(Node entry) {
+      LinkedList<Node> cycle = new LinkedList<>();
+      do {
+        assert !stack.isEmpty();
+        cycle.add(stack.pop());
+      } while (cycle.getLast() != entry);
+      return cycle;
+    }
+
+    private CallEdge findCallEdgeForRemoval(LinkedList<Node> extractedCycle) {
+      Node callee = extractedCycle.getLast();
+      for (Node caller : extractedCycle) {
+        if (!caller.callees.contains(callee)) {
+          // No need to break any edges since this cycle has already been broken previously.
+          assert !callee.callers.contains(caller);
+          return null;
+        }
+        if (edgeRemovalIsSafe(caller, callee)) {
+          return new CallEdge(caller, callee);
+        }
+        callee = caller;
+      }
+      throw new CompilationError(CYCLIC_FORCE_INLINING_MESSAGE);
+    }
+
+    private static boolean edgeRemovalIsSafe(Node caller, Node callee) {
+      // All call edges where the callee is a method that should be force inlined must be kept,
+      // to guarantee that the IR converter will process the callee before the caller.
+      return !callee.method.getOptimizationInfo().forceInline();
+    }
+
+    private void recoverStack(LinkedList<Node> extractedCycle) {
+      Iterator<Node> descendingIt = extractedCycle.descendingIterator();
+      while (descendingIt.hasNext()) {
+        stack.push(descendingIt.next());
+      }
+    }
+
+    private Collection<Node> reorderNodes(List<Node> nodes) {
+      assert options.testing.nondeterministicCycleElimination;
+      if (!InternalOptions.DETERMINISTIC_DEBUGGING) {
+        Collections.shuffle(nodes);
+      }
+      return nodes;
+    }
   }
 
   synchronized private Node ensureMethodNode(DexEncodedMethod method) {
@@ -268,7 +436,6 @@
     assert callee != null;
     if (caller != callee) {
       caller.addCallee(callee);
-      callee.addCaller(caller);
     } else {
       caller.isSelfRecursive = true;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index f276cb2..35ce779 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -76,6 +76,7 @@
   private final Int2ReferenceMap<DebugLocalInfo> emittedLocals = new Int2ReferenceOpenHashMap<>();
   private Int2ReferenceMap<DebugLocalInfo> pendingLocals = null;
   private boolean pendingLocalChanges = false;
+  private BasicBlock pendingFrame = null;
 
   private final List<LocalVariableInfo> localVariablesTable = new ArrayList<>();
   private final Int2ReferenceMap<LocalVariableInfo> openLocalVariables =
@@ -259,7 +260,6 @@
     BasicBlock block = blockIterator.next();
     CfLabel tryCatchStart = null;
     CatchHandlers<BasicBlock> tryCatchHandlers = CatchHandlers.EMPTY_BASIC_BLOCK;
-    BasicBlock pendingFrame = null;
     boolean previousFallthrough = false;
     do {
       assert stack.isEmpty();
@@ -286,17 +286,6 @@
         pendingFrame = block;
         emitLabel(getLabel(block));
       }
-      if (pendingFrame != null) {
-        boolean advancesPC = hasMaterializingInstructions(block, nextBlock);
-        // If block has no materializing instructions, then we postpone emitting the frame
-        // until the next block. In this case, nextBlock must be non-null
-        // (or we would fall off the edge of the method).
-        assert advancesPC || nextBlock != null;
-        if (advancesPC) {
-          addFrame(pendingFrame, Collections.emptyList());
-          pendingFrame = null;
-        }
-      }
       JumpInstruction exit = block.exit();
       boolean fallthrough =
           (exit.isGoto() && exit.asGoto().getTarget() == nextBlock)
@@ -308,7 +297,7 @@
         pendingLocals = new Int2ReferenceOpenHashMap<>(locals);
         pendingLocalChanges = true;
       }
-      buildCfInstructions(block, fallthrough, stack);
+      buildCfInstructions(block, nextBlock, fallthrough, stack);
       block = nextBlock;
       previousFallthrough = fallthrough;
     } while (block != null);
@@ -346,7 +335,19 @@
     return false;
   }
 
-  private void buildCfInstructions(BasicBlock block, boolean fallthrough, Stack stack) {
+  private void buildCfInstructions(
+      BasicBlock block, BasicBlock nextBlock, boolean fallthrough, Stack stack) {
+    if (pendingFrame != null) {
+      boolean advancesPC = hasMaterializingInstructions(block, nextBlock);
+      // If block has no materializing instructions, then we postpone emitting the frame
+      // until the next block. In this case, nextBlock must be non-null
+      // (or we would fall off the edge of the method).
+      assert advancesPC || nextBlock != null;
+      if (advancesPC) {
+        addFrame(pendingFrame, Collections.emptyList());
+        pendingFrame = null;
+      }
+    }
     InstructionIterator it = block.iterator();
     while (it.hasNext()) {
       Instruction instruction = it.next();
@@ -385,36 +386,16 @@
     boolean didPositionChange =
         position.isSome()
             && position != currentPosition
+            // Ignore synthetic positions prior to any actual position, except when inlined
+            // (that is, callerPosition != null).
+            && !(currentPosition.isNone() && position.synthetic && position.callerPosition == null)
             && (options.debug || instruction.instructionTypeCanThrow());
     if (!didLocalsChange && !didPositionChange) {
       return;
     }
     CfLabel label = ensureLabel();
     if (didLocalsChange) {
-      Int2ReferenceSortedMap<DebugLocalInfo> ending =
-          DebugLocalInfo.endingLocals(emittedLocals, pendingLocals);
-      Int2ReferenceSortedMap<DebugLocalInfo> starting =
-          DebugLocalInfo.startingLocals(emittedLocals, pendingLocals);
-      assert !ending.isEmpty() || !starting.isEmpty();
-      for (Entry<DebugLocalInfo> entry : ending.int2ReferenceEntrySet()) {
-        int localIndex = entry.getIntKey();
-        LocalVariableInfo info = openLocalVariables.remove(localIndex);
-        info.setEnd(label);
-        localVariablesTable.add(info);
-        DebugLocalInfo removed = emittedLocals.remove(localIndex);
-        assert removed == entry.getValue();
-      }
-      if (!starting.isEmpty()) {
-        for (Entry<DebugLocalInfo> entry : starting.int2ReferenceEntrySet()) {
-          int localIndex = entry.getIntKey();
-          assert !emittedLocals.containsKey(localIndex);
-          assert !openLocalVariables.containsKey(localIndex);
-          openLocalVariables.put(
-              localIndex, new LocalVariableInfo(localIndex, entry.getValue(), label));
-          emittedLocals.put(localIndex, entry.getValue());
-        }
-      }
-      pendingLocalChanges = false;
+      updateLocals(label);
     }
     if (didPositionChange) {
       add(new CfPosition(label, position));
@@ -422,6 +403,33 @@
     }
   }
 
+  private void updateLocals(CfLabel label) {
+    Int2ReferenceSortedMap<DebugLocalInfo> ending =
+        DebugLocalInfo.endingLocals(emittedLocals, pendingLocals);
+    Int2ReferenceSortedMap<DebugLocalInfo> starting =
+        DebugLocalInfo.startingLocals(emittedLocals, pendingLocals);
+    assert !ending.isEmpty() || !starting.isEmpty();
+    for (Entry<DebugLocalInfo> entry : ending.int2ReferenceEntrySet()) {
+      int localIndex = entry.getIntKey();
+      LocalVariableInfo info = openLocalVariables.remove(localIndex);
+      info.setEnd(label);
+      localVariablesTable.add(info);
+      DebugLocalInfo removed = emittedLocals.remove(localIndex);
+      assert removed == entry.getValue();
+    }
+    if (!starting.isEmpty()) {
+      for (Entry<DebugLocalInfo> entry : starting.int2ReferenceEntrySet()) {
+        int localIndex = entry.getIntKey();
+        assert !emittedLocals.containsKey(localIndex);
+        assert !openLocalVariables.containsKey(localIndex);
+        openLocalVariables.put(
+            localIndex, new LocalVariableInfo(localIndex, entry.getValue(), label));
+        emittedLocals.put(localIndex, entry.getValue());
+      }
+    }
+    pendingLocalChanges = false;
+  }
+
   private boolean localsChanged() {
     if (!pendingLocalChanges) {
       return false;
@@ -458,11 +466,25 @@
 
     Collection<Value> locals = registerAllocator.getLocalsAtBlockEntry(block);
     Int2ReferenceSortedMap<FrameType> mapping = new Int2ReferenceAVLTreeMap<>();
-
     for (Value local : locals) {
       mapping.put(getLocalRegister(local), getFrameType(block, local));
     }
-    instructions.add(new CfFrame(mapping, stackTypes));
+    CfFrame frame = new CfFrame(mapping, stackTypes);
+
+    // Make sure to end locals on this transition before the synthetic CfFrame instruction.
+    // Otherwise we might extend the live range of a local across a CfFrame instruction that
+    // the local is not live across. For example if we have a return followed by a move exception
+    // where the locals end. Inserting a CfFrame instruction between the return and the move
+    // exception without ending the locals will lead to having the local alive on the CfFrame
+    // instruction which is not correct and will cause us to not be able to build IR from the
+    // CfCode.
+    boolean didLocalsChange = localsChanged();
+    if (didLocalsChange) {
+      CfLabel label = ensureLabel();
+      updateLocals(label);
+    }
+
+    instructions.add(frame);
   }
 
   private FrameType getFrameType(BasicBlock liveBlock, Value local) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 92645c4..7892afb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.CanonicalPositions;
 import com.android.tools.r8.ir.code.CatchHandlers;
@@ -182,7 +183,6 @@
   private CfState state;
   private final CfCode code;
   private final DexEncodedMethod method;
-  private final Position callerPosition;
   private final Origin origin;
 
   private final Reference2IntMap<CfLabel> labelOffsets = new Reference2IntOpenHashMap<>();
@@ -200,12 +200,12 @@
   public CfSourceCode(
       CfCode code,
       DexEncodedMethod method,
+      DexMethod originalMethod,
       Position callerPosition,
       Origin origin,
       boolean preserveCaller) {
     this.code = code;
     this.method = method;
-    this.callerPosition = callerPosition;
     this.origin = origin;
     int cfPositionCount = 0;
     for (int i = 0; i < code.getInstructions().size(); i++) {
@@ -219,7 +219,7 @@
     }
     this.state = new CfState(origin);
     canonicalPositions =
-        new CanonicalPositions(callerPosition, preserveCaller, cfPositionCount, this.method.method);
+        new CanonicalPositions(callerPosition, preserveCaller, cfPositionCount, originalMethod);
   }
 
   @Override
@@ -328,7 +328,7 @@
   public void buildPrelude(IRBuilder builder) {
     assert !inPrelude;
     inPrelude = true;
-    state.buildPrelude();
+    state.buildPrelude(canonicalPositions.getPreamblePosition());
     setLocalVariableLists();
     buildArgumentInstructions(builder);
     recordStateForTarget(0, state.getSnapshot());
@@ -395,14 +395,8 @@
       if (instruction instanceof CfPosition) {
         CfPosition cfPosition = (CfPosition) instruction;
         Position position = cfPosition.getPosition();
-        Position newPosition =
-            canonicalPositions.getCanonical(
-                new Position(
-                    position.line,
-                    position.file,
-                    position.method,
-                    canonicalPositions.canonicalizeCallerPosition(position.callerPosition)));
-        CfPosition newCfPosition = new CfPosition(cfPosition.getLabel(), newPosition);
+        CfPosition newCfPosition =
+            new CfPosition(cfPosition.getLabel(), getCanonicalPosition(position));
         newCfPosition.buildIR(builder, state, this);
       } else {
         instruction.buildIR(builder, state, this);
@@ -441,7 +435,8 @@
     for (int i = 0; i < stack.length; i++) {
       stack[i] = convertUninitialized(frame.getStack().get(i));
     }
-    state.setStateFromFrame(locals, stack, getDebugPositionAtOffset(currentInstructionIndex));
+    state.setStateFromFrame(
+        locals, stack, getCanonicalDebugPositionAtOffset(currentInstructionIndex));
   }
 
   private DexType convertUninitialized(FrameType type) {
@@ -624,7 +619,7 @@
   }
 
   @Override
-  public Position getDebugPositionAtOffset(int offset) {
+  public Position getCanonicalDebugPositionAtOffset(int offset) {
     while (offset + 1 < code.getInstructions().size()) {
       CfInstruction insn = code.getInstructions().get(offset);
       if (!(insn instanceof CfLabel) && !(insn instanceof CfFrame)) {
@@ -636,13 +631,22 @@
       offset -= 1;
     }
     if (offset < 0) {
-      return Position.noneWithMethod(method.method, callerPosition);
+      return canonicalPositions.getPreamblePosition();
     }
-    return ((CfPosition) code.getInstructions().get(offset)).getPosition();
+    return getCanonicalPosition(((CfPosition) code.getInstructions().get(offset)).getPosition());
   }
 
   @Override
   public Position getCurrentPosition() {
     return state.getPosition();
   }
+
+  private Position getCanonicalPosition(Position position) {
+    return canonicalPositions.getCanonical(
+        new Position(
+            position.line,
+            position.file,
+            position.method,
+            canonicalPositions.canonicalizeCallerPosition(position.callerPosition)));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfState.java b/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
index 8174797..7f4ee72 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfState.java
@@ -74,8 +74,8 @@
 
   private static final int MAX_UPDATES = 4;
 
-  public void buildPrelude() {
-    current = new BaseSnapshot();
+  public void buildPrelude(Position preamblePosition) {
+    current = new BaseSnapshot(preamblePosition);
   }
 
   public void clear() {
@@ -92,9 +92,9 @@
     current = new BaseSnapshot(locals, stack, position);
   }
 
-  public void merge(Snapshot snapshot) {
+  public void merge(Snapshot snapshot, Position preamblePosition) {
     if (current == null) {
-      current = snapshot == null ? new BaseSnapshot() : snapshot;
+      current = snapshot == null ? new BaseSnapshot(preamblePosition) : snapshot;
     } else {
       current = merge(current, snapshot, origin);
     }
@@ -312,8 +312,8 @@
     final SlotType[] stack;
     final Position position;
 
-    BaseSnapshot() {
-      this(0, 0, Position.none());
+    BaseSnapshot(Position preamblePosition) {
+      this(0, 0, preamblePosition);
     }
 
     BaseSnapshot(int locals, int stack, Position position) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 9a9b836..94516a2 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -75,26 +75,30 @@
 
   private List<DexDebugEntry> debugEntries = null;
   // In case of inlining the position of the invoke in the caller.
-  private final DexMethod method;
+  private final DexMethod originalMethod;
 
   public DexSourceCode(
-      DexCode code, DexEncodedMethod method, Position callerPosition, boolean preserveCaller) {
+      DexCode code,
+      DexEncodedMethod method,
+      DexMethod originalMethod,
+      Position callerPosition,
+      boolean preserveCaller) {
     this.code = code;
     this.proto = method.method.proto;
     this.accessFlags = method.accessFlags;
-    this.method = method.method;
+    this.originalMethod = originalMethod;
 
     argumentTypes = computeArgumentTypes();
     DexDebugInfo info = code.getDebugInfo();
     if (info != null) {
-      debugEntries = info.computeEntries(method.method);
+      debugEntries = info.computeEntries(originalMethod);
     }
     canonicalPositions =
         new CanonicalPositions(
             callerPosition,
             preserveCaller,
             debugEntries == null ? 0 : debugEntries.size(),
-            this.method);
+            originalMethod);
   }
 
   @Override
@@ -184,7 +188,7 @@
   }
 
   @Override
-  public Position getDebugPositionAtOffset(int offset) {
+  public Position getCanonicalDebugPositionAtOffset(int offset) {
     DexDebugEntry entry = getDebugEntryAtOffset(offset);
     return entry == null
         ? canonicalPositions.getPreamblePosition()
@@ -253,7 +257,7 @@
   private Position getCanonicalPositionAppendCaller(DexDebugEntry entry) {
     // If this instruction has already been inlined then this.method must be the outermost caller.
     assert entry.callerPosition == null
-        || entry.callerPosition.getOutermostCaller().method == method;
+        || entry.callerPosition.getOutermostCaller().method == originalMethod;
 
     return canonicalPositions.getCanonical(
         new Position(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 963cf41..e66ddfc 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -87,6 +87,7 @@
 import com.android.tools.r8.utils.Pair;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntArraySet;
@@ -309,6 +310,7 @@
   // Basic blocks. Added after processing from the worklist.
   private final LinkedList<BasicBlock> blocks = new LinkedList<>();
 
+  private BasicBlock entryBlock = null;
   private BasicBlock currentBlock = null;
   final private ValueNumberGenerator valueNumberGenerator;
   private final DexEncodedMethod method;
@@ -324,24 +326,31 @@
   private Value previousLocalValue = null;
   private final List<Value> debugLocalReads = new ArrayList<>();
 
+  // Lazily populated list of local values that are referenced without being actually defined.
+  private Int2ReferenceMap<List<Value>> uninitializedDebugLocalValues = null;
+
   private int nextBlockNumber = 0;
 
   // Flag indicating if the instructions define values with imprecise types.
   private boolean hasImpreciseInstructionOutValueTypes = false;
 
-  public IRBuilder(DexEncodedMethod method, AppInfo appInfo,
-      SourceCode source, InternalOptions options) {
+  public IRBuilder(
+      DexEncodedMethod method, AppInfo appInfo, SourceCode source, InternalOptions options) {
     this(method, appInfo, source, options, new ValueNumberGenerator());
   }
 
   public IRBuilder(
-      DexEncodedMethod method, AppInfo appInfo, SourceCode source,
-      InternalOptions options, ValueNumberGenerator valueNumberGenerator) {
+      DexEncodedMethod method,
+      AppInfo appInfo,
+      SourceCode source,
+      InternalOptions options,
+      ValueNumberGenerator valueNumberGenerator) {
     assert source != null;
     this.method = method;
     this.appInfo = appInfo;
     this.source = source;
-    this.valueNumberGenerator = valueNumberGenerator;
+    this.valueNumberGenerator =
+        valueNumberGenerator != null ? valueNumberGenerator : new ValueNumberGenerator();
     this.options = options;
   }
 
@@ -416,6 +425,7 @@
     processedInstructions = null;
 
     setCurrentBlock(targets.get(INITIAL_BLOCK_OFFSET).block);
+    entryBlock = currentBlock;
     source.buildPrelude(this);
 
     // Process normal blocks reachable from the entry block using a worklist of reachable
@@ -434,6 +444,21 @@
     // Insert debug positions so all position changes are marked by an explicit instruction.
     boolean hasDebugPositions = insertDebugPositions();
 
+    // Insert definitions for all uninitialized local values.
+    if (uninitializedDebugLocalValues != null) {
+      InstructionListIterator it = entryBlock.listIterator();
+      it.nextUntil(i -> !i.isArgument());
+      it.previous();
+      for (List<Value> values : uninitializedDebugLocalValues.values()) {
+        for (Value value : values) {
+          Instruction def = new DebugLocalUninitialized(value);
+          def.setBlock(entryBlock);
+          def.setPosition(Position.none());
+          it.add(def);
+        }
+      }
+    }
+
     // Clear all reaching definitions to free up memory (and avoid invalid use).
     for (BasicBlock block : blocks) {
       block.clearCurrentDefinitions();
@@ -592,7 +617,7 @@
     int moveExceptionDest = source.getMoveExceptionRegister(targetIndex);
     if (moveExceptionDest >= 0) {
       Value out = writeRegister(moveExceptionDest, ValueType.OBJECT, ThrowingInfo.NO_THROW, null);
-      Position position = source.getDebugPositionAtOffset(moveExceptionItem.targetOffset);
+      Position position = source.getCanonicalDebugPositionAtOffset(moveExceptionItem.targetOffset);
       MoveException moveException = new MoveException(out);
       moveException.setPosition(position);
       currentBlock.add(moveException);
@@ -644,15 +669,6 @@
     addInstruction(new Argument(value));
   }
 
-  public void addDebugUninitialized(int register, ValueType type) {
-    if (!options.debug) {
-      return;
-    }
-    Value value = writeRegister(register, type, ThrowingInfo.NO_THROW, null);
-    assert !value.hasLocalInfo();
-    addInstruction(new DebugLocalUninitialized(value));
-  }
-
   private void addDebugLocalWrite(ValueType type, int dest, Value in) {
     assert options.debug;
     Value out = writeRegister(dest, type, ThrowingInfo.NO_THROW);
@@ -666,7 +682,7 @@
     assert local != null;
     assert local == getIncomingLocal(register);
     ValueType valueType = ValueType.fromDexType(local.type);
-    return readRegisterIgnoreLocal(register, valueType);
+    return readRegisterIgnoreLocal(register, valueType, local);
   }
 
   private static boolean isValidFor(Value value, DebugLocalInfo local) {
@@ -693,7 +709,7 @@
     assert local == getOutgoingLocal(register);
     ValueType valueType = ValueType.fromDexType(local.type);
     // TODO(mathiasr): Here we create a Phi with type based on debug info. That's just wrong!
-    Value incomingValue = readRegisterIgnoreLocal(register, valueType);
+    Value incomingValue = readRegisterIgnoreLocal(register, valueType, local);
 
     // TODO(mathiasr): This can be simplified once trivial phi removal is local-info aware.
     if (incomingValue.isPhi() || incomingValue.getLocalInfo() != local) {
@@ -1668,8 +1684,7 @@
     return value;
   }
 
-  private Value readRegisterIgnoreLocal(int register, ValueType type) {
-    DebugLocalInfo local = getIncomingLocal(register);
+  private Value readRegisterIgnoreLocal(int register, ValueType type, DebugLocalInfo local) {
     return readRegister(register, type, currentBlock, EdgeType.NON_EDGE, local);
   }
 
@@ -1703,7 +1718,14 @@
     }
     // If the register still has unknown value create a phi value for it.
     if (value == null) {
-      if (!block.isSealed()) {
+      if (block == entryBlock && local != null) {
+        assert block.getPredecessors().isEmpty();
+        // If a debug-local is referenced at the entry block, lazily introduce an SSA value for it.
+        // This value must *not* be added to the entry blocks current definitions since
+        // uninitialized debug-locals may be reference at the same register/local-index yet be of
+        // different types (eg, int in one part of the CFG and float in a disjoint part).
+        value = getUninitializedDebugLocalValue(register, type, local);
+      } else if (!block.isSealed()) {
         assert !blocks.isEmpty() : "No write to " + register;
         Phi phi = new Phi(valueNumberGenerator.next(), block, type, local);
         block.addIncompletePhi(register, phi, readingEdge);
@@ -1730,6 +1752,29 @@
     return value;
   }
 
+  private Value getUninitializedDebugLocalValue(
+      int register, ValueType type, DebugLocalInfo local) {
+    assert type == ValueType.fromDexType(local.type);
+    if (uninitializedDebugLocalValues == null) {
+      uninitializedDebugLocalValues = new Int2ReferenceOpenHashMap<>();
+    }
+    List<Value> values = uninitializedDebugLocalValues.get(register);
+    if (values != null) {
+      for (Value value : values) {
+        if (value.outType() == type) {
+          return value;
+        }
+      }
+    } else {
+      uninitializedDebugLocalValues.put(register, new ArrayList<>(2));
+    }
+    // Create a new SSA value for the uninitialized local value.
+    // Note that the uninitialized local value *does not* itself have local information!
+    Value value = new Value(valueNumberGenerator.next(), type, null);
+    uninitializedDebugLocalValues.get(register).add(value);
+    return value;
+  }
+
   public Value readNumericRegister(int register, NumericType type) {
     return readRegister(register, ValueType.fromNumericType(type));
   }
@@ -1781,7 +1826,7 @@
     previousLocalValue =
         (incomingLocal == null || incomingLocal != outgoingLocal)
             ? null
-            : readRegisterIgnoreLocal(register, type);
+            : readRegisterIgnoreLocal(register, type, incomingLocal);
     return writeRegister(register, type, throwing, outgoingLocal);
   }
 
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 d5f923e..15627e0 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
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexApplication.Builder;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -41,7 +42,7 @@
 import com.android.tools.r8.ir.optimize.DeadCodeRemover;
 import com.android.tools.r8.ir.optimize.Devirtualizer;
 import com.android.tools.r8.ir.optimize.Inliner;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.MemberValuePropagation;
 import com.android.tools.r8.ir.optimize.NonNullTracker;
 import com.android.tools.r8.ir.optimize.Outliner;
@@ -49,8 +50,10 @@
 import com.android.tools.r8.ir.optimize.RedundantFieldLoadElimination;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInliner;
 import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
+import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
+import com.android.tools.r8.kotlin.KotlinInfo;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.IdentifierNameStringMarker;
 import com.android.tools.r8.shaking.protolite.ProtoLitePruner;
@@ -92,9 +95,10 @@
   private final InterfaceMethodRewriter interfaceMethodRewriter;
   private final LambdaMerger lambdaMerger;
   private final ClassInliner classInliner;
+  private final ClassStaticizer classStaticizer;
   private final InternalOptions options;
   private final CfgPrinter printer;
-  private final GraphLense graphLense;
+  private GraphLense graphLense;
   private final CodeRewriter codeRewriter;
   private final MemberValuePropagation memberValuePropagation;
   private final LensCodeRewriter lensCodeRewriter;
@@ -105,9 +109,10 @@
   private final Devirtualizer devirtualizer;
   private final CovariantReturnTypeAnnotationTransformer covariantReturnTypeAnnotationTransformer;
 
-  private final boolean enableWholeProgramOptimizations;
+  public final boolean enableWholeProgramOptimizations;
 
   private final OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
+  private final OptimizationFeedback simpleOptimizationFeedback = new OptimizationFeedbackSimple();
   private DexString highestSortingString;
 
   private IRConverter(
@@ -141,8 +146,8 @@
     if (enableWholeProgramOptimizations) {
       assert appInfo.hasLiveness();
       this.nonNullTracker = new NonNullTracker();
-      this.inliner = new Inliner(appInfo.withLiveness(), graphLense, options);
-      this.outliner = new Outliner(appInfo.withLiveness(), options);
+      this.inliner = new Inliner(this, options);
+      this.outliner = new Outliner(appInfo.withLiveness(), options, this);
       this.memberValuePropagation =
           options.enableValuePropagation ?
               new MemberValuePropagation(appInfo.withLiveness()) : null;
@@ -176,8 +181,20 @@
     }
     this.classInliner =
         (options.enableClassInlining && options.enableInlining && inliner != null)
-            ? new ClassInliner(appInfo.dexItemFactory, options.classInliningInstructionLimit)
+            ? new ClassInliner(
+            appInfo.dexItemFactory, lambdaRewriter, options.classInliningInstructionLimit)
             : null;
+    this.classStaticizer = options.enableClassStaticizer && appInfo.hasLiveness()
+        ? new ClassStaticizer(appInfo.withLiveness(), this) : null;
+  }
+
+  public void setGraphLense(GraphLense graphLense) {
+    assert graphLense != null;
+    this.graphLense = graphLense;
+  }
+
+  public GraphLense getGraphLense() {
+    return graphLense;
   }
 
   /**
@@ -253,6 +270,18 @@
     }
   }
 
+  private void staticizeClasses(OptimizationFeedback feedback) {
+    if (classStaticizer != null) {
+      classStaticizer.staticizeCandidates(feedback);
+    }
+  }
+
+  private void collectStaticizerCandidates(DexApplication application) {
+    if (classStaticizer != null) {
+      classStaticizer.collectCandidates(application);
+    }
+  }
+
   private void desugarInterfaceMethods(
       Builder<?> builder, InterfaceMethodRewriter.Flavor includeAllResources) {
     if (interfaceMethodRewriter != null) {
@@ -356,24 +385,34 @@
       ExecutorService executor) throws ExecutionException {
     List<Future<?>> futures = new ArrayList<>();
     for (DexProgramClass clazz : classes) {
-      futures.add(
-          executor.submit(
-              () -> {
-                clazz.forEachMethodThrowing(this::convertMethodToDex);
-                return null; // we want a Callable not a Runnable to be able to throw
-              }));
+      futures.add(executor.submit(() -> convertMethodsToDex(clazz)));
     }
     ThreadUtils.awaitFutures(futures);
   }
 
-  void convertMethodToDex(DexEncodedMethod method) {
+  private void convertMethodsToDex(DexProgramClass clazz) {
+    // When converting all methods on a class always convert <clinit> first.
+    for (DexEncodedMethod method : clazz.directMethods()) {
+      if (method.isClassInitializer()) {
+        convertMethodToDex(method);
+        break;
+      }
+    }
+    clazz.forEachMethod(method -> {
+      if (!method.isClassInitializer()) {
+        convertMethodToDex(method);
+      }
+    });
+  }
+
+  private void convertMethodToDex(DexEncodedMethod method) {
     assert options.isGeneratingDex();
     if (method.getCode() != null) {
       boolean matchesMethodFilter = options.methodMatchesFilter(method);
       if (matchesMethodFilter) {
         if (!(options.passthroughDexCode && method.getCode().isDexCode())) {
           // We do not process in call graph order, so anything could be a leaf.
-          rewriteCode(method, ignoreOptimizationFeedback, x -> true, CallSiteInformation.empty(),
+          rewriteCode(method, simpleOptimizationFeedback, x -> true, CallSiteInformation.empty(),
               Outliner::noProcessing);
         }
         updateHighestSortingStrings(method);
@@ -394,6 +433,7 @@
       throws ExecutionException {
     removeLambdaDeserializationMethods();
     collectLambdaMergingCandidates(application);
+    collectStaticizerCandidates(application);
 
     // The process is in two phases.
     // 1) Subject all DexEncodedMethods to optimization (except outlining).
@@ -405,8 +445,8 @@
     OptimizationFeedback directFeedback = new OptimizationFeedbackDirect();
     {
       timing.begin("Build call graph");
-      CallGraph callGraph = CallGraph
-          .build(application, appInfo.withLiveness(), graphLense, options);
+      CallGraph callGraph =
+          CallGraph.build(application, appInfo.withLiveness(), graphLense, options, timing);
       timing.end();
       timing.begin("IR conversion phase 1");
       BiConsumer<IRCode, DexEncodedMethod> outlineHandler =
@@ -424,6 +464,8 @@
     Builder<?> builder = application.builder();
     builder.setHighestSortingString(highestSortingString);
 
+    staticizeClasses(directFeedback);
+
     // Second inlining pass for dealing with double inline callers.
     if (inliner != null) {
       // Use direct feedback still, since methods after inlining may
@@ -481,7 +523,8 @@
           executorService.submit(
               () -> {
                 IRCode code =
-                    method.buildIR(appInfo, options, appInfo.originFor(method.method.holder));
+                    method.buildIR(
+                        appInfo, graphLense, options, appInfo.originFor(method.method.holder));
                 assert code != null;
                 assert !method.getCode().isOutlineCode();
                 // Instead of repeating all the optimizations of rewriteCode(), only run the
@@ -565,7 +608,7 @@
     try {
       codeRewriter.enterCachedClass(clazz);
       // Process the generated class, but don't apply any outlining.
-      clazz.forEachMethodThrowing(this::optimizeSynthesizedMethod);
+      clazz.forEachMethod(this::optimizeSynthesizedMethod);
     } finally {
       codeRewriter.leaveCachedClass(clazz);
     }
@@ -593,7 +636,7 @@
       rewriteCode(method, feedback, isProcessedConcurrently, callSiteInformation, outlineHandler);
     } else {
       // Mark abstract methods as processed as well.
-      method.markProcessed(Constraint.NEVER);
+      method.markProcessed(ConstraintWithTarget.NEVER);
     }
   }
 
@@ -620,12 +663,13 @@
           method.toSourceString(), logCode(options, method));
     }
     if (options.skipIR) {
-      feedback.markProcessed(method, Constraint.NEVER);
+      feedback.markProcessed(method, ConstraintWithTarget.NEVER);
       return;
     }
-    IRCode code = method.buildIR(appInfo, options, appInfo.originFor(method.method.holder));
+    IRCode code =
+        method.buildIR(appInfo, graphLense, options, appInfo.originFor(method.method.holder));
     if (code == null) {
-      feedback.markProcessed(method, Constraint.NEVER);
+      feedback.markProcessed(method, ConstraintWithTarget.NEVER);
       return;
     }
     if (Log.ENABLED) {
@@ -635,9 +679,10 @@
     printC1VisualizerHeader(method);
     printMethod(code, "Initial IR (SSA)");
 
-    if (method.getCode() != null && method.getCode().isJarCode() &&
-        appInfo.definitionFor(method.method.holder).hasKotlinInfo()) {
-      computeKotlinNotNullParamHints(feedback, method, code);
+    DexClass holder = appInfo.definitionFor(method.method.holder);
+    if (method.getCode() != null && method.getCode().isJarCode()
+        && holder.hasKotlinInfo()) {
+      computeKotlinNotNullParamHints(feedback, holder.getKotlinInfo(), method, code);
     }
 
     if (options.canHaveArtStringNewInitBug()) {
@@ -659,6 +704,11 @@
       }
     }
 
+    if (classStaticizer != null) {
+      classStaticizer.fixupMethodCode(method, code);
+      assert code.isConsistentSSA();
+    }
+
     if (identifierNameStringMarker != null) {
       identifierNameStringMarker.decoupleIdentifierNameStringsInMethod(method, code);
       assert code.isConsistentSSA();
@@ -672,7 +722,7 @@
       codeRewriter.removeSwitchMaps(code);
     }
     if (options.disableAssertions) {
-      codeRewriter.disableAssertions(code);
+      codeRewriter.disableAssertions(appInfo, method, code, feedback);
     }
     if (options.enableNonNullTracking && nonNullTracker != null) {
       nonNullTracker.addNonNull(code);
@@ -733,11 +783,6 @@
       assert code.isConsistentSSA();
     }
 
-    if (interfaceMethodRewriter != null) {
-      interfaceMethodRewriter.rewriteMethodReferences(method, code);
-      assert code.isConsistentSSA();
-    }
-
     if (classInliner != null) {
       // Class inliner should work before lambda merger, so if it inlines the
       // lambda, it is not get collected by merger.
@@ -755,6 +800,11 @@
       assert code.isConsistentSSA();
     }
 
+    if (interfaceMethodRewriter != null) {
+      interfaceMethodRewriter.rewriteMethodReferences(method, code);
+      assert code.isConsistentSSA();
+    }
+
     if (lambdaMerger != null) {
       lambdaMerger.processMethodCode(method, code);
       assert code.isConsistentSSA();
@@ -785,6 +835,9 @@
       codeRewriter.identifyTrivialInitializer(method, code, feedback);
     }
     codeRewriter.identifyParameterUsages(method, code, feedback);
+    if (classStaticizer != null) {
+      classStaticizer.examineMethodCode(method, code);
+    }
 
     if (options.canHaveNumberConversionRegisterAllocationBug()) {
       codeRewriter.workaroundNumberConversionRegisterAllocationBug(code);
@@ -795,8 +848,19 @@
   }
 
   private void computeKotlinNotNullParamHints(
-      OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
-    // Try to infer Kotlin non-null parameter check to use it as a hint.
+      OptimizationFeedback feedback, KotlinInfo kotlinInfo, DexEncodedMethod method, IRCode code) {
+    // Use non-null parameter hints in Kotlin metadata if available.
+    if (kotlinInfo.hasNonNullParameterHints()) {
+      BitSet hintFromMetadata = kotlinInfo.lookupNonNullParameterHint(
+          method.method.name.toString(), method.method.proto.toDescriptorString());
+      if (hintFromMetadata != null) {
+        if (hintFromMetadata.length() > 0) {
+          feedback.setKotlinNotNullParamHints(method, hintFromMetadata);
+        }
+        return;
+      }
+    }
+    // Otherwise, fall back to inspecting the code.
     List<Value> arguments = code.collectArguments(true);
     BitSet paramsCheckedForNull = new BitSet();
     DexMethod checkParameterIsNotNull =
@@ -857,9 +921,9 @@
 
   private void markProcessed(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     // After all the optimizations have take place, we compute whether method should be inlinedex.
-    Constraint state;
+    ConstraintWithTarget state;
     if (!options.enableInlining || inliner == null) {
-      state = Constraint.NEVER;
+      state = ConstraintWithTarget.NEVER;
     } else {
       state = inliner.computeInliningConstraint(code, method);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 23d9a73..01c4dd0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -29,7 +29,6 @@
 import com.android.tools.r8.ir.conversion.JarState.Slot;
 import com.android.tools.r8.logging.Log;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import java.io.PrintWriter;
@@ -187,20 +186,20 @@
   // Cooked position to indicate positions in synthesized code (ie, for synchronization).
   private Position syntheticPosition = null;
 
-  private final DexMethod method;
+  private final DexMethod originalMethod;
   private final Position callerPosition;
 
   public JarSourceCode(
       DexType clazz,
       MethodNode node,
       JarApplicationReader application,
-      DexMethod method,
+      DexMethod originalMethod,
       Position callerPosition) {
     assert node != null;
     assert node.desc != null;
     this.node = node;
     this.application = application;
-    this.method = method;
+    this.originalMethod = originalMethod;
     this.clazz = clazz;
     this.callerPosition = callerPosition;
     parameterTypes = Arrays.asList(application.getArgumentTypes(node.desc));
@@ -275,7 +274,6 @@
     // Record types for arguments.
     Int2ReferenceMap<ValueType> argumentLocals = recordArgumentTypes();
     Int2ReferenceMap<ValueType> initializedLocals = new Int2ReferenceOpenHashMap<>(argumentLocals);
-    Int2ReferenceMap<ValueType> uninitializedLocals = new Int2ReferenceOpenHashMap<>();
     // Initialize all non-argument locals to ensure safe insertion of debug-local instructions.
     for (Object o : node.localVariables) {
       LocalVariableNode local = (LocalVariableNode) o;
@@ -320,11 +318,9 @@
       int localRegister = state.getLocalRegister(local.index, localType);
       ValueType existingLocalType = initializedLocals.get(localRegister);
       if (existingLocalType == null) {
-        // For uninitialized entries write the local to ensure it exists in the local state.
         int writeRegister = state.writeLocal(local.index, localType);
         assert writeRegister == localRegister;
         initializedLocals.put(localRegister, localValueType);
-        uninitializedLocals.put(localRegister, localValueType);
       }
     }
 
@@ -335,10 +331,6 @@
     // for arguments.
     buildArgumentInstructions(builder);
 
-    for (Entry<ValueType> entry : uninitializedLocals.int2ReferenceEntrySet()) {
-      builder.addDebugUninitialized(entry.getIntKey(), entry.getValue());
-    }
-
     // Add debug information for all locals at the initial label.
     for (Local local : state.getLocalsToOpen()) {
       if (!argumentLocals.containsKey(local.slot.register)) {
@@ -493,7 +485,7 @@
       // Don't include line changes when processing a label. Doing so will end up emitting local
       // writes after the line has changed and thus causing locals to become visible too late.
       currentPosition =
-          getDebugPositionAtOffset(
+          getCanonicalDebugPositionAtOffset(
               ((instructionIndex > 0) && (insn instanceof LabelNode))
                   ? instructionIndex - 1
                   : instructionIndex);
@@ -2832,7 +2824,7 @@
   }
 
   @Override
-  public Position getDebugPositionAtOffset(int offset) {
+  public Position getCanonicalDebugPositionAtOffset(int offset) {
     if (offset == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
       return getExceptionalExitPosition();
     }
@@ -2862,12 +2854,12 @@
 
   private Position getCanonicalPosition(int line) {
     return canonicalPositions.computeIfAbsent(
-        line, l -> new Position(l, null, method, callerPosition));
+        line, l -> new Position(l, null, originalMethod, callerPosition));
   }
 
   private Position getPreamblePosition() {
     if (preamblePosition == null) {
-      preamblePosition = Position.synthetic(0, method, null);
+      preamblePosition = Position.synthetic(0, originalMethod, null);
     }
     return preamblePosition;
   }
@@ -2891,8 +2883,8 @@
       }
       syntheticPosition =
           (min == Integer.MAX_VALUE)
-              ? Position.noneWithMethod(method, callerPosition)
-              : Position.synthetic(min < max ? min - 1 : min, method, callerPosition);
+              ? Position.noneWithMethod(originalMethod, callerPosition)
+              : Position.synthetic(min < max ? min - 1 : min, originalMethod, callerPosition);
     }
     return syntheticPosition;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
index 99bca1c..b67d12e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
@@ -339,6 +339,12 @@
     }
     int start = source.getOffset(node.start);
     int end = source.getOffset(node.end);
+    // If the locals information is invalid the node start or end could be a label that does
+    // not exist in the program.
+    if (start == -1 || end == -1) {
+      throw new InvalidDebugInfoException(
+          "Locals information for '" + node.name + "' has undefined start or end point.");
+    }
     // Ensure that there is an entry at the starting point of the local.
     {
       LocalsAtOffset atStart;
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 1877053..e9b789a 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
@@ -39,17 +39,17 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 public class LensCodeRewriter {
 
   private final GraphLense graphLense;
   private final AppInfoWithSubtyping appInfo;
 
-  private final Map<DexProto, DexProto> protoFixupCache = new HashMap<>();
+  private final Map<DexProto, DexProto> protoFixupCache = new ConcurrentHashMap<>();
 
   public LensCodeRewriter(GraphLense graphLense, AppInfoWithSubtyping appInfo) {
     this.graphLense = graphLense;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
index c26aecf..0833579 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
 import com.android.tools.r8.graph.ParameterUsagesInfo;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import java.util.BitSet;
 
 public interface OptimizationFeedback {
@@ -16,11 +16,12 @@
   void methodReturnsConstant(DexEncodedMethod method, long value);
   void methodNeverReturnsNull(DexEncodedMethod method);
   void methodNeverReturnsNormally(DexEncodedMethod method);
-  void markProcessed(DexEncodedMethod method, Constraint state);
+  void markProcessed(DexEncodedMethod method, ConstraintWithTarget state);
   void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
   void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
   void setClassInlinerEligibility(DexEncodedMethod method, ClassInlinerEligibility eligibility);
   void setTrivialInitializer(DexEncodedMethod method, TrivialInitializer info);
+  void setInitializerEnablingJavaAssertions(DexEncodedMethod method);
   void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo);
   void setKotlinNotNullParamHints(DexEncodedMethod method, BitSet hints);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
index 2b1a6db..16267f6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
 import com.android.tools.r8.graph.ParameterUsagesInfo;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import java.util.BitSet;
 
 public class OptimizationFeedbackDirect implements OptimizationFeedback {
@@ -34,7 +34,7 @@
   }
 
   @Override
-  public void markProcessed(DexEncodedMethod method, Constraint state) {
+  public void markProcessed(DexEncodedMethod method, ConstraintWithTarget state) {
     method.markProcessed(state);
   }
 
@@ -60,6 +60,11 @@
   }
 
   @Override
+  public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
+    method.setInitializerEnablingJavaAssertions();
+  }
+
+  @Override
   public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
     method.setParameterUsages(parameterUsagesInfo);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
index a547b36..a27f200 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
 import com.android.tools.r8.graph.ParameterUsagesInfo;
 import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import java.util.BitSet;
 
 public class OptimizationFeedbackIgnore implements OptimizationFeedback {
@@ -26,7 +26,7 @@
   public void methodNeverReturnsNormally(DexEncodedMethod method) {}
 
   @Override
-  public void markProcessed(DexEncodedMethod method, Constraint state) {}
+  public void markProcessed(DexEncodedMethod method, ConstraintWithTarget state) {}
 
   @Override
   public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {}
@@ -44,6 +44,10 @@
   }
 
   @Override
+  public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
+  }
+
+  @Override
   public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java
new file mode 100644
index 0000000..2583f8f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackSimple.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.conversion;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexEncodedMethod.ClassInlinerEligibility;
+import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer;
+import com.android.tools.r8.graph.ParameterUsagesInfo;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import java.util.BitSet;
+
+public class OptimizationFeedbackSimple implements OptimizationFeedback {
+
+  @Override
+  public void methodReturnsArgument(DexEncodedMethod method, int argument) {
+    // Ignored.
+  }
+
+  @Override
+  public void methodReturnsConstant(DexEncodedMethod method, long value) {
+    // Ignored.
+  }
+
+  @Override
+  public void methodNeverReturnsNull(DexEncodedMethod method) {
+    // Ignored.
+  }
+
+  @Override
+  public void methodNeverReturnsNormally(DexEncodedMethod method) {
+    // Ignored.
+  }
+
+  @Override
+  public void markProcessed(DexEncodedMethod method, ConstraintWithTarget state) {
+    // Just as processed, don't provide any inlining constraints.
+    method.markProcessed(ConstraintWithTarget.NEVER);
+  }
+
+  @Override
+  public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
+    // Ignored.
+  }
+
+  @Override
+  public void markTriggerClassInitBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
+    // Ignored.
+  }
+
+  @Override
+  public void setClassInlinerEligibility(
+      DexEncodedMethod method, ClassInlinerEligibility eligibility) {
+    // Ignored.
+  }
+
+  @Override
+  public void setTrivialInitializer(DexEncodedMethod method, TrivialInitializer info) {
+    // Ignored.
+  }
+
+  @Override
+  public void setInitializerEnablingJavaAssertions(DexEncodedMethod method) {
+    method.setInitializerEnablingJavaAssertions();
+  }
+
+  @Override
+  public void setParameterUsages(DexEncodedMethod method, ParameterUsagesInfo parameterUsagesInfo) {
+    // Ignored.
+  }
+
+  @Override
+  public void setKotlinNotNullParamHints(DexEncodedMethod method, BitSet hints) {
+    // Ignored.
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
index bf2b51f..2bc2aa1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
@@ -27,7 +27,7 @@
 
   Position getCurrentPosition();
 
-  Position getDebugPositionAtOffset(int offset);
+  Position getCanonicalDebugPositionAtOffset(int offset);
 
   /**
    * Trace block structure of the source-program.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index f4a76e6..c538e86 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -18,11 +19,15 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.origin.SynthesizedOrigin;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Deque;
+import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 // Default and static method interface desugaring processor for interfaces.
 //
@@ -78,7 +83,7 @@
       }
 
       // Remove bridge methods.
-      if (!virtual.accessFlags.isBridge()) {
+      if (interfaceMethodRemovalChangesApi(virtual, iface)) {
         remainingMethods.add(virtual);
       }
     }
@@ -180,6 +185,38 @@
     companionClasses.put(iface, companionClass);
   }
 
+  // Returns true if the given interface method must be kept on [iface] after moving its
+  // implementation to the companion class of [iface]. This is always the case for non-bridge
+  // methods. Bridge methods that does not override an implementation in a super-interface must
+  // also be kept (such a situation can happen if the vertical class merger merges two interfaces).
+  private boolean interfaceMethodRemovalChangesApi(DexEncodedMethod method, DexClass iface) {
+    if (method.accessFlags.isBridge()) {
+      Deque<DexType> worklist = new ArrayDeque<>();
+      Set<DexType> seenBefore = new HashSet<>();
+      if (iface.superType != null) {
+        worklist.add(iface.superType);
+      }
+      Collections.addAll(worklist, iface.interfaces.values);
+      while (!worklist.isEmpty()) {
+        DexType superType = worklist.pop();
+        if (!seenBefore.add(superType)) {
+          continue;
+        }
+        DexClass clazz = rewriter.findDefinitionFor(superType);
+        if (clazz != null) {
+          if (clazz.lookupVirtualMethod(method.method) != null) {
+            return false;
+          }
+          if (clazz.superType != null) {
+            worklist.add(clazz.superType);
+          }
+          Collections.addAll(worklist, clazz.interfaces.values);
+        }
+      }
+    }
+    return true;
+  }
+
   private boolean isStaticMethod(DexEncodedMethod method) {
     if (method.accessFlags.isNative()) {
       throw new Unimplemented("Native interface methods are not yet supported.");
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 81407bd..2fc5e56 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -29,11 +29,13 @@
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.google.common.base.Suppliers;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
 
 /**
  * Represents lambda class generated for a lambda descriptor in context
@@ -64,6 +66,8 @@
   final Target target;
   final AtomicBoolean addToMainDexList = new AtomicBoolean(false);
   private final Collection<DexProgramClass> synthesizedFrom = new ArrayList<DexProgramClass>(1);
+  private final Supplier<DexProgramClass> lazyDexClass =
+      Suppliers.memoize(this::synthesizeLambdaClass); // NOTE: thread-safe.
 
   LambdaClass(LambdaRewriter rewriter, DexType accessedFrom,
       DexType lambdaClassType, LambdaDescriptor descriptor) {
@@ -119,7 +123,11 @@
     return rewriter.factory.createType(lambdaClassDescriptor.toString());
   }
 
-  final DexProgramClass synthesizeLambdaClass() {
+  final DexProgramClass getLambdaClass() {
+    return lazyDexClass.get();
+  }
+
+  private DexProgramClass synthesizeLambdaClass() {
     return new DexProgramClass(
         type,
         null,
@@ -171,7 +179,7 @@
                 Constants.ACC_PUBLIC | Constants.ACC_FINAL, false),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
-            new SynthesizedCode(new LambdaMainMethodSourceCode(this, mainMethod)));
+            new SynthesizedCode(() -> new LambdaMainMethodSourceCode(this, mainMethod)));
 
     // Synthesize bridge methods.
     for (DexProto bridgeProto : descriptor.bridges) {
@@ -188,7 +196,7 @@
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
               new SynthesizedCode(
-                  new LambdaBridgeMethodSourceCode(this, mainMethod, bridgeMethod)));
+                  () -> new LambdaBridgeMethodSourceCode(this, mainMethod, bridgeMethod)));
     }
     return methods;
   }
@@ -208,7 +216,7 @@
                 true),
             DexAnnotationSet.empty(),
             ParameterAnnotationsList.empty(),
-            new SynthesizedCode(new LambdaConstructorSourceCode(this)));
+            new SynthesizedCode(() -> new LambdaConstructorSourceCode(this)));
 
     // Class constructor for stateless lambda classes.
     if (stateless) {
@@ -489,15 +497,21 @@
         if (implMethod.match(encodedMethod)) {
           // We need to create a new static method with the same code to be able to safely
           // relax its accessibility without making it virtual.
-          DexEncodedMethod newMethod = new DexEncodedMethod(
-              callTarget, encodedMethod.accessFlags, encodedMethod.annotations,
-              encodedMethod.parameterAnnotationsList, encodedMethod.getCode());
-          // TODO(ager): Should we give the new first parameter an actual name? Maybe 'this'?
-          encodedMethod.accessFlags.setStatic();
-          encodedMethod.accessFlags.unsetPrivate();
+          MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy();
+          newAccessFlags.setStatic();
+          newAccessFlags.unsetPrivate();
           // Always make the method public to provide access when r8 minification is allowed to move
           // the lambda class accessing this method to another package (-allowaccessmodification).
-          encodedMethod.accessFlags.setPublic();
+          newAccessFlags.setPublic();
+          DexEncodedMethod newMethod =
+              new DexEncodedMethod(
+                  callTarget,
+                  newAccessFlags,
+                  encodedMethod.annotations,
+                  encodedMethod.parameterAnnotationsList,
+                  encodedMethod.getCode());
+          rewriter.methodMapping.put(encodedMethod.method, callTarget);
+          // TODO(ager): Should we give the new first parameter an actual name? Maybe 'this'?
           DexCode dexCode = newMethod.getCode().asDexCode();
           dexCode.setDebugInfo(dexCode.debugInfoWithAdditionalFirstParameter(null));
           assert (dexCode.getDebugInfo() == null)
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index e7686db..cb6856c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -27,6 +27,8 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -62,6 +64,8 @@
   final DexString deserializeLambdaMethodName;
   final DexProto deserializeLambdaMethodProto;
 
+  final BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
+
   // Maps call sites seen so far to inferred lambda descriptor. It is intended
   // to help avoid re-matching call sites we already seen. Note that same call
   // site may match one or several lambda classes.
@@ -141,7 +145,6 @@
           if (method.name == deserializeLambdaMethodName &&
               method.proto == deserializeLambdaMethodProto) {
             assert encoded.accessFlags.isStatic();
-            assert encoded.accessFlags.isPrivate();
             assert encoded.accessFlags.isSynthetic();
 
             DexEncodedMethod[] newMethods = new DexEncodedMethod[methodCount - 1];
@@ -166,14 +169,28 @@
     // referenced symbols to make them accessible. This can result in
     // method access relaxation or creation of accessor method.
     for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
+      // This call may cause methodMapping to be updated.
       lambdaClass.target.ensureAccessibility();
     }
+    if (converter.enableWholeProgramOptimizations && !methodMapping.isEmpty()) {
+      converter.setGraphLense(
+        new LambdaRewriterGraphLense(methodMapping, converter.getGraphLense(), factory));
+    }
+  }
+
+  /**
+   * Returns a synthetic class for desugared lambda or `null` if the `type`
+   * does not represent one. Method can be called concurrently.
+   */
+  public DexProgramClass getLambdaClass(DexType type) {
+    LambdaClass lambdaClass = getKnown(knownLambdaClasses, type);
+    return lambdaClass == null ? null : lambdaClass.getLambdaClass();
   }
 
   /** Generates lambda classes and adds them to the builder. */
   public void synthesizeLambdaClasses(Builder<?> builder) {
     for (LambdaClass lambdaClass : knownLambdaClasses.values()) {
-      DexProgramClass synthesizedClass = lambdaClass.synthesizeLambdaClass();
+      DexProgramClass synthesizedClass = lambdaClass.getLambdaClass();
       converter.optimizeSynthesizedClass(synthesizedClass);
       builder.addSynthesizedClass(synthesizedClass, lambdaClass.addToMainDexList.get());
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
new file mode 100644
index 0000000..26f890e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.desugar;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableMap;
+
+class LambdaRewriterGraphLense extends NestedGraphLense {
+
+  LambdaRewriterGraphLense(
+      BiMap<DexMethod, DexMethod> methodMapping, GraphLense previous, DexItemFactory factory) {
+    super(
+        ImmutableMap.of(),
+        methodMapping,
+        ImmutableMap.of(),
+        ImmutableBiMap.of(),
+        methodMapping.inverse(),
+        previous,
+        factory);
+  }
+
+  @Override
+  protected Type mapInvocationType(
+      DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
+    if (methodMap.get(originalMethod) == newMethod) {
+      assert type == Type.VIRTUAL || type == Type.DIRECT;
+      return Type.STATIC;
+    }
+    return super.mapInvocationType(newMethod, originalMethod, context, type);
+  }
+
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 94e8147..df6e65a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -953,6 +953,14 @@
         continue;
       }
 
+      if (insn.isGoto()) {
+        // Trivial goto to the next block.
+        if (insn.asGoto().isTrivialGotoToTheNextBlock(code)) {
+          continue;
+        }
+        return null;
+      }
+
       // Other instructions make the instance initializer not eligible.
       return null;
     }
@@ -1265,7 +1273,29 @@
    * }
    * </pre>
    */
-  public void disableAssertions(IRCode code) {
+  public void disableAssertions(
+      AppInfo appInfo, DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+    if (method.isClassInitializer()) {
+      if (!hasJavacClinitAssertionCode(code)) {
+        return;
+      }
+      // Mark the clinit as having code to turn on assertions.
+      feedback.setInitializerEnablingJavaAssertions(method);
+    } else {
+      // If the clinit of this class did not have have code to turn on assertions don't try to
+      // remove assertion code from the method.
+      DexClass clazz = appInfo.definitionFor(method.method.holder);
+      if (clazz == null) {
+        return;
+      }
+      DexEncodedMethod clinit = clazz.getClassInitializer();
+      if (clinit == null
+          || !clinit.isProcessed()
+          || !clinit.getOptimizationInfo().isInitializerEnablingJavaAssertions()) {
+        return;
+      }
+    }
+
     InstructionIterator iterator = code.instructionIterator();
     while (iterator.hasNext()) {
       Instruction current = iterator.next();
@@ -1288,6 +1318,78 @@
     }
   }
 
+  private boolean isClassDesiredAssertionStatusInvoke(Instruction instruction) {
+    return instruction.isInvokeMethod()
+    && instruction.asInvokeMethod().getInvokedMethod()
+        == dexItemFactory.classMethods.desiredAssertionStatus;
+  }
+
+  private boolean isAssertionsDisabledFieldPut(Instruction instruction) {
+    return instruction.isStaticPut()
+        && instruction.asStaticPut().getField().name == dexItemFactory.assertionsDisabled;
+  }
+
+  private boolean isNotDebugInstruction(Instruction instruction) {
+    return !instruction.isDebugInstruction();
+  }
+
+  private Value blockWithSingleConstNumberAndGoto(BasicBlock block) {
+    InstructionIterator iterator  = block.iterator();
+    Instruction constNumber = iterator.nextUntil(this::isNotDebugInstruction);
+    if (constNumber == null || !constNumber.isConstNumber()) {
+      return null;
+    }
+    Instruction exit = iterator.nextUntil(this::isNotDebugInstruction);
+    return exit != null && exit.isGoto() ? constNumber.outValue() : null;
+  }
+
+  private Value blockWithAssertionsDisabledFieldPut(BasicBlock block) {
+    InstructionIterator iterator  = block.iterator();
+    Instruction fieldPut = iterator.nextUntil(this::isNotDebugInstruction);
+    return fieldPut != null
+        && isAssertionsDisabledFieldPut(fieldPut) ? fieldPut.inValues().get(0) : null;
+  }
+
+  private boolean hasJavacClinitAssertionCode(IRCode code) {
+    InstructionIterator iterator = code.instructionIterator();
+    Instruction current = iterator.nextUntil(this::isClassDesiredAssertionStatusInvoke);
+    if (current == null) {
+      return false;
+    }
+
+    Value DesiredAssertionStatus = current.outValue();
+    assert iterator.hasNext();
+    current = iterator.next();
+    if (!current.isIf()
+        || !current.asIf().isZeroTest()
+        || current.asIf().inValues().get(0) != DesiredAssertionStatus) {
+      return false;
+    }
+
+    If theIf = current.asIf();
+    BasicBlock trueTarget = theIf.getTrueTarget();
+    BasicBlock falseTarget = theIf.fallthroughBlock();
+    if (trueTarget == falseTarget) {
+      return false;
+    }
+
+    Value trueValue = blockWithSingleConstNumberAndGoto(trueTarget);
+    Value falseValue = blockWithSingleConstNumberAndGoto(falseTarget);
+    if (trueValue == null
+        || falseValue == null
+        || (trueTarget.exit().asGoto().getTarget() != falseTarget.exit().asGoto().getTarget())) {
+      return false;
+    }
+
+    BasicBlock target = trueTarget.exit().asGoto().getTarget();
+    Value storeValue = blockWithAssertionsDisabledFieldPut(target);
+    return storeValue != null
+        && storeValue.isPhi()
+        && storeValue.asPhi().getOperands().size() == 2
+        && storeValue.asPhi().getOperands().contains(trueValue)
+        && storeValue.asPhi().getOperands().contains(falseValue);
+  }
+
   // Check if the static put is a constant derived from the class holding the method.
   // This checks for java.lang.Class.getName and java.lang.Class.getSimpleName.
   private boolean isClassNameConstant(DexEncodedMethod method, StaticPut put) {
@@ -2346,16 +2448,73 @@
           // Zero test with a value range, or comparison between between two values,
           // each with a value ranges.
           if (theIf.isZeroTest()) {
-            if (!inValues.get(0).isValueInRange(0)) {
-              int cond = Long.signum(inValues.get(0).getValueRange().getMin());
-              simplifyIfWithKnownCondition(code, block, theIf, cond);
+            LongInterval interval = inValues.get(0).getValueRange();
+            if (!interval.containsValue(0)) {
+              // Interval doesn't contain zero at all.
+              int sign = Long.signum(interval.getMin());
+              simplifyIfWithKnownCondition(code, block, theIf, sign);
+            } else {
+              // Interval contains zero.
+              switch (theIf.getType()) {
+                case GE:
+                case LT:
+                  // [a, b] >= 0 is always true if a >= 0.
+                  // [a, b] < 0 is always false if a >= 0.
+                  // In both cases a zero condition takes the right branch.
+                  if (interval.getMin() == 0) {
+                    simplifyIfWithKnownCondition(code, block, theIf, 0);
+                  }
+                  break;
+                case LE:
+                case GT:
+                  // [a, b] <= 0 is always true if b <= 0.
+                  // [a, b] > 0 is always false if b <= 0.
+                  if (interval.getMax() == 0) {
+                    simplifyIfWithKnownCondition(code, block, theIf, 0);
+                  }
+                  break;
+                case EQ:
+                case NE:
+                  // Only a single element interval [0, 0] can be dealt with here.
+                  // Such intervals should have been replaced by constants.
+                  assert !interval.isSingleValue();
+                  break;
+              }
             }
           } else {
             LongInterval leftRange = inValues.get(0).getValueRange();
             LongInterval rightRange = inValues.get(1).getValueRange();
+            // Two overlapping ranges. Check for single point overlap.
             if (!leftRange.overlapsWith(rightRange)) {
+              // No overlap.
               int cond = Long.signum(leftRange.getMin() - rightRange.getMin());
               simplifyIfWithKnownCondition(code, block, theIf, cond);
+            } else {
+              // The two intervals overlap. We can simplify if they overlap at the end points.
+              switch (theIf.getType()) {
+                case LT:
+                case GE:
+                  // [a, b] < [c, d] is always false when a == d.
+                  // [a, b] >= [c, d] is always true when a == d.
+                  // In both cases 0 condition will choose the right branch.
+                  if (leftRange.getMin() == rightRange.getMax()) {
+                    simplifyIfWithKnownCondition(code, block, theIf, 0);
+                  }
+                  break;
+                case GT:
+                case LE:
+                  // [a, b] > [c, d] is always false when b == c.
+                  // [a, b] <= [c, d] is always true when b == c.
+                  // In both cases 0 condition will choose the right branch.
+                  if (leftRange.getMax() == rightRange.getMin()) {
+                    simplifyIfWithKnownCondition(code, block, theIf, 0);
+                  }
+                  break;
+                case EQ:
+                case NE:
+                  // Since there is overlap EQ and NE cannot be determined.
+                  break;
+              }
             }
           }
         } else if (theIf.isZeroTest() && !inValues.get(0).isConstNumber()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 631f234..998ebe9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -93,11 +93,11 @@
   private Reason computeInliningReason(DexEncodedMethod target) {
     if (target.getOptimizationInfo().forceInline()
         || (inliner.appInfo.hasLiveness()
-            && inliner.appInfo.withLiveness().forceInline.contains(target))) {
+            && inliner.appInfo.withLiveness().forceInline.contains(target.method))) {
       return Reason.FORCE;
     }
     if (inliner.appInfo.hasLiveness()
-        && inliner.appInfo.withLiveness().alwaysInline.contains(target)) {
+        && inliner.appInfo.withLiveness().alwaysInline.contains(target.method)) {
       return Reason.ALWAYS;
     }
     if (callSiteInformation.hasSingleCallSite(target)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index e6de678..eb3511b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -18,7 +18,7 @@
 import com.android.tools.r8.ir.code.InvokeVirtual;
 import com.android.tools.r8.ir.code.NonNull;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -86,8 +86,9 @@
           continue;
         }
         // Due to the potential downcast below, make sure the new target holder is visible.
-        Constraint visibility = Constraint.classIsVisible(invocationContext, holderType, appInfo);
-        if (visibility == Constraint.NEVER) {
+        ConstraintWithTarget visibility =
+            ConstraintWithTarget.classIsVisible(invocationContext, holderType, appInfo);
+        if (visibility == ConstraintWithTarget.NEVER) {
           continue;
         }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
index 1b68f7d..e23b534 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
@@ -3,10 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
@@ -30,12 +32,14 @@
 public class EnumOrdinalMapCollector {
 
   private final AppInfoWithLiveness appInfo;
+  private final GraphLense graphLense;
   private final InternalOptions options;
 
   private final Map<DexType, Reference2IntMap<DexField>> ordinalsMaps = new IdentityHashMap<>();
 
-  public EnumOrdinalMapCollector(AppInfoWithLiveness appInfo, InternalOptions options) {
-    this.appInfo = appInfo;
+  public EnumOrdinalMapCollector(AppView<AppInfoWithLiveness> appView, InternalOptions options) {
+    this.appInfo = appView.getAppInfo();
+    this.graphLense = appView.getGraphLense();
     this.options = options;
   }
 
@@ -55,7 +59,8 @@
       return;
     }
     DexEncodedMethod initializer = clazz.getClassInitializer();
-    IRCode code = initializer.getCode().buildIR(initializer, appInfo, options, clazz.origin);
+    IRCode code =
+        initializer.getCode().buildIR(initializer, appInfo, graphLense, options, clazz.origin);
     Reference2IntMap<DexField> ordinalsMap = new Reference2IntArrayMap<>();
     ordinalsMap.defaultReturnValue(-1);
     InstructionIterator it = code.instructionIterator();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index d3d1e2f..85f7a39 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -60,7 +60,8 @@
 
   @Override
   public void ensureMethodProcessed(DexEncodedMethod target, IRCode inlinee) {
-    assert target.isProcessed();
+    // Do nothing. If the method is not yet processed, we still should
+    // be able to build IR for inlining, though.
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index db674bd..04cd083 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -43,8 +43,8 @@
 public class Inliner {
   private static final int INITIAL_INLINING_INSTRUCTION_ALLOWANCE = 1500;
 
+  private final IRConverter converter;
   protected final AppInfoWithLiveness appInfo;
-  private final GraphLense graphLense;
   final InternalOptions options;
 
   // State for inlining methods which are known to be called twice.
@@ -55,12 +55,9 @@
 
   private final Set<DexMethod> blackList = Sets.newIdentityHashSet();
 
-  public Inliner(
-      AppInfoWithLiveness appInfo,
-      GraphLense graphLense,
-      InternalOptions options) {
-    this.appInfo = appInfo;
-    this.graphLense = graphLense;
+  public Inliner(IRConverter converter, InternalOptions options) {
+    this.converter = converter;
+    this.appInfo = converter.appInfo.withLiveness();
     this.options = options;
     fillInBlackList(appInfo);
   }
@@ -71,30 +68,33 @@
   }
 
   public boolean isBlackListed(DexEncodedMethod method) {
-    return blackList.contains(method.method) || appInfo.neverInline.contains(method);
+    return blackList.contains(method.method) || appInfo.neverInline.contains(method.method);
   }
 
-  private Constraint instructionAllowedForInlining(
+  private ConstraintWithTarget instructionAllowedForInlining(
       Instruction instruction, InliningConstraints inliningConstraints, DexType invocationContext) {
-    Constraint result = instruction.inliningConstraint(inliningConstraints, invocationContext);
-    if (result == Constraint.NEVER && instruction.isDebugInstruction()) {
-      return Constraint.ALWAYS;
+    ConstraintWithTarget result =
+        instruction.inliningConstraint(inliningConstraints, invocationContext);
+    if (result == ConstraintWithTarget.NEVER && instruction.isDebugInstruction()) {
+      return ConstraintWithTarget.ALWAYS;
     }
     return result;
   }
 
-  public Constraint computeInliningConstraint(IRCode code, DexEncodedMethod method) {
-    Constraint result = Constraint.ALWAYS;
+  public ConstraintWithTarget computeInliningConstraint(IRCode code, DexEncodedMethod method) {
+    ConstraintWithTarget result = ConstraintWithTarget.ALWAYS;
     InliningConstraints inliningConstraints = new InliningConstraints(appInfo);
     InstructionIterator it = code.instructionIterator();
     while (it.hasNext()) {
       Instruction instruction = it.next();
-      Constraint state =
+      ConstraintWithTarget state =
           instructionAllowedForInlining(instruction, inliningConstraints, method.method.holder);
-      result = Constraint.min(result, state);
-      if (result == Constraint.NEVER) {
+      if (state == ConstraintWithTarget.NEVER) {
+        result = state;
         break;
       }
+      // TODO(b/111080693): we may need to collect all meaningful constraints.
+      result = ConstraintWithTarget.meet(result, state, appInfo);
     }
     return result;
   }
@@ -176,11 +176,17 @@
    */
   public enum Constraint {
     // The ordinal values are important so please do not reorder.
-    NEVER,     // Never inline this.
-    SAMECLASS, // Only inline this into methods with same holder.
-    PACKAGE,   // Only inline this into methods with holders from same package.
-    SUBCLASS,  // Only inline this into methods with holders from a subclass.
-    ALWAYS;    // No restrictions for inlining this.
+    NEVER(1),     // Never inline this.
+    SAMECLASS(2), // Inlineable into methods with same holder.
+    PACKAGE(4),   // Inlineable into methods with holders from the same package.
+    SUBCLASS(8),  // Inlineable into methods with holders from a subclass in a different package.
+    ALWAYS(16);   // No restrictions for inlining this.
+
+    int value;
+
+    Constraint(int value) {
+      this.value = value;
+    }
 
     static {
       assert NEVER.ordinal() < SAMECLASS.ordinal();
@@ -189,7 +195,67 @@
       assert SUBCLASS.ordinal() < ALWAYS.ordinal();
     }
 
-    public static Constraint deriveConstraint(
+    boolean isSet(int value) {
+      return (this.value & value) != 0;
+    }
+  }
+
+  /**
+   * Encodes the constraints for inlining, along with the target holder.
+   * <p>
+   * Constraint itself cannot determine whether or not the method can be inlined if instructions in
+   * the method have different constraints with different targets. For example,
+   *   SUBCLASS of x.A v.s. PACKAGE of y.B
+   * Without any target holder information, min of those two Constraints is PACKAGE, meaning that
+   * the current method can be inlined to any method whose holder is in package y. This could cause
+   * an illegal access error due to protect members in x.A. Because of different target holders,
+   * those constraints should not be combined.
+   * <p>
+   * Instead, a right constraint for inlining constraint for the example above is: a method whose
+   * holder is a subclass of x.A _and_ in the same package of y.B can inline this method.
+   */
+  public static class ConstraintWithTarget {
+    public final Constraint constraint;
+    // Note that this is not context---where this constraint is encoded.
+    // It literally refers to the holder type of the target, which could be:
+    // invoked method in invocations, field in field instructions, type of check-cast, etc.
+    final DexType targetHolder;
+
+    public static final ConstraintWithTarget NEVER = new ConstraintWithTarget(Constraint.NEVER);
+    public static final ConstraintWithTarget ALWAYS = new ConstraintWithTarget(Constraint.ALWAYS);
+
+    private ConstraintWithTarget(Constraint constraint) {
+      assert constraint == Constraint.NEVER || constraint == Constraint.ALWAYS;
+      this.constraint = constraint;
+      this.targetHolder = null;
+    }
+
+    ConstraintWithTarget(Constraint constraint, DexType targetHolder) {
+      assert constraint != Constraint.NEVER && constraint != Constraint.ALWAYS;
+      assert targetHolder != null;
+      this.constraint = constraint;
+      this.targetHolder = targetHolder;
+    }
+
+    @Override
+    public int hashCode() {
+      if (targetHolder == null) {
+        return constraint.ordinal();
+      }
+      return constraint.ordinal() * targetHolder.computeHashCode();
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (!(other instanceof ConstraintWithTarget)) {
+        return false;
+      }
+      ConstraintWithTarget o = (ConstraintWithTarget) other;
+      return this.constraint.ordinal() == o.constraint.ordinal()
+          && this.targetHolder == o.targetHolder;
+    }
+
+    public static ConstraintWithTarget deriveConstraint(
         DexType contextHolder,
         DexType targetHolder,
         AccessFlags flags,
@@ -197,23 +263,25 @@
       if (flags.isPublic()) {
         return ALWAYS;
       } else if (flags.isPrivate()) {
-        return targetHolder == contextHolder ? SAMECLASS : NEVER;
+        return targetHolder == contextHolder
+            ? new ConstraintWithTarget(Constraint.SAMECLASS, targetHolder) : NEVER;
       } else if (flags.isProtected()) {
         if (targetHolder.isSamePackage(contextHolder)) {
           // Even though protected, this is visible via the same package from the context.
-          return PACKAGE;
+          return new ConstraintWithTarget(Constraint.PACKAGE, targetHolder);
         } else if (contextHolder.isSubtypeOf(targetHolder, appInfo)) {
-          return SUBCLASS;
+          return new ConstraintWithTarget(Constraint.SUBCLASS, targetHolder);
         }
         return NEVER;
       } else {
         /* package-private */
-        return targetHolder.isSamePackage(contextHolder) ? PACKAGE : NEVER;
+        return targetHolder.isSamePackage(contextHolder)
+            ? new ConstraintWithTarget(Constraint.PACKAGE, targetHolder) : NEVER;
       }
     }
 
-    public static Constraint classIsVisible(DexType context, DexType clazz,
-        AppInfoWithSubtyping appInfo) {
+    public static ConstraintWithTarget classIsVisible(
+        DexType context, DexType clazz, AppInfoWithSubtyping appInfo) {
       if (clazz.isArrayType()) {
         return classIsVisible(context, clazz.toArrayElementType(appInfo.dexItemFactory), appInfo);
       }
@@ -227,8 +295,76 @@
           : deriveConstraint(context, clazz, definition.accessFlags, appInfo);
     }
 
-    public static Constraint min(Constraint one, Constraint other) {
-      return one.ordinal() < other.ordinal() ? one : other;
+    public static ConstraintWithTarget meet(
+        ConstraintWithTarget one, ConstraintWithTarget other, AppInfoWithSubtyping appInfo) {
+      if (one.equals(other)) {
+        return one;
+      }
+      if (other.constraint.ordinal() < one.constraint.ordinal()) {
+        return meet(other, one, appInfo);
+      }
+      // From now on, one.constraint.ordinal() <= other.constraint.ordinal()
+      if (one == NEVER) {
+        return NEVER;
+      }
+      if (other == ALWAYS) {
+        return one;
+      }
+      int constraint = one.constraint.value | other.constraint.value;
+      assert !Constraint.NEVER.isSet(constraint);
+      assert !Constraint.ALWAYS.isSet(constraint);
+      // SAMECLASS <= SAMECLASS, PACKAGE, SUBCLASS
+      if (Constraint.SAMECLASS.isSet(constraint)) {
+        assert one.constraint == Constraint.SAMECLASS;
+        if (other.constraint == Constraint.SAMECLASS) {
+          assert one.targetHolder != other.targetHolder;
+          return NEVER;
+        }
+        if (other.constraint == Constraint.PACKAGE) {
+          if (one.targetHolder.isSamePackage(other.targetHolder)) {
+            return one;
+          }
+          return NEVER;
+        }
+        assert other.constraint == Constraint.SUBCLASS;
+        if (one.targetHolder.isSubtypeOf(other.targetHolder, appInfo)) {
+          return one;
+        }
+        return NEVER;
+      }
+      // PACKAGE <= PACKAGE, SUBCLASS
+      if (Constraint.PACKAGE.isSet(constraint)) {
+        assert one.constraint == Constraint.PACKAGE;
+        if (other.constraint == Constraint.PACKAGE) {
+          assert one.targetHolder != other.targetHolder;
+          if (one.targetHolder.isSamePackage(other.targetHolder)) {
+            return one;
+          }
+          // PACKAGE of x and PACKAGE of y cannot be satisfied together.
+          return NEVER;
+        }
+        assert other.constraint == Constraint.SUBCLASS;
+        if (other.targetHolder.isSamePackage(one.targetHolder)) {
+          // Then, PACKAGE is more restrictive constraint.
+          return one;
+        }
+        // TODO(b/111080693): towards finer-grained constraints, we need both.
+        // The target method is still inlineable to methods with a holder from the same package of
+        // one's holder and a subtype of other's holder.
+        return NEVER;
+      }
+      // SUBCLASS <= SUBCLASS
+      assert Constraint.SUBCLASS.isSet(constraint);
+      assert one.constraint == other.constraint;
+      assert one.targetHolder != other.targetHolder;
+      if (one.targetHolder.isSubtypeOf(other.targetHolder, appInfo)) {
+        return one;
+      }
+      if (other.targetHolder.isSubtypeOf(one.targetHolder, appInfo)) {
+        return other;
+      }
+      // SUBCLASS of x and SUBCLASS of y while x and y are not a subtype of each other.
+      return NEVER;
     }
   }
 
@@ -270,7 +406,8 @@
         Position callerPosition) {
       // Build the IR for a yet not processed method, and perform minimal IR processing.
       Origin origin = appInfo.originFor(target.method.holder);
-      IRCode code = target.buildInliningIR(appInfo, options, generator, callerPosition, origin);
+      IRCode code =
+          target.buildInliningIR(appInfo, graphLense, options, generator, callerPosition, origin);
       if (!target.isProcessed()) {
         new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
       }
@@ -447,11 +584,12 @@
               invokePosition = Position.noneWithMethod(method.method, null);
             }
             assert invokePosition.callerPosition == null
-                || invokePosition.getOutermostCaller().method == method.method;
+                || invokePosition.getOutermostCaller().method
+                    == converter.getGraphLense().getOriginalMethodSignature(method.method);
 
             IRCode inlinee =
-                result.buildInliningIR(
-                    code.valueNumberGenerator, appInfo, graphLense, options, invokePosition);
+                result.buildInliningIR(code.valueNumberGenerator,
+                    appInfo, converter.getGraphLense(), options, invokePosition);
             if (inlinee != null) {
               // TODO(64432527): Get rid of this additional check by improved inlining.
               if (block.hasCatchHandlers() && inlinee.computeNormalExitBlocks().isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 073d6ae..129e9cb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.Collection;
 
@@ -45,71 +46,71 @@
     this.graphLense = graphLense;
   }
 
-  public Constraint forAlwaysMaterializingUser() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forAlwaysMaterializingUser() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint forArgument() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forArgument() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint forArrayGet() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forArrayGet() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint forArrayLength() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forArrayLength() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint forArrayPut() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forArrayPut() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint forBinop() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forBinop() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint forCheckCast(DexType type, DexType invocationContext) {
-    return Constraint.classIsVisible(invocationContext, type, appInfo);
+  public ConstraintWithTarget forCheckCast(DexType type, DexType invocationContext) {
+    return ConstraintWithTarget.classIsVisible(invocationContext, type, appInfo);
   }
 
-  public Constraint forConstClass(DexType type, DexType invocationContext) {
-    return Constraint.classIsVisible(invocationContext, type, appInfo);
+  public ConstraintWithTarget forConstClass(DexType type, DexType invocationContext) {
+    return ConstraintWithTarget.classIsVisible(invocationContext, type, appInfo);
   }
 
-  public Constraint forConstInstruction() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forConstInstruction() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint forDebugLocalRead() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forDebugLocalRead() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint forDebugLocalsChange() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forDebugLocalsChange() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint forDebugPosition() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forDebugPosition() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint forInstanceGet(DexField field, DexType invocationContext) {
+  public ConstraintWithTarget forInstanceGet(DexField field, DexType invocationContext) {
     DexField lookup = graphLense.lookupField(field);
     return forFieldInstruction(
         lookup, appInfo.lookupInstanceTarget(lookup.clazz, lookup), invocationContext);
   }
 
-  public Constraint forInstanceOf(DexType type, DexType invocationContext) {
-    return Constraint.classIsVisible(invocationContext, type, appInfo);
+  public ConstraintWithTarget forInstanceOf(DexType type, DexType invocationContext) {
+    return ConstraintWithTarget.classIsVisible(invocationContext, type, appInfo);
   }
 
-  public Constraint forInstancePut(DexField field, DexType invocationContext) {
+  public ConstraintWithTarget forInstancePut(DexField field, DexType invocationContext) {
     DexField lookup = graphLense.lookupField(field);
     return forFieldInstruction(
         lookup, appInfo.lookupInstanceTarget(lookup.clazz, lookup), invocationContext);
   }
 
-  public Constraint forInvoke(DexMethod method, Type type, DexType invocationContext) {
+  public ConstraintWithTarget forInvoke(DexMethod method, Type type, DexType invocationContext) {
     switch (type) {
       case DIRECT:
         return forInvokeDirect(method, invocationContext);
@@ -130,162 +131,165 @@
     }
   }
 
-  public Constraint forInvokeCustom() {
-    return Constraint.NEVER;
+  public ConstraintWithTarget forInvokeCustom() {
+    return ConstraintWithTarget.NEVER;
   }
 
-  public Constraint forInvokeDirect(DexMethod method, DexType invocationContext) {
+  public ConstraintWithTarget forInvokeDirect(DexMethod method, DexType invocationContext) {
     DexMethod lookup = graphLense.lookupMethod(method);
     return forSingleTargetInvoke(lookup, appInfo.lookupDirectTarget(lookup), invocationContext);
   }
 
-  public Constraint forInvokeInterface(DexMethod method, DexType invocationContext) {
+  public ConstraintWithTarget forInvokeInterface(DexMethod method, DexType invocationContext) {
     DexMethod lookup = graphLense.lookupMethod(method);
     return forVirtualInvoke(lookup, appInfo.lookupInterfaceTargets(lookup), invocationContext);
   }
 
-  public Constraint forInvokeMultiNewArray(DexType type, DexType invocationContext) {
-    return Constraint.classIsVisible(invocationContext, type, appInfo);
+  public ConstraintWithTarget forInvokeMultiNewArray(DexType type, DexType invocationContext) {
+    return ConstraintWithTarget.classIsVisible(invocationContext, type, appInfo);
   }
 
-  public Constraint forInvokeNewArray(DexType type, DexType invocationContext) {
-    return Constraint.classIsVisible(invocationContext, type, appInfo);
+  public ConstraintWithTarget forInvokeNewArray(DexType type, DexType invocationContext) {
+    return ConstraintWithTarget.classIsVisible(invocationContext, type, appInfo);
   }
 
-  public Constraint forInvokePolymorphic(DexMethod method, DexType invocationContext) {
-    return Constraint.NEVER;
+  public ConstraintWithTarget forInvokePolymorphic(DexMethod method, DexType invocationContext) {
+    return ConstraintWithTarget.NEVER;
   }
 
-  public Constraint forInvokeStatic(DexMethod method, DexType invocationContext) {
+  public ConstraintWithTarget forInvokeStatic(DexMethod method, DexType invocationContext) {
     DexMethod lookup = graphLense.lookupMethod(method);
     return forSingleTargetInvoke(lookup, appInfo.lookupStaticTarget(lookup), invocationContext);
   }
 
-  public Constraint forInvokeSuper(DexMethod method, DexType invocationContext) {
+  public ConstraintWithTarget forInvokeSuper(DexMethod method, DexType invocationContext) {
     // The semantics of invoke super depend on the context.
-    return Constraint.SAMECLASS;
+    return new ConstraintWithTarget(Constraint.SAMECLASS, invocationContext);
   }
 
-  public Constraint forInvokeVirtual(DexMethod method, DexType invocationContext) {
+  public ConstraintWithTarget forInvokeVirtual(DexMethod method, DexType invocationContext) {
     DexMethod lookup = graphLense.lookupMethod(method);
     return forVirtualInvoke(lookup, appInfo.lookupVirtualTargets(lookup), invocationContext);
   }
 
-  public Constraint forJumpInstruction() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forJumpInstruction() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint forLoad() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forLoad() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint forMonitor() {
+  public ConstraintWithTarget forMonitor() {
     // Conservative choice.
-    return Constraint.NEVER;
+    return ConstraintWithTarget.NEVER;
   }
 
-  public Constraint forMove() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forMove() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint forMoveException() {
+  public ConstraintWithTarget forMoveException() {
     // TODO(64432527): Revisit this constraint.
-    return Constraint.NEVER;
+    return ConstraintWithTarget.NEVER;
   }
 
-  public Constraint forNewArrayEmpty(DexType type, DexType invocationContext) {
-    return Constraint.classIsVisible(invocationContext, type, appInfo);
+  public ConstraintWithTarget forNewArrayEmpty(DexType type, DexType invocationContext) {
+    return ConstraintWithTarget.classIsVisible(invocationContext, type, appInfo);
   }
 
-  public Constraint forNewArrayFilledData() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forNewArrayFilledData() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint forNewInstance(DexType type, DexType invocationContext) {
-    return Constraint.classIsVisible(invocationContext, type, appInfo);
+  public ConstraintWithTarget forNewInstance(DexType type, DexType invocationContext) {
+    return ConstraintWithTarget.classIsVisible(invocationContext, type, appInfo);
   }
 
-  public Constraint forNonNull() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forNonNull() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint forPop() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forPop() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint forReturn() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forReturn() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint forStaticGet(DexField field, DexType invocationContext) {
+  public ConstraintWithTarget forStaticGet(DexField field, DexType invocationContext) {
     DexField lookup = graphLense.lookupField(field);
     return forFieldInstruction(
         lookup, appInfo.lookupStaticTarget(lookup.clazz, lookup), invocationContext);
   }
 
-  public Constraint forStaticPut(DexField field, DexType invocationContext) {
+  public ConstraintWithTarget forStaticPut(DexField field, DexType invocationContext) {
     DexField lookup = graphLense.lookupField(field);
     return forFieldInstruction(
         lookup, appInfo.lookupStaticTarget(lookup.clazz, lookup), invocationContext);
   }
 
-  public Constraint forStore() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forStore() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint forThrow() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forThrow() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint forUnop() {
-    return Constraint.ALWAYS;
+  public ConstraintWithTarget forUnop() {
+    return ConstraintWithTarget.ALWAYS;
   }
 
-  private Constraint forFieldInstruction(
+  private ConstraintWithTarget forFieldInstruction(
       DexField field, DexEncodedField target, DexType invocationContext) {
     // Resolve the field if possible and decide whether the instruction can inlined.
     DexType fieldHolder = graphLense.lookupType(field.clazz);
     DexClass fieldClass = appInfo.definitionFor(fieldHolder);
     if (target != null && fieldClass != null) {
-      Constraint fieldConstraint =
-          Constraint.deriveConstraint(invocationContext, fieldHolder, target.accessFlags, appInfo);
-      Constraint classConstraint =
-          Constraint.deriveConstraint(
+      ConstraintWithTarget fieldConstraintWithTarget =
+          ConstraintWithTarget.deriveConstraint(
+              invocationContext, fieldHolder, target.accessFlags, appInfo);
+      ConstraintWithTarget classConstraintWithTarget =
+          ConstraintWithTarget.deriveConstraint(
               invocationContext, fieldHolder, fieldClass.accessFlags, appInfo);
-      return Constraint.min(fieldConstraint, classConstraint);
+      return ConstraintWithTarget.meet(
+          fieldConstraintWithTarget, classConstraintWithTarget, appInfo);
     }
-    return Constraint.NEVER;
+    return ConstraintWithTarget.NEVER;
   }
 
-  private Constraint forSingleTargetInvoke(
+  private ConstraintWithTarget forSingleTargetInvoke(
       DexMethod method, DexEncodedMethod target, DexType invocationContext) {
     if (method.holder.isArrayType()) {
-      return Constraint.ALWAYS;
+      return ConstraintWithTarget.ALWAYS;
     }
     if (target != null) {
       DexType methodHolder = graphLense.lookupType(target.method.holder);
       DexClass methodClass = appInfo.definitionFor(methodHolder);
       if (methodClass != null) {
-        Constraint methodConstraint =
-            Constraint.deriveConstraint(
+        ConstraintWithTarget methodConstraintWithTarget =
+            ConstraintWithTarget.deriveConstraint(
                 invocationContext, methodHolder, target.accessFlags, appInfo);
         // We also have to take the constraint of the enclosing class into account.
-        Constraint classConstraint =
-            Constraint.deriveConstraint(
+        ConstraintWithTarget classConstraintWithTarget =
+            ConstraintWithTarget.deriveConstraint(
                 invocationContext, methodHolder, methodClass.accessFlags, appInfo);
-        return Constraint.min(methodConstraint, classConstraint);
+        return ConstraintWithTarget.meet(
+            methodConstraintWithTarget, classConstraintWithTarget, appInfo);
       }
     }
-    return Constraint.NEVER;
+    return ConstraintWithTarget.NEVER;
   }
 
-  private Constraint forVirtualInvoke(
+  private ConstraintWithTarget forVirtualInvoke(
       DexMethod method, Collection<DexEncodedMethod> targets, DexType invocationContext) {
     if (method.holder.isArrayType()) {
-      return Constraint.ALWAYS;
+      return ConstraintWithTarget.ALWAYS;
     }
     if (targets == null) {
-      return Constraint.NEVER;
+      return ConstraintWithTarget.NEVER;
     }
 
     // Perform resolution and derive inlining constraints based on the accessibility of the
@@ -294,25 +298,26 @@
     DexEncodedMethod resolutionTarget = resolutionResult.asResultOfResolve();
     if (resolutionTarget == null) {
       // This will fail at runtime.
-      return Constraint.NEVER;
+      return ConstraintWithTarget.NEVER;
     }
 
     DexType methodHolder = graphLense.lookupType(resolutionTarget.method.holder);
     DexClass methodClass = appInfo.definitionFor(methodHolder);
     assert methodClass != null;
-    Constraint methodConstraint =
-        Constraint.deriveConstraint(
+    ConstraintWithTarget methodConstraintWithTarget =
+        ConstraintWithTarget.deriveConstraint(
             invocationContext, methodHolder, resolutionTarget.accessFlags, appInfo);
     // We also have to take the constraint of the enclosing class of the resolution result
     // into account. We do not allow inlining this method if it is calling something that
     // is inaccessible. Inlining in that case could move the code to another package making a
     // call succeed that should not succeed. Conversely, if the resolution result is accessible,
     // we have to make sure that inlining cannot make it inaccessible.
-    Constraint classConstraint =
-        Constraint.deriveConstraint(
+    ConstraintWithTarget classConstraintWithTarget =
+        ConstraintWithTarget.deriveConstraint(
             invocationContext, methodHolder, methodClass.accessFlags, appInfo);
-    Constraint result = Constraint.min(methodConstraint, classConstraint);
-    if (result == Constraint.NEVER) {
+    ConstraintWithTarget result =
+        ConstraintWithTarget.meet(methodConstraintWithTarget, classConstraintWithTarget, appInfo);
+    if (result == ConstraintWithTarget.NEVER) {
       return result;
     }
 
@@ -321,10 +326,11 @@
     for (DexEncodedMethod target : targets) {
       methodHolder = graphLense.lookupType(target.method.holder);
       assert appInfo.definitionFor(methodHolder) != null;
-      methodConstraint =
-          Constraint.deriveConstraint(invocationContext, methodHolder, target.accessFlags, appInfo);
-      result = Constraint.min(result, methodConstraint);
-      if (result == Constraint.NEVER) {
+      methodConstraintWithTarget =
+          ConstraintWithTarget.deriveConstraint(
+              invocationContext, methodHolder, target.accessFlags, appInfo);
+      result = ConstraintWithTarget.meet(result, methodConstraintWithTarget, appInfo);
+      if (result == ConstraintWithTarget.NEVER) {
         return result;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
new file mode 100644
index 0000000..bd3acb6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
@@ -0,0 +1,223 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.function.Predicate;
+
+// Per-class collection of method signatures.
+//
+// Example use case: to determine if a certain method can be publicized or not.
+public class MethodPoolCollection {
+
+  private static final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
+
+  private final DexApplication application;
+  private final Map<DexClass, MethodPool> methodPools = new ConcurrentHashMap<>();
+
+  public MethodPoolCollection(DexApplication application) {
+    this.application = application;
+  }
+
+  public void buildAll(ExecutorService executorService, Timing timing) throws ExecutionException {
+    timing.begin("Building method pool collection");
+    try {
+      List<Future<?>> futures = new ArrayList<>();
+      @SuppressWarnings("unchecked")
+      List<DexClass> classes = (List) application.classes();
+      submitAll(classes, futures, executorService);
+      ThreadUtils.awaitFutures(futures);
+    } finally {
+      timing.end();
+    }
+  }
+
+  public MethodPool buildForHierarchy(
+      DexClass clazz, ExecutorService executorService, Timing timing) throws ExecutionException {
+    timing.begin("Building method pool collection");
+    try {
+      List<Future<?>> futures = new ArrayList<>();
+      submitAll(
+          getAllSuperTypesInclusive(clazz, methodPools::containsKey), futures, executorService);
+      submitAll(getAllSubTypesExclusive(clazz, methodPools::containsKey), futures, executorService);
+      ThreadUtils.awaitFutures(futures);
+    } finally {
+      timing.end();
+    }
+    return get(clazz);
+  }
+
+  public MethodPool get(DexClass clazz) {
+    assert methodPools.containsKey(clazz);
+    return methodPools.get(clazz);
+  }
+
+  public boolean markIfNotSeen(DexClass clazz, DexMethod method) {
+    MethodPool methodPool = get(clazz);
+    Wrapper<DexMethod> key = equivalence.wrap(method);
+    if (methodPool.hasSeen(key)) {
+      return true;
+    }
+    methodPool.seen(key);
+    return false;
+  }
+
+  private void submitAll(
+      Iterable<DexClass> classes, List<Future<?>> futures, ExecutorService executorService) {
+    for (DexClass clazz : classes) {
+      futures.add(executorService.submit(computeMethodPoolPerClass(clazz)));
+    }
+  }
+
+  private Runnable computeMethodPoolPerClass(DexClass clazz) {
+    return () -> {
+      MethodPool methodPool = methodPools.computeIfAbsent(clazz, k -> new MethodPool());
+      clazz.forEachMethod(
+          encodedMethod -> {
+            // We will add private instance methods when we promote them.
+            if (!encodedMethod.isPrivateMethod() || encodedMethod.isStaticMethod()) {
+              methodPool.seen(equivalence.wrap(encodedMethod.method));
+            }
+          });
+      if (clazz.superType != null) {
+        DexClass superClazz = application.definitionFor(clazz.superType);
+        if (superClazz != null) {
+          MethodPool superPool = methodPools.computeIfAbsent(superClazz, k -> new MethodPool());
+          superPool.linkSubtype(methodPool);
+          methodPool.linkSupertype(superPool);
+        }
+      }
+      if (clazz.isInterface()) {
+        clazz.type.forAllImplementsSubtypes(
+            implementer -> {
+              DexClass subClazz = application.definitionFor(implementer);
+              if (subClazz != null) {
+                MethodPool childPool = methodPools.computeIfAbsent(subClazz, k -> new MethodPool());
+                childPool.linkInterface(methodPool);
+              }
+            });
+      }
+    };
+  }
+
+  private Set<DexClass> getAllSuperTypesInclusive(
+      DexClass subject, Predicate<DexClass> stoppingCriterion) {
+    Set<DexClass> superTypes = new HashSet<>();
+    Deque<DexClass> worklist = new ArrayDeque<>();
+    worklist.add(subject);
+    while (!worklist.isEmpty()) {
+      DexClass clazz = worklist.pop();
+      if (stoppingCriterion.test(clazz)) {
+        continue;
+      }
+      if (superTypes.add(clazz)) {
+        if (clazz.superType != null) {
+          addNonNull(worklist, application.definitionFor(clazz.superType));
+        }
+        for (DexType interfaceType : clazz.interfaces.values) {
+          addNonNull(worklist, application.definitionFor(interfaceType));
+        }
+      }
+    }
+    return superTypes;
+  }
+
+  private Set<DexClass> getAllSubTypesExclusive(
+      DexClass subject, Predicate<DexClass> stoppingCriterion) {
+    Set<DexClass> subTypes = new HashSet<>();
+    Deque<DexClass> worklist = new ArrayDeque<>();
+    subject.type.forAllExtendsSubtypes(
+        type -> addNonNull(worklist, application.definitionFor(type)));
+    subject.type.forAllImplementsSubtypes(
+        type -> addNonNull(worklist, application.definitionFor(type)));
+    while (!worklist.isEmpty()) {
+      DexClass clazz = worklist.pop();
+      if (stoppingCriterion.test(clazz)) {
+        continue;
+      }
+      if (subTypes.add(clazz)) {
+        clazz.type.forAllExtendsSubtypes(
+            type -> addNonNull(worklist, application.definitionFor(type)));
+        clazz.type.forAllImplementsSubtypes(
+            type -> addNonNull(worklist, application.definitionFor(type)));
+      }
+    }
+    return subTypes;
+  }
+
+  public static class MethodPool {
+    private MethodPool superType;
+    private final Set<MethodPool> interfaces = new HashSet<>();
+    private final Set<MethodPool> subTypes = new HashSet<>();
+    private final Set<Wrapper<DexMethod>> methodPool = new HashSet<>();
+
+    private MethodPool() {}
+
+    synchronized void linkSupertype(MethodPool superType) {
+      assert this.superType == null;
+      this.superType = superType;
+    }
+
+    synchronized void linkSubtype(MethodPool subType) {
+      boolean added = subTypes.add(subType);
+      assert added;
+    }
+
+    synchronized void linkInterface(MethodPool itf) {
+      boolean added = interfaces.add(itf);
+      assert added;
+    }
+
+    public void seen(DexMethod method) {
+      seen(MethodSignatureEquivalence.get().wrap(method));
+    }
+
+    public synchronized void seen(Wrapper<DexMethod> method) {
+      boolean added = methodPool.add(method);
+      assert added;
+    }
+
+    public boolean hasSeen(Wrapper<DexMethod> method) {
+      return hasSeenUpwardRecursive(method) || hasSeenDownwardRecursive(method);
+    }
+
+    private boolean hasSeenUpwardRecursive(Wrapper<DexMethod> method) {
+      return methodPool.contains(method)
+          || (superType != null && superType.hasSeenUpwardRecursive(method))
+          || interfaces.stream().anyMatch(itf -> itf.hasSeenUpwardRecursive(method));
+    }
+
+    private boolean hasSeenDownwardRecursive(Wrapper<DexMethod> method) {
+      return methodPool.contains(method)
+          || subTypes.stream().anyMatch(subType -> subType.hasSeenDownwardRecursive(method));
+    }
+  }
+
+  private static <T> void addNonNull(Collection<T> collection, T item) {
+    if (item != null) {
+      collection.add(item);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index c3a3781..5a7cba6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -20,6 +20,7 @@
 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.GraphLense;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.UseRegistry;
@@ -43,8 +44,9 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.SourceCode;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.SynthesizedOrigin;
@@ -107,6 +109,7 @@
 
   final private AppInfoWithLiveness appInfo;
   final private DexItemFactory dexItemFactory;
+  final private IRConverter converter;
   private final InliningConstraints inliningConstraints;
 
   // Representation of an outline.
@@ -476,8 +479,9 @@
 
       // See whether we could move this invoke somewhere else. We reuse the logic from inlining
       // here, as the constraints are the same.
-      Constraint constraint = invoke.inliningConstraint(inliningConstraints, method.method.holder);
-      if (constraint != Constraint.ALWAYS) {
+      ConstraintWithTarget constraint =
+          invoke.inliningConstraint(inliningConstraints, method.method.holder);
+      if (constraint != ConstraintWithTarget.ALWAYS) {
         return false;
       }
       // Find the number of in-going arguments, if adding this instruction.
@@ -816,9 +820,10 @@
     }
   }
 
-  public Outliner(AppInfoWithLiveness appInfo, InternalOptions options) {
+  public Outliner(AppInfoWithLiveness appInfo, InternalOptions options, IRConverter converter) {
     this.appInfo = appInfo;
     this.dexItemFactory = appInfo.dexItemFactory;
+    this.converter = converter;
     this.inliningConstraints = new InliningConstraints(appInfo);
     this.options = options;
   }
@@ -850,7 +855,10 @@
     assert outlineSites.size() == 0;
     for (List<DexEncodedMethod> outlineMethods : candidateMethodLists) {
       if (outlineMethods.size() >= options.outline.threshold) {
-        methodsSelectedForOutlining.addAll(outlineMethods);
+        for (DexEncodedMethod outlineMethod : outlineMethods) {
+          methodsSelectedForOutlining.add(
+              converter.getGraphLense().mapDexEncodedMethod(appInfo, outlineMethod));
+        }
       }
     }
     candidateMethodLists.clear();
@@ -1106,7 +1114,7 @@
     }
 
     @Override
-    public Position getDebugPositionAtOffset(int offset) {
+    public Position getCanonicalDebugPositionAtOffset(int offset) {
       throw new Unreachable();
     }
 
@@ -1163,10 +1171,14 @@
 
     @Override
     public IRCode buildIR(
-        DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
+        DexEncodedMethod encodedMethod,
+        AppInfo appInfo,
+        GraphLense graphLense,
+        InternalOptions options,
+        Origin origin) {
+      assert getOwner() == encodedMethod;
       OutlineSourceCode source = new OutlineSourceCode(outline);
-      IRBuilder builder = new IRBuilder(encodedMethod, appInfo, source, options);
-      return builder.build();
+      return new IRBuilder(encodedMethod, appInfo, source, options).build();
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index dee935e..9b0ab04 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -112,6 +112,11 @@
             assert !couldBeVolatile(field);
             if (instruction.isInstanceGet() && !instruction.outValue().hasLocalInfo()) {
               Value object = instruction.asInstanceGet().object();
+              // Values from NonNull instructions will always be replaced with their original
+              // value before code is generated.
+              if (!object.isPhi() && object.definition.isNonNull()) {
+                object = object.definition.asNonNull().src();
+              }
               FieldAndObject fieldAndObject = new FieldAndObject(field, object);
               if (activeInstanceFields.containsKey(fieldAndObject)) {
                 Instruction active = activeInstanceFields.get(fieldAndObject);
@@ -132,7 +137,8 @@
             }
           }
         }
-        if (instruction.isMonitor() || instruction.isInvokeMethod()) {
+        if ((instruction.isMonitor() && instruction.asMonitor().isEnter())
+            || instruction.isInvokeMethod()) {
           killAllActiveFields();
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
index a586355..c2fbc9e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
@@ -3,12 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionIterator;
@@ -61,14 +63,16 @@
 public class SwitchMapCollector {
 
   private final AppInfoWithLiveness appInfo;
+  private final GraphLense graphLense;
   private final InternalOptions options;
   private final DexString switchMapPrefix;
   private final DexType intArrayType;
 
   private final Map<DexField, Int2ReferenceMap<DexField>> switchMaps = new IdentityHashMap<>();
 
-  public SwitchMapCollector(AppInfoWithLiveness appInfo, InternalOptions options) {
-    this.appInfo = appInfo;
+  public SwitchMapCollector(AppView<AppInfoWithLiveness> appView, InternalOptions options) {
+    this.appInfo = appView.getAppInfo();
+    this.graphLense = appView.getGraphLense();
     this.options = options;
     switchMapPrefix = appInfo.dexItemFactory.createString("$SwitchMap$");
     intArrayType = appInfo.dexItemFactory.createType("[I");
@@ -92,7 +96,8 @@
     List<DexEncodedField> switchMapFields = Arrays.stream(clazz.staticFields())
         .filter(this::maybeIsSwitchMap).collect(Collectors.toList());
     if (!switchMapFields.isEmpty()) {
-      IRCode initializer = clazz.getClassInitializer().buildIR(appInfo, options, clazz.origin);
+      IRCode initializer =
+          clazz.getClassInitializer().buildIR(appInfo, graphLense, options, clazz.origin);
       switchMapFields.forEach(field -> extractSwitchMap(field, initializer));
     }
   }
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 5bf2c74..3a2371d 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
@@ -8,10 +8,10 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
 import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
 import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -27,15 +27,18 @@
 
 public final class ClassInliner {
   private final DexItemFactory factory;
+  private final LambdaRewriter lambdaRewriter;
   private final int totalMethodInstructionLimit;
-  private final ConcurrentHashMap<DexType, Boolean> knownClasses = new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexClass, Boolean> knownClasses = new ConcurrentHashMap<>();
 
   public interface InlinerAction {
     void inline(Map<InvokeMethod, InliningInfo> methods);
   }
 
-  public ClassInliner(DexItemFactory factory, int totalMethodInstructionLimit) {
+  public ClassInliner(DexItemFactory factory,
+      LambdaRewriter lambdaRewriter, int totalMethodInstructionLimit) {
     this.factory = factory;
+    this.lambdaRewriter = lambdaRewriter;
     this.totalMethodInstructionLimit = totalMethodInstructionLimit;
   }
 
@@ -142,8 +145,8 @@
       while (rootsIterator.hasNext()) {
         Instruction root = rootsIterator.next();
         InlineCandidateProcessor processor =
-            new InlineCandidateProcessor(factory, appInfo,
-                type -> isClassEligible(appInfo, type),
+            new InlineCandidateProcessor(factory, appInfo, lambdaRewriter,
+                clazz -> isClassEligible(appInfo, clazz),
                 isProcessedConcurrently, method, root);
 
         // Assess eligibility of instance and class.
@@ -180,7 +183,7 @@
     } while (repeat);
   }
 
-  private boolean isClassEligible(AppInfo appInfo, DexType clazz) {
+  private boolean isClassEligible(AppInfo appInfo, DexClass clazz) {
     Boolean eligible = knownClasses.get(clazz);
     if (eligible == null) {
       Boolean computed = computeClassEligible(appInfo, clazz);
@@ -195,15 +198,14 @@
   //   - is not an abstract class or interface
   //   - does not declare finalizer
   //   - does not trigger any static initializers except for its own
-  private boolean computeClassEligible(AppInfo appInfo, DexType clazz) {
-    DexClass definition = appInfo.definitionFor(clazz);
-    if (definition == null || definition.isLibraryClass() ||
-        definition.accessFlags.isAbstract() || definition.accessFlags.isInterface()) {
+  private boolean computeClassEligible(AppInfo appInfo, DexClass clazz) {
+    if (clazz == null || clazz.isLibraryClass() ||
+        clazz.accessFlags.isAbstract() || clazz.accessFlags.isInterface()) {
       return false;
     }
 
     // Class must not define finalizer.
-    for (DexEncodedMethod method : definition.virtualMethods()) {
+    for (DexEncodedMethod method : clazz.virtualMethods()) {
       if (method.method.name == factory.finalizeMethodName &&
           method.method.proto == factory.objectMethods.finalize.proto) {
         return false;
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 57ce42f..5fedf37 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
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.desugar.LambdaRewriter;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.InliningInfo;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -53,7 +54,8 @@
 
   private final DexItemFactory factory;
   private final AppInfoWithLiveness appInfo;
-  private final Predicate<DexType> isClassEligible;
+  private final LambdaRewriter lambdaRewriter;
+  private final Predicate<DexClass> isClassEligible;
   private final Predicate<DexEncodedMethod> isProcessedConcurrently;
   private final DexEncodedMethod method;
   private final Instruction root;
@@ -61,6 +63,7 @@
   private Value eligibleInstance;
   private DexType eligibleClass;
   private DexClass eligibleClassDefinition;
+  private boolean isDesugaredLambda;
 
   private final Map<InvokeMethod, InliningInfo> methodCallsOnInstance
       = new IdentityHashMap<>();
@@ -73,10 +76,11 @@
 
   InlineCandidateProcessor(
       DexItemFactory factory, AppInfoWithLiveness appInfo,
-      Predicate<DexType> isClassEligible,
+      LambdaRewriter lambdaRewriter, Predicate<DexClass> isClassEligible,
       Predicate<DexEncodedMethod> isProcessedConcurrently,
       DexEncodedMethod method, Instruction root) {
     this.factory = factory;
+    this.lambdaRewriter = lambdaRewriter;
     this.isClassEligible = isClassEligible;
     this.method = method;
     this.root = root;
@@ -99,6 +103,11 @@
     eligibleClass = isNewInstance() ?
         root.asNewInstance().clazz : root.asStaticGet().getField().type;
     eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
+    if (eligibleClassDefinition == null && lambdaRewriter != null) {
+      // Check if the class is synthesized for a desugared lambda
+      eligibleClassDefinition = lambdaRewriter.getLambdaClass(eligibleClass);
+      isDesugaredLambda = eligibleClassDefinition != null;
+    }
     return eligibleClassDefinition != null;
   }
 
@@ -114,7 +123,7 @@
   //      * class has class initializer marked as TrivialClassInitializer, and
   //        class initializer initializes the field we are reading here.
   boolean isClassAndUsageEligible() {
-    if (!isClassEligible.test(eligibleClass)) {
+    if (!isClassEligible.test(eligibleClassDefinition)) {
       return false;
     }
 
@@ -129,6 +138,11 @@
 
     assert root.isStaticGet();
 
+    // We know that desugared lambda classes satisfy eligibility requirements.
+    if (isDesugaredLambda) {
+      return true;
+    }
+
     // Checking if we can safely inline class implemented following singleton-like
     // pattern, by which we assume a static final field holding on to the reference
     // initialized in class constructor.
@@ -444,7 +458,7 @@
         : "Inlined constructor? [invoke: " + initInvoke +
         ", expected class: " + eligibleClass + "]";
 
-    DexEncodedMethod definition = appInfo.definitionFor(init);
+    DexEncodedMethod definition = findSingleTarget(init, true);
     if (definition == null || isProcessedConcurrently.test(definition)) {
       return null;
     }
@@ -455,6 +469,12 @@
       return null;
     }
 
+    if (isDesugaredLambda) {
+      // Lambda desugaring synthesizes eligible constructors.
+      markSizeForInlining(definition);
+      return new InliningInfo(definition, eligibleClass);
+    }
+
     // If the superclass of the initializer is NOT java.lang.Object, the super class
     // initializer being called must be classified as TrivialInstanceInitializer.
     //
@@ -499,7 +519,7 @@
   private InliningInfo isEligibleMethodCall(boolean allowMethodsWithoutNormalReturns,
       DexMethod callee, Predicate<ClassInlinerEligibility> eligibilityAcceptanceCheck) {
 
-    DexEncodedMethod singleTarget = findSingleTarget(callee);
+    DexEncodedMethod singleTarget = findSingleTarget(callee, false);
     if (singleTarget == null || isProcessedConcurrently.test(singleTarget)) {
       return null;
     }
@@ -507,6 +527,16 @@
       return null; // Don't inline itself.
     }
 
+    if (isDesugaredLambda) {
+      // If this is the call to method of the desugared lambda, we consider only calls
+      // to main lambda method eligible (for both direct and indirect calls).
+      if (singleTarget.accessFlags.isBridge()) {
+        return null;
+      }
+      markSizeForInlining(singleTarget);
+      return new InliningInfo(singleTarget, eligibleClass);
+    }
+
     OptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
 
     ClassInlinerEligibility eligibility = optimizationInfo.getClassInlinerEligibility();
@@ -652,6 +682,9 @@
 
   private boolean exemptFromInstructionLimit(DexEncodedMethod inlinee) {
     DexType inlineeHolder = inlinee.method.holder;
+    if (isDesugaredLambda && inlineeHolder == eligibleClass) {
+      return true;
+    }
     if (appInfo.isPinned(inlineeHolder)) {
       return false;
     }
@@ -674,14 +707,16 @@
     return root.isNewInstance();
   }
 
-  private DexEncodedMethod findSingleTarget(DexMethod callee) {
+  private DexEncodedMethod findSingleTarget(DexMethod callee, boolean isDirect) {
     // We don't use computeSingleTarget(...) on invoke since it sometimes fails to
     // find the single target, while this code may be more successful since we exactly
     // know what is the actual type of the receiver.
 
     // Note that we also intentionally limit ourselves to methods directly defined in
     // the instance's class. This may be improved later.
-    return eligibleClassDefinition.lookupVirtualMethod(callee);
+    return isDirect
+        ? eligibleClassDefinition.lookupDirectMethod(callee)
+        : eligibleClassDefinition.lookupVirtualMethod(callee);
   }
 
   private void removeInstruction(Instruction instruction) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
new file mode 100644
index 0000000..9b5af03
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -0,0 +1,628 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer;
+
+import com.android.tools.r8.graph.DexApplication;
+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;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.UseRegistry;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.OptimizationFeedback;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
+
+public final class ClassStaticizer {
+  final AppInfoWithLiveness appInfo;
+  final DexItemFactory factory;
+  final IRConverter converter;
+
+  private enum Phase {
+    None, Examine, Fixup
+  }
+
+  private Phase phase = Phase.None;
+  private BiConsumer<DexEncodedMethod, IRCode> fixupStrategy = null;
+
+  // Represents a staticizing candidate with all information
+  // needed for staticizing.
+  final class CandidateInfo {
+    final DexProgramClass candidate;
+    final DexEncodedField singletonField;
+    final AtomicBoolean preserveRead = new AtomicBoolean(false);
+    // Number of singleton field writes.
+    final AtomicInteger fieldWrites = new AtomicInteger();
+    // Number of instances created.
+    final AtomicInteger instancesCreated = new AtomicInteger();
+    final Set<DexEncodedMethod> referencedFrom = Sets.newConcurrentHashSet();
+    final AtomicReference<DexEncodedMethod> constructor = new AtomicReference<>();
+
+    CandidateInfo(DexProgramClass candidate, DexEncodedField singletonField) {
+      assert candidate != null;
+      assert singletonField != null;
+      this.candidate = candidate;
+      this.singletonField = singletonField;
+
+      // register itself
+      candidates.put(candidate.type, this);
+    }
+
+    boolean isHostClassInitializer(DexEncodedMethod method) {
+      return factory.isClassConstructor(method.method) && method.method.holder == hostType();
+    }
+
+    DexType hostType() {
+      return singletonField.field.clazz;
+    }
+
+    DexClass hostClass() {
+      DexClass hostClass = appInfo.definitionFor(hostType());
+      assert hostClass != null;
+      return hostClass;
+    }
+
+    CandidateInfo invalidate() {
+      candidates.remove(candidate.type);
+      return null;
+    }
+  }
+
+  // The map storing all the potential candidates for staticizing.
+  final ConcurrentHashMap<DexType, CandidateInfo> candidates = new ConcurrentHashMap<>();
+
+  public ClassStaticizer(AppInfoWithLiveness appInfo, IRConverter converter) {
+    this.appInfo = appInfo;
+    this.factory = appInfo.dexItemFactory;
+    this.converter = converter;
+  }
+
+  // Before doing any usage-based analysis we collect a set of classes that can be
+  // candidates for staticizing. This analysis is very simple, but minimizes the
+  // set of eligible classes staticizer tracks and thus time and memory it needs.
+  public final void collectCandidates(DexApplication app) {
+    Set<DexType> notEligible = Sets.newIdentityHashSet();
+    Map<DexType, DexEncodedField> singletonFields = new HashMap<>();
+
+    app.classes().forEach(cls -> {
+      // We only consider classes eligible for staticizing if there is just
+      // one single static field in the whole app which has a type of this
+      // class. This field will be considered to be a candidate for a singleton
+      // field. The requirements for the initialization of this field will be
+      // checked later.
+      for (DexEncodedField field : cls.staticFields()) {
+        DexType type = field.field.type;
+        if (singletonFields.put(type, field) != null) {
+          // There is already candidate singleton field found.
+          notEligible.add(type);
+        }
+      }
+
+      // Don't allow fields with this candidate types.
+      for (DexEncodedField field : cls.instanceFields()) {
+        notEligible.add(field.field.type);
+      }
+
+      // Let's also assume no methods should take or return a
+      // value of this type.
+      for (DexEncodedMethod method : cls.methods()) {
+        DexProto proto = method.method.proto;
+        notEligible.add(proto.returnType);
+        notEligible.addAll(Arrays.asList(proto.parameters.values));
+      }
+
+      // High-level limitations on what classes we consider eligible.
+      if (cls.isInterface() || // Must not be an interface or an abstract class.
+          cls.accessFlags.isAbstract() ||
+          // Don't support candidates with instance fields
+          cls.instanceFields().length > 0 ||
+          // Only support classes directly extending java.lang.Object
+          cls.superType != factory.objectType ||
+          // Instead of requiring the class being final,
+          // just ensure it does not have subtypes
+          cls.type.hasSubtypes() ||
+          // Staticizing classes implementing interfaces is more
+          // difficult, so don't support it until we really need it.
+          !cls.interfaces.isEmpty()) {
+        notEligible.add(cls.type);
+      }
+    });
+
+    // Finalize the set of the candidates.
+    app.classes().forEach(cls -> {
+      DexType type = cls.type;
+      if (!notEligible.contains(type)) {
+        DexEncodedField field = singletonFields.get(type);
+        if (field != null && // Singleton field found
+            !field.accessFlags.isVolatile() && // Don't remove volatile fields.
+            !isPinned(cls, field)) { // Don't remove pinned objects.
+          assert field.accessFlags.isStatic();
+          // Note: we don't check that the field is final, since we will analyze
+          //       later how and where it is initialized.
+          new CandidateInfo(cls, field); // will self-register
+        }
+      }
+    });
+
+    // Next phase -- examine code for candidate usages
+    phase = Phase.Examine;
+  }
+
+  private boolean isPinned(DexClass clazz, DexEncodedField singletonField) {
+    if (appInfo.isPinned(clazz.type) || appInfo.isPinned(singletonField.field)) {
+      return true;
+    }
+    for (DexEncodedMethod method : clazz.methods()) {
+      if (!method.isStaticMethod() && appInfo.isPinned(method.method)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  // Check staticizing candidates' usages to ensure the candidate can be staticized.
+  //
+  // The criteria for type CANDIDATE to be eligible for staticizing fall into
+  // these categories:
+  //
+  //  * checking that there is only one instance of the class created, and it is created
+  //    inside the host class initializer, and it is guaranteed that nobody can access this
+  //    field before it is assigned.
+  //
+  //  * no other singleton field writes (except for those used to store the only candidate
+  //    class instance described above) are allowed.
+  //
+  //  * values read from singleton field should only be used for instance method calls.
+  //
+  // NOTE: there are more criteria eligible class needs to satisfy to be actually staticized,
+  // those will be checked later in staticizeCandidates().
+  //
+  // This method also collects all DexEncodedMethod instances that need to be rewritten if
+  // appropriate candidate is staticized. Essentially anything that references instance method
+  // or field defined in the class.
+  //
+  // NOTE: can be called concurrently.
+  public final void examineMethodCode(DexEncodedMethod method, IRCode code) {
+    if (phase != Phase.Examine) {
+      return;
+    }
+
+    Set<Instruction> alreadyProcessed = Sets.newIdentityHashSet();
+
+    CandidateInfo receiverClassCandidateInfo = candidates.get(method.method.holder);
+    Value receiverValue = code.getThis(); // NOTE: is null for static methods.
+    if (receiverClassCandidateInfo != null && receiverValue != null) {
+      // We are inside an instance method of candidate class (not an instance initializer
+      // which we will check later), check if all the references to 'this' are valid
+      // (the call will invalidate the candidate if some of them are not valid).
+      analyzeAllValueUsers(receiverClassCandidateInfo,
+          receiverValue, factory.isConstructor(method.method));
+
+      // If the candidate is still valid, ignore all instructions
+      // we treat as valid usages on receiver.
+      if (candidates.get(method.method.holder) != null) {
+        alreadyProcessed.addAll(receiverValue.uniqueUsers());
+      }
+    }
+
+    ListIterator<Instruction> iterator =
+        Lists.newArrayList(code.instructionIterator()).listIterator();
+    while (iterator.hasNext()) {
+      Instruction instruction = iterator.next();
+      if (alreadyProcessed.contains(instruction)) {
+        continue;
+      }
+
+      if (instruction.isNewInstance()) {
+        // Check the class being initialized against valid staticizing candidates.
+        NewInstance newInstance = instruction.asNewInstance();
+        CandidateInfo candidateInfo = processInstantiation(method, iterator, newInstance);
+        if (candidateInfo != null) {
+          // For host class initializers having eligible instantiation we also want to
+          // ensure that the rest of the initializer consist of code w/o side effects.
+          // This must guarantee that removing field access will not result in missing side
+          // effects, otherwise we can still staticize, but cannot remove singleton reads.
+          while (iterator.hasNext()) {
+            if (!isAllowedInHostClassInitializer(method.method.holder, iterator.next(), code)) {
+              candidateInfo.preserveRead.set(true);
+              iterator.previous();
+              break;
+            }
+            // Ignore just read instruction.
+          }
+          candidateInfo.referencedFrom.add(method);
+        }
+        continue;
+      }
+
+      if (instruction.isStaticPut()) {
+        // Check the field being written to: no writes to singleton fields are allowed
+        // except for those processed in processInstantiation(...).
+        DexType candidateType = instruction.asStaticPut().getField().type;
+        CandidateInfo candidateInfo = candidates.get(candidateType);
+        if (candidateInfo != null) {
+          candidateInfo.invalidate();
+        }
+        continue;
+      }
+
+      if (instruction.isStaticGet()) {
+        // Check the field being read: make sure all usages are valid.
+        CandidateInfo info = processStaticFieldRead(instruction.asStaticGet());
+        if (info != null) {
+          info.referencedFrom.add(method);
+          // If the candidate still valid, ignore all usages in further analysis.
+          Value value = instruction.outValue();
+          if (value != null) {
+            alreadyProcessed.addAll(value.uniqueUsers());
+          }
+        }
+        continue;
+      }
+
+      if (instruction.isInvokeMethodWithReceiver()) {
+        DexMethod invokedMethod = instruction.asInvokeMethodWithReceiver().getInvokedMethod();
+        CandidateInfo candidateInfo = candidates.get(invokedMethod.holder);
+        if (candidateInfo != null) {
+          // A call to instance method of the candidate class we don't know how to deal with.
+          candidateInfo.invalidate();
+        }
+        continue;
+      }
+
+      if (instruction.isInvokeCustom()) {
+        // Just invalidate any candidates referenced from non-static context.
+        CallSiteReferencesInvalidator invalidator = new CallSiteReferencesInvalidator();
+        invalidator.registerCallSite(instruction.asInvokeCustom().getCallSite());
+        continue;
+      }
+
+      if (instruction.isInstanceGet() || instruction.isInstancePut()) {
+        DexField fieldReferenced = instruction.asFieldInstruction().getField();
+        CandidateInfo candidateInfo = candidates.get(fieldReferenced.clazz);
+        if (candidateInfo != null) {
+          // Reads/writes to instance field of the candidate class are not supported.
+          candidateInfo.invalidate();
+        }
+        continue;
+      }
+    }
+  }
+
+  private boolean isAllowedInHostClassInitializer(
+      DexType host, Instruction insn, IRCode code) {
+    return (insn.isStaticPut() && insn.asStaticPut().getField().clazz == host) ||
+        insn.isConstNumber() ||
+        insn.isConstString() ||
+        (insn.isGoto() && insn.asGoto().isTrivialGotoToTheNextBlock(code)) ||
+        insn.isReturn();
+  }
+
+  private CandidateInfo processInstantiation(
+      DexEncodedMethod method, ListIterator<Instruction> iterator, NewInstance newInstance) {
+
+    DexType candidateType = newInstance.clazz;
+    CandidateInfo candidateInfo = candidates.get(candidateType);
+    if (candidateInfo == null) {
+      return null; // Not interested.
+    }
+
+    if (iterator.previousIndex() != 0) {
+      // Valid new instance must be the first instruction in the class initializer
+      return candidateInfo.invalidate();
+    }
+
+    if (!candidateInfo.isHostClassInitializer(method)) {
+      // A valid candidate must only have one instantiation which is
+      // done in the static initializer of the host class.
+      return candidateInfo.invalidate();
+    }
+
+    if (candidateInfo.instancesCreated.incrementAndGet() > 1) {
+      // Only one instance must be ever created.
+      return candidateInfo.invalidate();
+    }
+
+    Value candidateValue = newInstance.dest();
+    if (candidateValue == null) {
+      // Must be assigned to a singleton field.
+      return candidateInfo.invalidate();
+    }
+
+    if (candidateValue.numberOfPhiUsers() > 0) {
+      return candidateInfo.invalidate();
+    }
+
+    if (candidateValue.numberOfUsers() != 2) {
+      // We expect only two users for each instantiation: constructor call and
+      // static field write. We only check count here, since the exact instructions
+      // will be checked later.
+      return candidateInfo.invalidate();
+    }
+
+    // Check usages. Currently we only support the patterns like:
+    //
+    //     static constructor void <clinit>() {
+    //        new-instance v0, <candidate-type>
+    //  (opt) const/4 v1, #int 0 // (optional)
+    //        invoke-direct {v0, ...}, void <candidate-type>.<init>(...)
+    //        sput-object v0, <instance-field>
+    //        ...
+    //        ...
+    //
+    // In case we guarantee candidate constructor does not access <instance-field>
+    // directly or indirectly we can guarantee that all the potential reads get
+    // same non-null value.
+
+    // Skip potential constant instructions
+    while (iterator.hasNext() && isNonThrowingConstInstruction(iterator.next())) {
+      // Intentionally empty.
+    }
+    iterator.previous();
+
+    if (!iterator.hasNext()) {
+      return candidateInfo.invalidate();
+    }
+    if (!isValidInitCall(candidateInfo, iterator.next(), candidateValue, candidateType)) {
+      iterator.previous();
+      return candidateInfo.invalidate();
+    }
+
+    if (!iterator.hasNext()) {
+      return candidateInfo.invalidate();
+    }
+    if (!isValidStaticPut(candidateInfo, iterator.next())) {
+      iterator.previous();
+      return candidateInfo.invalidate();
+    }
+    if (candidateInfo.fieldWrites.incrementAndGet() > 1) {
+      return candidateInfo.invalidate();
+    }
+
+    return candidateInfo;
+  }
+
+  private boolean isNonThrowingConstInstruction(Instruction instruction) {
+    return instruction.isConstInstruction() && !instruction.instructionTypeCanThrow();
+  }
+
+  private boolean isValidInitCall(
+      CandidateInfo info, Instruction instruction, Value candidateValue, DexType candidateType) {
+    if (!instruction.isInvokeDirect()) {
+      return false;
+    }
+
+    // Check constructor.
+    InvokeDirect invoke = instruction.asInvokeDirect();
+    DexEncodedMethod methodInvoked = appInfo.lookupDirectTarget(invoke.getInvokedMethod());
+    List<Value> values = invoke.inValues();
+
+    if (values.lastIndexOf(candidateValue) != 0 ||
+        methodInvoked == null || methodInvoked.method.holder != candidateType) {
+      return false;
+    }
+
+    // Check arguments.
+    for (int i = 1; i < values.size(); i++) {
+      if (!values.get(i).definition.isConstInstruction()) {
+        return false;
+      }
+    }
+
+    DexEncodedMethod previous = info.constructor.getAndSet(methodInvoked);
+    assert previous == null;
+    return true;
+  }
+
+  private boolean isValidStaticPut(CandidateInfo info, Instruction instruction) {
+    if (!instruction.isStaticPut()) {
+      return false;
+    }
+    // Allow single assignment to a singleton field.
+    StaticPut staticPut = instruction.asStaticPut();
+    DexEncodedField fieldAccessed =
+        appInfo.lookupStaticTarget(staticPut.getField().clazz, staticPut.getField());
+    return fieldAccessed == info.singletonField;
+  }
+
+  // Static field get: can be a valid singleton field for a
+  // candidate in which case we should check if all the usages of the
+  // value read are eligible.
+  private CandidateInfo processStaticFieldRead(StaticGet staticGet) {
+    DexField field = staticGet.getField();
+    DexType candidateType = field.type;
+    CandidateInfo candidateInfo = candidates.get(candidateType);
+    if (candidateInfo == null) {
+      return null;
+    }
+
+    assert candidateInfo.singletonField == appInfo.lookupStaticTarget(field.clazz, field)
+        : "Added reference after collectCandidates(...)?";
+
+    Value singletonValue = staticGet.dest();
+    if (singletonValue != null) {
+      candidateInfo = analyzeAllValueUsers(candidateInfo, singletonValue, false);
+    }
+    return candidateInfo;
+  }
+
+  private CandidateInfo analyzeAllValueUsers(
+      CandidateInfo candidateInfo, Value value, boolean ignoreSuperClassInitInvoke) {
+    assert value != null;
+
+    if (value.numberOfPhiUsers() > 0) {
+      return candidateInfo.invalidate();
+    }
+
+    for (Instruction user : value.uniqueUsers()) {
+      if (user.isInvokeVirtual() || user.isInvokeDirect() /* private methods */) {
+        InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
+        DexMethod methodReferenced = invoke.getInvokedMethod();
+        if (factory.isConstructor(methodReferenced)) {
+          assert user.isInvokeDirect();
+          if (ignoreSuperClassInitInvoke &&
+              invoke.inValues().lastIndexOf(value) == 0 &&
+              methodReferenced == factory.objectMethods.constructor) {
+            // If we are inside candidate constructor and analyzing usages
+            // of the receiver, we want to ignore invocations of superclass
+            // constructor which will be removed after staticizing.
+            continue;
+          }
+          return candidateInfo.invalidate();
+        }
+        DexEncodedMethod methodInvoked = user.isInvokeDirect()
+            ? appInfo.lookupDirectTarget(methodReferenced)
+            : appInfo.lookupVirtualTarget(methodReferenced.holder, methodReferenced);
+        if (invoke.inValues().lastIndexOf(value) == 0 &&
+            methodInvoked != null && methodInvoked.method.holder == candidateInfo.candidate.type) {
+          continue;
+        }
+      }
+
+      // All other users are not allowed.
+      return candidateInfo.invalidate();
+    }
+
+    return candidateInfo;
+  }
+
+  // Perform staticizing candidates:
+  //
+  //  1. After filtering candidates based on usage, finalize the list of candidates by
+  //  filtering out candidates which don't satisfy the requirements:
+  //
+  //    * there must be one instance of the class
+  //    * constructor of the class used to create this instance must be a trivial one
+  //    * class initializer should only be present if candidate itself is own host
+  //    * no abstract or native instance methods
+  //
+  //  2. Rewrite instance methods of classes being staticized into static ones
+  //  3. Rewrite methods referencing staticized members, also remove instance creation
+  //
+  public final void staticizeCandidates(OptimizationFeedback feedback) {
+    phase = Phase.None; // We are done with processing/examining methods.
+    new StaticizingProcessor(this).run(feedback);
+  }
+
+  public final void fixupMethodCode(DexEncodedMethod method, IRCode code) {
+    if (phase == Phase.Fixup) {
+      assert fixupStrategy != null;
+      fixupStrategy.accept(method, code);
+    }
+  }
+
+  void setFixupStrategy(BiConsumer<DexEncodedMethod, IRCode> strategy) {
+    assert phase == Phase.None;
+    assert strategy != null;
+    phase = Phase.Fixup;
+    fixupStrategy = strategy;
+  }
+
+  void cleanFixupStrategy() {
+    assert phase == Phase.Fixup;
+    assert fixupStrategy != null;
+    phase = Phase.None;
+    fixupStrategy = null;
+  }
+
+  private class CallSiteReferencesInvalidator extends UseRegistry {
+    private boolean registerMethod(DexMethod method) {
+      registerTypeReference(method.holder);
+      registerProto(method.proto);
+      return true;
+    }
+
+    private boolean registerField(DexField field) {
+      registerTypeReference(field.clazz);
+      registerTypeReference(field.type);
+      return true;
+    }
+
+    @Override
+    public boolean registerInvokeVirtual(DexMethod method) {
+      return registerMethod(method);
+    }
+
+    @Override
+    public boolean registerInvokeDirect(DexMethod method) {
+      return registerMethod(method);
+    }
+
+    @Override
+    public boolean registerInvokeStatic(DexMethod method) {
+      return registerMethod(method);
+    }
+
+    @Override
+    public boolean registerInvokeInterface(DexMethod method) {
+      return registerMethod(method);
+    }
+
+    @Override
+    public boolean registerInvokeSuper(DexMethod method) {
+      return registerMethod(method);
+    }
+
+    @Override
+    public boolean registerInstanceFieldWrite(DexField field) {
+      return registerField(field);
+    }
+
+    @Override
+    public boolean registerInstanceFieldRead(DexField field) {
+      return registerField(field);
+    }
+
+    @Override
+    public boolean registerNewInstance(DexType type) {
+      return registerTypeReference(type);
+    }
+
+    @Override
+    public boolean registerStaticFieldRead(DexField field) {
+      return registerField(field);
+    }
+
+    @Override
+    public boolean registerStaticFieldWrite(DexField field) {
+      return registerField(field);
+    }
+
+    @Override
+    public boolean registerTypeReference(DexType type) {
+      CandidateInfo candidateInfo = candidates.get(type);
+      if (candidateInfo != null) {
+        candidateInfo.invalidate();
+      }
+      return true;
+    }
+  }
+}
+
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
new file mode 100644
index 0000000..4f6c33c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableMap;
+
+class ClassStaticizerGraphLense extends NestedGraphLense {
+  ClassStaticizerGraphLense(GraphLense previous, DexItemFactory factory,
+      BiMap<DexField, DexField> fieldMapping, BiMap<DexMethod, DexMethod> methodMapping) {
+    super(ImmutableMap.of(),
+        methodMapping,
+        fieldMapping,
+        fieldMapping.inverse(),
+        methodMapping.inverse(),
+        previous,
+        factory);
+  }
+
+  @Override
+  protected Type mapInvocationType(
+      DexMethod newMethod, DexMethod originalMethod,
+      DexEncodedMethod context, Type type) {
+    if (methodMap.get(originalMethod) == newMethod) {
+      assert type == Type.VIRTUAL || type == Type.DIRECT;
+      return Type.STATIC;
+    }
+    return super.mapInvocationType(newMethod, originalMethod, context, type);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
new file mode 100644
index 0000000..c5993dd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -0,0 +1,444 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer;
+
+import com.android.tools.r8.graph.DebugLocalInfo;
+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.DexEncodedMethod.TrivialInitializer;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.MemberType;
+import com.android.tools.r8.ir.code.StaticGet;
+import com.android.tools.r8.ir.code.StaticPut;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.CallSiteInformation;
+import com.android.tools.r8.ir.conversion.OptimizationFeedback;
+import com.android.tools.r8.ir.optimize.Outliner;
+import com.android.tools.r8.ir.optimize.staticizer.ClassStaticizer.CandidateInfo;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
+import java.util.ArrayList;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+final class StaticizingProcessor {
+  private final ClassStaticizer classStaticizer;
+
+  private final Set<DexEncodedMethod> referencingExtraMethods = Sets.newIdentityHashSet();
+  private final Map<DexEncodedMethod, CandidateInfo> hostClassInits = new IdentityHashMap<>();
+  private final Set<DexEncodedMethod> methodsToBeStaticized = Sets.newIdentityHashSet();
+  private final Map<DexField, CandidateInfo> singletonFields = new IdentityHashMap<>();
+  private final Map<DexType, DexType> candidateToHostMapping = new IdentityHashMap<>();
+
+  StaticizingProcessor(ClassStaticizer classStaticizer) {
+    this.classStaticizer = classStaticizer;
+  }
+
+  final void run(OptimizationFeedback feedback) {
+    // Filter out candidates based on the information we collected
+    // while examining methods.
+    finalEligibilityCheck();
+
+    // Prepare interim data.
+    prepareCandidates();
+
+    // Process all host class initializers (only remove instantiations).
+    processMethods(hostClassInits.keySet().stream(), this::removeCandidateInstantiation, feedback);
+
+    // Process instance methods to be staticized (only remove references to 'this').
+    processMethods(methodsToBeStaticized.stream(), this::removeReferencesToThis, feedback);
+
+    // Convert instance methods into static methods with an extra parameter.
+    Set<DexEncodedMethod> staticizedMethods = staticizeMethodSymbols();
+
+    // Process all other methods that may reference singleton fields
+    // and call methods on them. (Note that we exclude the former instance methods,
+    // but include new static methods created as a result of staticizing.
+    Stream<DexEncodedMethod> methods = Streams.concat(
+        referencingExtraMethods.stream(),
+        staticizedMethods.stream(),
+        hostClassInits.keySet().stream());
+    processMethods(methods, this::rewriteReferences, feedback);
+  }
+
+  private void finalEligibilityCheck() {
+    Iterator<Entry<DexType, CandidateInfo>> it =
+        classStaticizer.candidates.entrySet().iterator();
+    while (it.hasNext()) {
+      Entry<DexType, CandidateInfo> entry = it.next();
+      DexType candidateType = entry.getKey();
+      CandidateInfo info = entry.getValue();
+      DexProgramClass candidateClass = info.candidate;
+      DexType candidateHostType = info.hostType();
+      DexEncodedMethod constructorUsed = info.constructor.get();
+
+      int instancesCreated = info.instancesCreated.get();
+      assert instancesCreated == info.fieldWrites.get();
+      assert instancesCreated <= 1;
+      assert (instancesCreated == 0) == (constructorUsed == null);
+
+      // CHECK: One instance, one singleton field, known constructor
+      if (instancesCreated == 0) {
+        // Give up on the candidate, if there are any reads from instance
+        // field the user should read null.
+        it.remove();
+        continue;
+      }
+
+      // CHECK: instance initializer used to create an instance is trivial.
+      // NOTE: Along with requirement that candidate does not have instance
+      // fields this should guarantee that the constructor is empty.
+      assert candidateClass.instanceFields().length == 0;
+      assert constructorUsed.isProcessed();
+      TrivialInitializer trivialInitializer =
+          constructorUsed.getOptimizationInfo().getTrivialInitializerInfo();
+      if (trivialInitializer == null) {
+        it.remove();
+        continue;
+      }
+
+      // CHECK: class initializer should only be present if candidate itself is its own host.
+      DexEncodedMethod classInitializer = candidateClass.getClassInitializer();
+      assert classInitializer != null || candidateType != candidateHostType;
+      if (classInitializer != null && candidateType != candidateHostType) {
+        it.remove();
+        continue;
+      }
+
+      // CHECK: no abstract or native instance methods.
+      if (Streams.stream(candidateClass.methods()).anyMatch(
+          method -> !method.isStaticMethod() &&
+              (method.accessFlags.isAbstract() || method.accessFlags.isNative()))) {
+        it.remove();
+        continue;
+      }
+    }
+  }
+
+  private void prepareCandidates() {
+    Set<DexEncodedMethod> removedInstanceMethods = Sets.newIdentityHashSet();
+
+    for (CandidateInfo candidate : classStaticizer.candidates.values()) {
+      DexProgramClass candidateClass = candidate.candidate;
+      // Host class initializer
+      DexClass hostClass = candidate.hostClass();
+      DexEncodedMethod hostClassInitializer = hostClass.getClassInitializer();
+      assert hostClassInitializer != null;
+      CandidateInfo previous = hostClassInits.put(hostClassInitializer, candidate);
+      assert previous == null;
+
+      // Collect instance methods to be staticized.
+      for (DexEncodedMethod method : candidateClass.methods()) {
+        if (!method.isStaticMethod()) {
+          removedInstanceMethods.add(method);
+          if (!factory().isConstructor(method.method)) {
+            methodsToBeStaticized.add(method);
+          }
+        }
+      }
+      singletonFields.put(candidate.singletonField.field, candidate);
+      referencingExtraMethods.addAll(candidate.referencedFrom);
+    }
+
+    referencingExtraMethods.removeAll(removedInstanceMethods);
+  }
+
+  private void processMethods(Stream<DexEncodedMethod> methods,
+      BiConsumer<DexEncodedMethod, IRCode> strategy, OptimizationFeedback feedback) {
+    classStaticizer.setFixupStrategy(strategy);
+    methods.sorted(DexEncodedMethod::slowCompare).forEach(
+        method -> classStaticizer.converter.processMethod(method, feedback,
+            x -> false, CallSiteInformation.empty(), Outliner::noProcessing));
+    classStaticizer.cleanFixupStrategy();
+  }
+
+  private void removeCandidateInstantiation(DexEncodedMethod method, IRCode code) {
+    CandidateInfo candidateInfo = hostClassInits.get(method);
+    assert candidateInfo != null;
+
+    // Find and remove instantiation and its users.
+    InstructionIterator iterator = code.instructionIterator();
+    while (iterator.hasNext()) {
+      Instruction instruction = iterator.next();
+      if (instruction.isNewInstance() &&
+          instruction.asNewInstance().clazz == candidateInfo.candidate.type) {
+        // Remove all usages
+        // NOTE: requiring (a) the instance initializer to be trivial, (b) not allowing
+        //       candidates with instance fields and (c) requiring candidate to directly
+        //       extend java.lang.Object guarantees that the constructor is actually
+        //       empty and does not need to be inlined.
+        assert candidateInfo.candidate.superType == factory().objectType;
+        assert candidateInfo.candidate.instanceFields().length == 0;
+
+        Value singletonValue = instruction.outValue();
+        assert singletonValue != null;
+        singletonValue.uniqueUsers().forEach(Instruction::removeOrReplaceByDebugLocalRead);
+        instruction.removeOrReplaceByDebugLocalRead();
+        return;
+      }
+    }
+
+    assert false : "Must always be able to find and remove the instantiation";
+  }
+
+  private void removeReferencesToThis(DexEncodedMethod method, IRCode code) {
+    fixupStaticizedValueUsers(code, code.getThis());
+  }
+
+  private void rewriteReferences(DexEncodedMethod method, IRCode code) {
+    // Process all singleton field reads and rewrite their users.
+    List<StaticGet> singletonFieldReads =
+        Streams.stream(code.instructionIterator())
+            .filter(Instruction::isStaticGet)
+            .map(Instruction::asStaticGet)
+            .filter(get -> singletonFields.containsKey(get.getField()))
+            .collect(Collectors.toList());
+
+    singletonFieldReads.forEach(read -> {
+      CandidateInfo candidateInfo = singletonFields.get(read.getField());
+      assert candidateInfo != null;
+      Value value = read.dest();
+      if (value != null) {
+        fixupStaticizedValueUsers(code, value);
+      }
+      if (!candidateInfo.preserveRead.get()) {
+        read.removeOrReplaceByDebugLocalRead();
+      }
+    });
+
+    if (!candidateToHostMapping.isEmpty()) {
+      remapMovedCandidates(code);
+    }
+  }
+
+  // Fixup value usages: rewrites all method calls so that they point to static methods.
+  private void fixupStaticizedValueUsers(IRCode code, Value thisValue) {
+    assert thisValue != null;
+    assert thisValue.numberOfPhiUsers() == 0;
+
+    for (Instruction user : thisValue.uniqueUsers()) {
+      assert user.isInvokeVirtual() || user.isInvokeDirect();
+      InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
+      Value newValue = null;
+      Value outValue = invoke.outValue();
+      if (outValue != null) {
+        newValue = code.createValue(outValue.outType());
+        DebugLocalInfo localInfo = outValue.getLocalInfo();
+        if (localInfo != null) {
+          newValue.setLocalInfo(localInfo);
+        }
+      }
+      List<Value> args = invoke.inValues();
+      invoke.replace(new InvokeStatic(
+          invoke.getInvokedMethod(), newValue, args.subList(1, args.size())));
+    }
+
+    assert thisValue.numberOfUsers() == 0;
+  }
+
+  private void remapMovedCandidates(IRCode code) {
+    InstructionIterator it = code.instructionIterator();
+    while (it.hasNext()) {
+      Instruction instruction = it.next();
+
+      if (instruction.isStaticGet()) {
+        StaticGet staticGet = instruction.asStaticGet();
+        DexField field = mapFieldIfMoved(staticGet.getField());
+        if (field != staticGet.getField()) {
+          Value outValue = staticGet.dest();
+          assert outValue != null;
+          it.replaceCurrentInstruction(
+              new StaticGet(
+                  MemberType.fromDexType(field.type),
+                  code.createValue(ValueType.fromDexType(field.type), outValue.getLocalInfo()),
+                  field
+              )
+          );
+        }
+        continue;
+      }
+
+      if (instruction.isStaticPut()) {
+        StaticPut staticPut = instruction.asStaticPut();
+        DexField field = mapFieldIfMoved(staticPut.getField());
+        if (field != staticPut.getField()) {
+          it.replaceCurrentInstruction(
+              new StaticPut(MemberType.fromDexType(field.type), staticPut.inValue(), field)
+          );
+        }
+        continue;
+      }
+
+      if (instruction.isInvokeStatic()) {
+        InvokeStatic invoke = instruction.asInvokeStatic();
+        DexMethod method = invoke.getInvokedMethod();
+        DexType hostType = candidateToHostMapping.get(method.holder);
+        if (hostType != null) {
+          DexMethod newMethod = factory().createMethod(hostType, method.proto, method.name);
+          Value outValue = invoke.outValue();
+          Value newOutValue = method.proto.returnType.isVoidType() ? null
+              : code.createValue(
+                  ValueType.fromDexType(method.proto.returnType),
+                  outValue == null ? null : outValue.getLocalInfo());
+          it.replaceCurrentInstruction(
+              new InvokeStatic(newMethod, newOutValue, invoke.inValues()));
+        }
+        continue;
+      }
+    }
+  }
+
+  private DexField mapFieldIfMoved(DexField field) {
+    DexType hostType = candidateToHostMapping.get(field.clazz);
+    if (hostType != null) {
+      field = factory().createField(hostType, field.type, field.name);
+    }
+    hostType = candidateToHostMapping.get(field.type);
+    if (hostType != null) {
+      field = factory().createField(field.clazz, hostType, field.name);
+    }
+    return field;
+  }
+
+  private Set<DexEncodedMethod> staticizeMethodSymbols() {
+    BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
+    BiMap<DexField, DexField> fieldMapping = HashBiMap.create();
+
+    Set<DexEncodedMethod> staticizedMethods = Sets.newIdentityHashSet();
+    for (CandidateInfo candidate : classStaticizer.candidates.values()) {
+      DexProgramClass candidateClass = candidate.candidate;
+
+      // Move instance methods into static ones.
+      List<DexEncodedMethod> newDirectMethods = new ArrayList<>();
+      for (DexEncodedMethod method : candidateClass.methods()) {
+        if (method.isStaticMethod()) {
+          newDirectMethods.add(method);
+        } else if (!factory().isConstructor(method.method)) {
+          DexEncodedMethod staticizedMethod = method.toStaticMethodWithoutThis();
+          newDirectMethods.add(staticizedMethod);
+          staticizedMethods.add(staticizedMethod);
+          methodMapping.put(method.method, staticizedMethod.method);
+        }
+      }
+      candidateClass.setVirtualMethods(DexEncodedMethod.EMPTY_ARRAY);
+      candidateClass.setDirectMethods(
+          newDirectMethods.toArray(new DexEncodedMethod[newDirectMethods.size()]));
+
+      // Consider moving static members from candidate into host.
+      DexType hostType = candidate.hostType();
+      if (candidateClass.type != hostType) {
+        DexClass hostClass = classStaticizer.appInfo.definitionFor(hostType);
+        assert hostClass != null;
+        if (!classMembersConflict(candidateClass, hostClass)) {
+          // Move all members of the candidate class into host class.
+          moveMembersIntoHost(staticizedMethods,
+              candidateClass, hostType, hostClass, methodMapping, fieldMapping);
+        }
+      }
+    }
+
+    if (!methodMapping.isEmpty() || fieldMapping.isEmpty()) {
+      classStaticizer.converter.setGraphLense(
+          new ClassStaticizerGraphLense(
+              classStaticizer.converter.getGraphLense(),
+              classStaticizer.factory,
+              fieldMapping,
+              methodMapping));
+    }
+    return staticizedMethods;
+  }
+
+  private boolean classMembersConflict(DexClass a, DexClass b) {
+    assert Streams.stream(a.methods()).allMatch(DexEncodedMethod::isStaticMethod);
+    assert a.instanceFields().length == 0;
+    return Stream.of(a.staticFields()).anyMatch(fld -> b.lookupField(fld.field) != null) ||
+        Streams.stream(a.methods()).anyMatch(method -> b.lookupMethod(method.method) != null);
+  }
+
+  private void moveMembersIntoHost(Set<DexEncodedMethod> staticizedMethods,
+      DexProgramClass candidateClass,
+      DexType hostType, DexClass hostClass,
+      BiMap<DexMethod, DexMethod> methodMapping,
+      BiMap<DexField, DexField> fieldMapping) {
+    candidateToHostMapping.put(candidateClass.type, hostType);
+
+    // Process static fields.
+    // Append fields first.
+    if (candidateClass.staticFields().length > 0) {
+      DexEncodedField[] oldFields = hostClass.staticFields();
+      DexEncodedField[] extraFields = candidateClass.staticFields();
+      DexEncodedField[] newFields = new DexEncodedField[oldFields.length + extraFields.length];
+      System.arraycopy(oldFields, 0, newFields, 0, oldFields.length);
+      System.arraycopy(extraFields, 0, newFields, oldFields.length, extraFields.length);
+      hostClass.setStaticFields(newFields);
+    }
+
+    // Fixup field types.
+    DexEncodedField[] staticFields = hostClass.staticFields();
+    for (int i = 0; i < staticFields.length; i++) {
+      DexEncodedField field = staticFields[i];
+      DexField newField = mapCandidateField(field.field, candidateClass.type, hostType);
+      if (newField != field.field) {
+        staticFields[i] = field.toTypeSubstitutedField(newField);
+        fieldMapping.put(field.field, newField);
+      }
+    }
+
+    // Process static methods.
+    if (candidateClass.directMethods().length > 0) {
+      DexEncodedMethod[] oldMethods = hostClass.directMethods();
+      DexEncodedMethod[] extraMethods = candidateClass.directMethods();
+      DexEncodedMethod[] newMethods = new DexEncodedMethod[oldMethods.length + extraMethods.length];
+      System.arraycopy(oldMethods, 0, newMethods, 0, oldMethods.length);
+      for (int i = 0; i < extraMethods.length; i++) {
+        DexEncodedMethod method = extraMethods[i];
+        DexEncodedMethod newMethod = method.toTypeSubstitutedMethod(
+            factory().createMethod(hostType, method.method.proto, method.method.name));
+        newMethods[oldMethods.length + i] = newMethod;
+        staticizedMethods.add(newMethod);
+        staticizedMethods.remove(method);
+        DexMethod originalMethod = methodMapping.inverse().get(method.method);
+        if (originalMethod == null) {
+          methodMapping.put(method.method, newMethod.method);
+        } else {
+          methodMapping.put(originalMethod, newMethod.method);
+        }
+      }
+      hostClass.setDirectMethods(newMethods);
+    }
+  }
+
+  private DexField mapCandidateField(DexField field, DexType candidateType, DexType hostType) {
+    return field.clazz != candidateType && field.type != candidateType ? field
+        : factory().createField(
+            field.clazz == candidateType ? hostType : field.clazz,
+            field.type == candidateType ? hostType : field.type,
+            field.name);
+  }
+
+  private DexItemFactory factory() {
+    return classStaticizer.factory;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index a9df6a3..8fd09d0 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -40,8 +40,10 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
 import it.unimi.dsi.fastutil.ints.IntArraySet;
 import it.unimi.dsi.fastutil.ints.IntIterator;
+import it.unimi.dsi.fastutil.ints.IntList;
 import it.unimi.dsi.fastutil.ints.IntSet;
 import it.unimi.dsi.fastutil.objects.Reference2IntArrayMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
@@ -144,6 +146,10 @@
   // List of intervals that no register has been allocated to sorted by first live range.
   protected PriorityQueue<LiveIntervals> unhandled = new PriorityQueue<>();
 
+  // The registers that have been released as a result of advancing to the next live intervals.
+  // A register is released if an active or inactive interval becomes handled.
+  private IntList expiredHere = new IntArrayList();
+
   // List of intervals for the result of move-exception instructions.
   // Always empty in mode ALLOW_ARGUMENT_REUSE.
   private List<LiveIntervals> moveExceptionIntervals = new ArrayList<>();
@@ -826,6 +832,7 @@
     // Go through each unhandled live interval and find a register for it.
     while (!unhandled.isEmpty()) {
       assert invariantsHold(mode);
+      expiredHere.clear();
 
       LiveIntervals unhandledInterval = unhandled.poll();
 
@@ -849,6 +856,12 @@
         if (start >= activeIntervals.getEnd()) {
           activeIterator.remove();
           freeOccupiedRegistersForIntervals(activeIntervals);
+          if (start == activeIntervals.getEnd()) {
+            expiredHere.add(activeIntervals.getRegister());
+            if (activeIntervals.getType().isWide()) {
+              expiredHere.add(activeIntervals.getRegister() + 1);
+            }
+          }
         } else if (!activeIntervals.overlapsPosition(start)) {
           activeIterator.remove();
           assert activeIntervals.getRegister() != NO_REGISTER;
@@ -863,6 +876,12 @@
         LiveIntervals inactiveIntervals = inactiveIterator.next();
         if (start >= inactiveIntervals.getEnd()) {
           inactiveIterator.remove();
+          if (start == inactiveIntervals.getEnd()) {
+            expiredHere.add(inactiveIntervals.getRegister());
+            if (inactiveIntervals.getType().isWide()) {
+              expiredHere.add(inactiveIntervals.getRegister() + 1);
+            }
+          }
         } else if (inactiveIntervals.overlapsPosition(start)) {
           inactiveIterator.remove();
           assert inactiveIntervals.getRegister() != NO_REGISTER;
@@ -1153,17 +1172,137 @@
     return false;
   }
 
-  private int getSpillRegister(LiveIntervals intervals) {
+  private int getNewSpillRegister(LiveIntervals intervals) {
     if (intervals.isArgumentInterval()) {
       return intervals.getSplitParent().getRegister();
     }
 
     int register = maxRegisterNumber + 1;
     increaseCapacity(maxRegisterNumber + intervals.requiredRegisters());
+    return register;
+  }
+
+  private int getSpillRegister(LiveIntervals intervals, IntList excludedRegisters) {
+    if (intervals.isArgumentInterval()) {
+      return intervals.getSplitParent().getRegister();
+    }
+
+    TreeSet<Integer> previousFreeRegisters = new TreeSet<>(freeRegisters);
+    int previousMaxRegisterNumber = maxRegisterNumber;
+    freeRegisters.removeAll(expiredHere);
+    if (excludedRegisters != null) {
+      freeRegisters.removeAll(excludedRegisters);
+    }
+
+    // Check if we can use a register that was previously used as a register for intervals.
+    // This could lead to fewer moves during resolution.
+    int register = -1;
+    for (LiveIntervals split : intervals.getSplitParent().getSplitChildren()) {
+      int candidate = split.getRegister();
+      if (candidate != NO_REGISTER
+          && registersAreFreeAndConsecutive(candidate, intervals.getType().isWide())
+          && maySpillLiveIntervalsToRegister(intervals, candidate, previousMaxRegisterNumber)) {
+        register = candidate;
+        break;
+      }
+    }
+
+    if (register == -1) {
+      do {
+        // If the register needs to fit in 4 bits at the next use, then prioritize a small register.
+        // If we can find a small register, we do not need to insert a move at the next use.
+        boolean prioritizeSmallRegisters =
+            !intervals.getUses().isEmpty()
+                && intervals.getUses().first().getLimit() == Constants.U4BIT_MAX;
+        register =
+            getFreeConsecutiveRegisters(intervals.requiredRegisters(), prioritizeSmallRegisters);
+      } while (!maySpillLiveIntervalsToRegister(intervals, register, previousMaxRegisterNumber));
+    }
+
+    // Going to spill to the register (pair).
+    freeRegisters = previousFreeRegisters;
+    // If getFreeConsecutiveRegisters had to increment |maxRegisterNumber|, we need to update
+    // freeRegisters.
+    for (int i = previousMaxRegisterNumber + 1; i <= maxRegisterNumber; ++i) {
+      freeRegisters.add(i);
+    }
     assert registersAreFree(register, intervals.getType().isWide());
     return register;
   }
 
+  private boolean maySpillLiveIntervalsToRegister(
+      LiveIntervals intervals, int register, int previousMaxRegisterNumber) {
+    if (register > previousMaxRegisterNumber) {
+      // Nothing can prevent us from spilling to an entirely fresh register.
+      return true;
+    }
+
+    // If we are about to spill to an argument register, we need to be careful that the live range
+    // that is being spilled does not overlap with the live range of the corresponding argument.
+    //
+    // Note that this is *not* guaranteed when overlapsInactiveIntervals is null, because it is
+    // possible that some live ranges of the argument are still in the unhandled set.
+    if (register < numberOfArgumentRegisters) {
+      // Find the first argument value that uses the given register.
+      LiveIntervals argumentLiveIntervals = firstArgumentValue.getLiveIntervals();
+      while (!argumentLiveIntervals.usesRegister(register, intervals.getType().isWide())) {
+        argumentLiveIntervals = argumentLiveIntervals.getNextConsecutive();
+        assert argumentLiveIntervals != null;
+      }
+      do {
+        if (argumentLiveIntervals.anySplitOverlaps(intervals)) {
+          // Remove so that next invocation of getFreeConsecutiveRegisters does not consider this.
+          freeRegisters.remove(register);
+          // We have just established that there is an overlap between the live range of the
+          // current argument and the live range we need to find a register for. Therefore, if
+          // the argument is wide, and the current register corresponds to the low register of the
+          // argument, we know that the subsequent register will not work either.
+          if (register == argumentLiveIntervals.getRegister()
+              && argumentLiveIntervals.getType().isWide()) {
+            freeRegisters.remove(register + 1);
+          }
+          return false;
+        }
+        // The next argument live interval may also use the register, if it is a wide register pair.
+        argumentLiveIntervals = argumentLiveIntervals.getNextConsecutive();
+      } while (argumentLiveIntervals != null
+          && argumentLiveIntervals.usesRegister(register, intervals.getType().isWide()));
+    }
+
+    // Check for overlap with inactive intervals.
+    LiveIntervals overlapsInactiveIntervals = null;
+    for (LiveIntervals inactiveIntervals : inactive) {
+      if (inactiveIntervals.usesRegister(register, intervals.getType().isWide())
+          && intervals.overlaps(inactiveIntervals)) {
+        overlapsInactiveIntervals = inactiveIntervals;
+        break;
+      }
+    }
+    if (overlapsInactiveIntervals != null) {
+      // Remove so that next invocation of getFreeConsecutiveRegisters does not consider this.
+      freeRegisters.remove(register);
+      if (register == overlapsInactiveIntervals.getRegister()
+          && overlapsInactiveIntervals.getType().isWide()) {
+        freeRegisters.remove(register + 1);
+      }
+      return false;
+    }
+
+    // Check for overlap with the move exception interval.
+    boolean overlapsMoveExceptionInterval =
+        hasDedicatedMoveExceptionRegister()
+            && (register == getMoveExceptionRegister()
+                || (intervals.getType().isWide() && register + 1 == getMoveExceptionRegister()))
+            && overlapsMoveExceptionInterval(intervals);
+    if (overlapsMoveExceptionInterval) {
+      // Remove so that next invocation of getFreeConsecutiveRegisters does not consider this.
+      freeRegisters.remove(register);
+      return false;
+    }
+
+    return true;
+  }
+
   private int toInstructionPosition(int position) {
     return position % 2 == 0 ? position : position + 1;
   }
@@ -1496,7 +1635,7 @@
       // of finding another candidate to spill via allocateBlockedRegister.
       if (!unhandledInterval.getUses().first().hasConstraint()) {
         int nextConstrainedPosition = unhandledInterval.firstUseWithConstraint().getPosition();
-        int register = getSpillRegister(unhandledInterval);
+        int register = getSpillRegister(unhandledInterval, null);
         LiveIntervals split = unhandledInterval.splitBefore(nextConstrainedPosition);
         assignFreeRegisterToUnhandledInterval(unhandledInterval, register);
         unhandled.add(split);
@@ -1839,7 +1978,8 @@
       int splitPosition = unhandledInterval.getFirstUse();
       LiveIntervals split = unhandledInterval.splitBefore(splitPosition);
       assert split != unhandledInterval;
-      int registerNumber = getSpillRegister(unhandledInterval);
+      // Experiments show that it has a positive impact on code size to use a fresh register here.
+      int registerNumber = getNewSpillRegister(unhandledInterval);
       assignFreeRegisterToUnhandledInterval(unhandledInterval, registerNumber);
       unhandledInterval.setSpilled(true);
       unhandled.add(split);
@@ -1926,6 +2066,18 @@
       LiveIntervals unhandledInterval, int candidate, boolean candidateIsWide) {
     assert unhandledInterval.getRegister() == NO_REGISTER;
     assert atLeastOneOfRegistersAreTaken(candidate, candidateIsWide);
+    // Registers that we cannot choose for spilling.
+    IntList excludedRegisters = new IntArrayList(candidateIsWide ? 2 : 1);
+    excludedRegisters.add(candidate);
+    if (candidateIsWide) {
+      excludedRegisters.add(candidate + 1);
+    }
+    if (unhandledInterval.isArgumentInterval()
+        && unhandledInterval != unhandledInterval.getSplitParent()) {
+      // This live interval will become active in its original argument register and in the
+      // candidate register simultaneously.
+      unhandledInterval.getSplitParent().forEachRegister(excludedRegisters::add);
+    }
     // Spill overlapping active intervals.
     List<LiveIntervals> newActive = new ArrayList<>();
     Iterator<LiveIntervals> activeIterator = active.iterator();
@@ -1934,7 +2086,7 @@
       assert registersForIntervalsAreTaken(intervals);
       if (intervals.usesRegister(candidate, candidateIsWide)) {
         activeIterator.remove();
-        int registerNumber = getSpillRegister(intervals);
+        int registerNumber = getSpillRegister(intervals, excludedRegisters);
         // Important not to free the registers for intervals before finding a spill register,
         // because we might otherwise end up spilling to the current registers of intervals,
         // depending on getSpillRegister.
@@ -2662,8 +2814,33 @@
   }
 
   private int getFreeConsecutiveRegisters(int numberOfRegisters) {
+    return getFreeConsecutiveRegisters(numberOfRegisters, false);
+  }
+
+  private int getFreeConsecutiveRegisters(int numberOfRegisters, boolean prioritizeSmallRegisters) {
     int oldMaxRegisterNumber = maxRegisterNumber;
-    Iterator<Integer> freeRegistersIterator = freeRegisters.iterator();
+    TreeSet<Integer> freeRegistersWithDesiredOrdering = this.freeRegisters;
+    if (prioritizeSmallRegisters) {
+      freeRegistersWithDesiredOrdering =
+          new TreeSet<>(
+              (Integer x, Integer y) -> {
+                boolean xIsArgument = x < numberOfArgumentRegisters;
+                boolean yIsArgument = y < numberOfArgumentRegisters;
+                // If x is an argument and y is not, then prioritize y.
+                if (xIsArgument && !yIsArgument) {
+                  return 1;
+                }
+                // If x is not an argument and y is, then prioritize x.
+                if (!xIsArgument && yIsArgument) {
+                  return -1;
+                }
+                // Otherwise use their normal ordering.
+                return x - y;
+              });
+      freeRegistersWithDesiredOrdering.addAll(this.freeRegisters);
+    }
+
+    Iterator<Integer> freeRegistersIterator = freeRegistersWithDesiredOrdering.iterator();
     int first = getNextFreeRegister(freeRegistersIterator);
     int current = first;
     while (current - first + 1 != numberOfRegisters) {
@@ -2692,6 +2869,22 @@
     return first;
   }
 
+  private boolean registersAreFreeAndConsecutive(int register, boolean registerIsWide) {
+    if (!freeRegisters.contains(register)) {
+      return false;
+    }
+    if (registerIsWide) {
+      if (!freeRegisters.contains(register + 1)) {
+        return false;
+      }
+      if (register == numberOfArgumentRegisters - 1) {
+        // Will not be consecutive after reordering the arguments and temporaries.
+        return false;
+      }
+    }
+    return true;
+  }
+
   private int getNextFreeRegister(Iterator<Integer> freeRegistersIterator) {
     if (freeRegistersIterator.hasNext()) {
       return freeRegistersIterator.next();
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index a5807c3..df273a7 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
@@ -8,27 +8,36 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.SourceCode;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 public final class SynthesizedCode extends Code {
 
-  private final SourceCode sourceCode;
+  private final Supplier<SourceCode> sourceCodeProvider;
   private final Consumer<UseRegistry> registryCallback;
 
-  public SynthesizedCode(SourceCode sourceCode) {
-    this.sourceCode = sourceCode;
+  public SynthesizedCode(SourceCode sourceCodeProvider) {
+    this(() -> sourceCodeProvider);
+  }
+
+  public SynthesizedCode(Supplier<SourceCode> sourceCodeProvider) {
+    this.sourceCodeProvider = sourceCodeProvider;
     this.registryCallback = SynthesizedCode::registerReachableDefinitionsDefault;
   }
 
-  public SynthesizedCode(SourceCode sourceCode, Consumer<UseRegistry> callback) {
-    this.sourceCode = sourceCode;
+  public SynthesizedCode(
+      SourceCode sourceCode, Consumer<UseRegistry> callback) {
+    this.sourceCodeProvider = () -> sourceCode;
     this.registryCallback = callback;
   }
 
@@ -39,8 +48,29 @@
 
   @Override
   public final IRCode buildIR(
-      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
-    return new IRBuilder(encodedMethod, appInfo, sourceCode, options).build();
+      DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
+      GraphLense graphLense,
+      InternalOptions options,
+      Origin origin) {
+    assert getOwner() == encodedMethod;
+    return new IRBuilder(encodedMethod, appInfo, sourceCodeProvider.get(), options).build();
+  }
+
+  @Override
+  public IRCode buildInliningIR(
+      DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
+      GraphLense graphLense,
+      InternalOptions options,
+      ValueNumberGenerator valueNumberGenerator,
+      Position callerPosition,
+      Origin origin) {
+    assert getOwner() == encodedMethod;
+    IRBuilder builder =
+        new IRBuilder(
+            encodedMethod, appInfo, sourceCodeProvider.get(), options, valueNumberGenerator);
+    return builder.build();
   }
 
   @Override
@@ -59,17 +89,16 @@
 
   @Override
   protected final int computeHashCode() {
-    return sourceCode.hashCode();
+    throw new Unreachable();
   }
 
   @Override
   protected final boolean computeEquals(Object other) {
-    return other instanceof SynthesizedCode &&
-        this.sourceCode.equals(((SynthesizedCode) other).sourceCode);
+    throw new Unreachable();
   }
 
   @Override
   public final String toString(DexEncodedMethod method, ClassNameMapper naming) {
-    return "SynthesizedCode: " + sourceCode.toString();
+    return "SynthesizedCode";
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index c64d8ed..991d371 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -214,7 +214,7 @@
   }
 
   @Override
-  public Position getDebugPositionAtOffset(int offset) {
+  public Position getCanonicalDebugPositionAtOffset(int offset) {
     throw new Unreachable();
   }
 
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 7a25e1a..6537667 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.DexValue.UnknownDexValue;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.JarClassFileReader;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
@@ -61,6 +62,7 @@
   private static final boolean PRINT_CF = false;
 
   private final DexApplication application;
+  private final GraphLense graphLense;
   private final NamingLens namingLens;
   private final InternalOptions options;
 
@@ -72,10 +74,12 @@
       DexApplication application,
       InternalOptions options,
       String deadCode,
+      GraphLense graphLense,
       NamingLens namingLens,
       String proguardSeedsData,
       ProguardMapSupplier proguardMapSupplier) {
     this.application = application;
+    this.graphLense = graphLense;
     this.namingLens = namingLens;
     this.options = options;
     this.proguardMapSupplier = proguardMapSupplier;
@@ -102,7 +106,13 @@
       }
     }
     ApplicationWriter.supplyAdditionalConsumers(
-        application, namingLens, options, deadCode, proguardMapSupplier, proguardSeedsData);
+        application,
+        graphLense,
+        namingLens,
+        options,
+        deadCode,
+        proguardMapSupplier,
+        proguardSeedsData);
   }
 
   private void writeClass(DexProgramClass clazz, ClassFileConsumer consumer) throws IOException {
diff --git a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
index 459d7de..c707968 100644
--- a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.graph.JarApplicationReader;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.conversion.JarSourceCode;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import org.objectweb.asm.MethodVisitor;
@@ -40,7 +40,7 @@
   private final DexEncodedMethod method;
   private final DexType invocationContext;
 
-  private Constraint constraint;
+  private ConstraintWithTarget constraint;
 
   public InliningConstraintVisitor(
       JarApplicationReader application,
@@ -58,22 +58,22 @@
     this.invocationContext = invocationContext;
 
     // Model a synchronized method as having a monitor instruction.
-    this.constraint =
-        method.accessFlags.isSynchronized() ? inliningConstraints.forMonitor() : Constraint.ALWAYS;
+    this.constraint = method.accessFlags.isSynchronized()
+        ? inliningConstraints.forMonitor() : ConstraintWithTarget.ALWAYS;
   }
 
-  public Constraint getConstraint() {
+  public ConstraintWithTarget getConstraint() {
     return constraint;
   }
 
-  private void updateConstraint(Constraint other) {
-    constraint = Constraint.min(constraint, other);
+  private void updateConstraint(ConstraintWithTarget other) {
+    constraint = ConstraintWithTarget.meet(constraint, other, appInfo);
   }
 
   // Used to signal that the result is ready, such that we do not need to visit all instructions of
   // the method, if we can see early on that it cannot be inlined anyway.
   public boolean isFinished() {
-    return constraint == Constraint.NEVER;
+    return constraint == ConstraintWithTarget.NEVER;
   }
 
   public void accept(TryCatchBlockNode tryCatchBlock) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 05c7b92..fb6ef33 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -10,11 +10,20 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.Set;
 
 /** Class provides basic information about symbols related to Kotlin support. */
 public final class Kotlin {
+  // Simply "Lkotlin/", but to avoid being renamed by Shadow.relocate
+  private static final String KOTLIN =
+      String.join("", ImmutableList.of("L", "k", "o", "t", "l", "i", "n", "/"));
+
+  static String addKotlinPrefix(String str) {
+    return KOTLIN + str;
+  }
+
   public final DexItemFactory factory;
 
   public final Functional functional;
@@ -44,14 +53,15 @@
       //
       // This implementation just ignores lambdas with arity > 22.
       for (int i = 0; i <= 22; i++) {
-        functions.add(factory.createType("Lkotlin/jvm/functions/Function" + i + ";"));
+        functions.add(factory.createType(addKotlinPrefix("jvm/functions/Function") + i + ";"));
       }
     }
 
     public final DexString kotlinStyleLambdaInstanceName = factory.createString("INSTANCE");
 
-    public final DexType functionBase = factory.createType("Lkotlin/jvm/internal/FunctionBase;");
-    public final DexType lambdaType = factory.createType("Lkotlin/jvm/internal/Lambda;");
+    public final DexType functionBase =
+        factory.createType(addKotlinPrefix("jvm/internal/FunctionBase;"));
+    public final DexType lambdaType = factory.createType(addKotlinPrefix("jvm/internal/Lambda;"));
 
     public final DexMethod lambdaInitializerMethod = factory.createMethod(
         lambdaType,
@@ -64,7 +74,7 @@
   }
 
   public final class Metadata {
-    public final DexType kotlinMetadataType = factory.createType("Lkotlin/Metadata;");
+    public final DexType kotlinMetadataType = factory.createType(addKotlinPrefix("Metadata;"));
     public final DexString kind = factory.createString("k");
     public final DexString metadataVersion = factory.createString("mv");
     public final DexString bytecodeVersion = factory.createString("bv");
@@ -77,7 +87,7 @@
 
   // kotlin.jvm.internal.Intrinsics class
   public final class Intrinsics {
-    public final DexType type = factory.createType("Lkotlin/jvm/internal/Intrinsics;");
+    public final DexType type = factory.createType(addKotlinPrefix("jvm/internal/Intrinsics;"));
     public final DexMethod throwParameterIsNullException = factory.createMethod(type,
         factory.createProto(factory.voidType, factory.stringType), "throwParameterIsNullException");
     public final DexMethod checkParameterIsNotNull = factory.createMethod(type,
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index 74184a5..d15ed5a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -4,7 +4,12 @@
 
 package com.android.tools.r8.kotlin;
 
+import static kotlinx.metadata.Flag.Property.IS_VAR;
+
 import kotlinx.metadata.KmClassVisitor;
+import kotlinx.metadata.KmConstructorVisitor;
+import kotlinx.metadata.KmFunctionVisitor;
+import kotlinx.metadata.KmPropertyVisitor;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 public class KotlinClass extends KotlinInfo<KotlinClassMetadata.Class> {
@@ -20,13 +25,30 @@
   }
 
   @Override
-  void validateMetadata(KotlinClassMetadata.Class metadata) {
-    ClassMetadataVisitor visitor = new ClassMetadataVisitor();
+  void processMetadata(KotlinClassMetadata.Class metadata) {
     // To avoid lazy parsing/verifying metadata.
-    metadata.accept(visitor);
+    metadata.accept(new ClassVisitorForNonNullParameterHints());
   }
 
-  private static class ClassMetadataVisitor extends KmClassVisitor {
+  private class ClassVisitorForNonNullParameterHints extends KmClassVisitor {
+    @Override
+    public KmFunctionVisitor visitFunction(int functionFlags, String functionName) {
+      return new NonNullParameterHintCollector.FunctionVisitor(nonNullparamHints);
+    }
+
+    @Override
+    public KmConstructorVisitor visitConstructor(int ctorFlags) {
+      return new NonNullParameterHintCollector.ConstructorVisitor(nonNullparamHints);
+    }
+
+    @Override
+    public KmPropertyVisitor visitProperty(
+        int propertyFlags, String name, int getterFlags, int setterFlags) {
+      if (IS_VAR.invoke(propertyFlags)) {
+        return new NonNullParameterHintCollector.PropertyVisitor(nonNullparamHints);
+      }
+      return null;
+    }
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
index 62db3d1..de8a12a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -20,7 +20,7 @@
   }
 
   @Override
-  void validateMetadata(KotlinClassMetadata.MultiFileClassFacade metadata) {
+  void processMetadata(KotlinClassMetadata.MultiFileClassFacade metadata) {
     // No worries about lazy parsing/verifying, since no API to explore metadata details.
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 32aebff..389a8f8 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -36,7 +36,7 @@
             new StringDiagnostic("Class " + clazz.type.toSourceString()
                 + " has malformed kotlin.Metadata: " + e.getMessage()));
       } catch (Throwable e) {
-         reporter.warning(
+        reporter.warning(
             new StringDiagnostic("Unexpected error while reading " + clazz.type.toSourceString()
                 + "'s kotlin.Metadata: " + e.getMessage()));
       }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index da66e6c..225e63a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -4,7 +4,11 @@
 
 package com.android.tools.r8.kotlin;
 
+import static kotlinx.metadata.Flag.Property.IS_VAR;
+
+import kotlinx.metadata.KmFunctionVisitor;
 import kotlinx.metadata.KmPackageVisitor;
+import kotlinx.metadata.KmPropertyVisitor;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 public final class KotlinClassPart extends KotlinInfo<KotlinClassMetadata.MultiFileClassPart> {
@@ -21,12 +25,25 @@
   }
 
   @Override
-  void validateMetadata(KotlinClassMetadata.MultiFileClassPart metadata) {
+  void processMetadata(KotlinClassMetadata.MultiFileClassPart metadata) {
     // To avoid lazy parsing/verifying metadata.
-    metadata.accept(new MultiFileClassPartMetadataVisitor());
+    metadata.accept(new PackageVisitorForNonNullParameterHints());
   }
 
-  private static class MultiFileClassPartMetadataVisitor extends KmPackageVisitor {
+  private class PackageVisitorForNonNullParameterHints extends KmPackageVisitor {
+    @Override
+    public KmFunctionVisitor visitFunction(int functionFlags, String functionName) {
+      return new NonNullParameterHintCollector.FunctionVisitor(nonNullparamHints);
+    }
+
+    @Override
+    public KmPropertyVisitor visitProperty(
+        int propertyFlags, String name, int getterFlags, int setterFlags) {
+      if (IS_VAR.invoke(propertyFlags)) {
+        return new NonNullParameterHintCollector.PropertyVisitor(nonNullparamHints);
+      }
+      return null;
+    }
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
index bcb70ed..2f9477e 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -4,7 +4,11 @@
 
 package com.android.tools.r8.kotlin;
 
+import static kotlinx.metadata.Flag.Property.IS_VAR;
+
+import kotlinx.metadata.KmFunctionVisitor;
 import kotlinx.metadata.KmPackageVisitor;
+import kotlinx.metadata.KmPropertyVisitor;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 public final class KotlinFile extends KotlinInfo<KotlinClassMetadata.FileFacade> {
@@ -21,12 +25,25 @@
   }
 
   @Override
-  void validateMetadata(KotlinClassMetadata.FileFacade metadata) {
+  void processMetadata(KotlinClassMetadata.FileFacade metadata) {
     // To avoid lazy parsing/verifying metadata.
-    metadata.accept(new FileFacadeMetadataVisitor());
+    metadata.accept(new PackageVisitorForNonNullParameterHints());
   }
 
-  private static class FileFacadeMetadataVisitor extends KmPackageVisitor {
+  private class PackageVisitorForNonNullParameterHints extends KmPackageVisitor {
+    @Override
+    public KmFunctionVisitor visitFunction(int functionFlags, String functionName) {
+      return new NonNullParameterHintCollector.FunctionVisitor(nonNullparamHints);
+    }
+
+    @Override
+    public KmPropertyVisitor visitProperty(
+        int propertyFlags, String name, int getterFlags, int setterFlags) {
+      if (IS_VAR.invoke(propertyFlags)) {
+        return new NonNullParameterHintCollector.PropertyVisitor(nonNullparamHints);
+      }
+      return null;
+    }
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
index 702e0eb..7cc8aa1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -4,22 +4,25 @@
 
 package com.android.tools.r8.kotlin;
 
+import com.google.common.collect.HashBasedTable;
+import java.util.BitSet;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 // Provides access to kotlin information.
 public abstract class KotlinInfo<MetadataKind extends KotlinClassMetadata> {
   MetadataKind metadata;
+  final HashBasedTable<String, String, BitSet> nonNullparamHints = HashBasedTable.create();
 
   KotlinInfo() {
   }
 
   KotlinInfo(MetadataKind metadata) {
-    validateMetadata(metadata);
+    processMetadata(metadata);
     this.metadata = metadata;
   }
 
-  // Subtypes will define how to validate the given metadata.
-  abstract void validateMetadata(MetadataKind metadata);
+  // Subtypes will define how to process the given metadata.
+  abstract void processMetadata(MetadataKind metadata);
 
   public enum Kind {
     Class, File, Synthetic, Part, Facade
@@ -66,4 +69,12 @@
   public KotlinClassFacade asClassFacade() {
     return null;
   }
+
+  public boolean hasNonNullParameterHints() {
+    return !nonNullparamHints.isEmpty();
+  }
+
+  public BitSet lookupNonNullParameterHint(String name, String descriptor) {
+    return nonNullparamHints.get(name, descriptor);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
index 4660800..fc11f8c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.kotlin;
 
 import com.android.tools.r8.graph.DexClass;
+import kotlinx.metadata.KmFunctionVisitor;
 import kotlinx.metadata.KmLambdaVisitor;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
@@ -32,21 +33,23 @@
   }
 
   private KotlinSyntheticClass(Flavour flavour, KotlinClassMetadata.SyntheticClass metadata) {
+    super(metadata);
     this.flavour = flavour;
-    validateMetadata(metadata);
-    this.metadata = metadata;
   }
 
   @Override
-  void validateMetadata(KotlinClassMetadata.SyntheticClass metadata) {
+  void processMetadata(KotlinClassMetadata.SyntheticClass metadata) {
     if (metadata.isLambda()) {
-      SyntheticClassMetadataVisitor visitor = new SyntheticClassMetadataVisitor();
       // To avoid lazy parsing/verifying metadata.
-      metadata.accept(visitor);
+      metadata.accept(new LambdaVisitorForNonNullParameterHints());
     }
   }
 
-  private static class SyntheticClassMetadataVisitor extends KmLambdaVisitor {
+  private class LambdaVisitorForNonNullParameterHints extends KmLambdaVisitor {
+    @Override
+    public KmFunctionVisitor visitFunction(int functionFlags, String functionName) {
+      return new NonNullParameterHintCollector.FunctionVisitor(nonNullparamHints);
+    }
   }
 
   public boolean isLambda() {
diff --git a/src/main/java/com/android/tools/r8/kotlin/NonNullParameterHintCollector.java b/src/main/java/com/android/tools/r8/kotlin/NonNullParameterHintCollector.java
new file mode 100644
index 0000000..21b9409
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/NonNullParameterHintCollector.java
@@ -0,0 +1,184 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import static kotlinx.metadata.Flag.Type.IS_NULLABLE;
+
+import com.google.common.collect.HashBasedTable;
+import java.util.BitSet;
+import kotlinx.metadata.KmConstructorExtensionVisitor;
+import kotlinx.metadata.KmConstructorVisitor;
+import kotlinx.metadata.KmExtensionType;
+import kotlinx.metadata.KmFunctionExtensionVisitor;
+import kotlinx.metadata.KmFunctionVisitor;
+import kotlinx.metadata.KmPropertyExtensionVisitor;
+import kotlinx.metadata.KmPropertyVisitor;
+import kotlinx.metadata.KmTypeVisitor;
+import kotlinx.metadata.KmValueParameterVisitor;
+import kotlinx.metadata.jvm.JvmConstructorExtensionVisitor;
+import kotlinx.metadata.jvm.JvmFieldSignature;
+import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor;
+import kotlinx.metadata.jvm.JvmMethodSignature;
+import kotlinx.metadata.jvm.JvmPropertyExtensionVisitor;
+
+class NonNullParameterHintCollector {
+
+  static class FunctionVisitor extends KmFunctionVisitor {
+
+    private final HashBasedTable<String, String, BitSet> paramHints;
+
+    private BitSet paramHint = new BitSet();
+    private int paramIndex = 0;
+    private String name = "";
+    private String descriptor = "";
+
+    FunctionVisitor(HashBasedTable<String, String, BitSet> paramHints) {
+      this.paramHints = paramHints;
+    }
+
+    @Override
+    public KmTypeVisitor visitReceiverParameterType(int typeFlags) {
+      if (!IS_NULLABLE.invoke(typeFlags)) {
+        paramHint.set(paramIndex);
+      }
+      paramIndex++;
+      return null;
+    }
+
+    @Override
+    public KmValueParameterVisitor visitValueParameter(int paramFlags, String paramName) {
+      return new KmValueParameterVisitor() {
+        @Override
+        public KmTypeVisitor visitType(int typeFlags) {
+          if (!IS_NULLABLE.invoke(typeFlags)) {
+            paramHint.set(paramIndex);
+          }
+          paramIndex++;
+          return null;
+        }
+      };
+    }
+
+    @Override
+    public KmFunctionExtensionVisitor visitExtensions(KmExtensionType type) {
+      if (type != JvmFunctionExtensionVisitor.TYPE) {
+        return null;
+      }
+      return new JvmFunctionExtensionVisitor() {
+        @Override
+        public void visit(JvmMethodSignature desc) {
+          name = desc.getName();
+          descriptor = desc.getDesc();
+        }
+      };
+    }
+
+    @Override
+    public void visitEnd() {
+      if (name.isEmpty() || descriptor.isEmpty()) {
+        return;
+      }
+      paramHints.put(name, descriptor, paramHint);
+    }
+  }
+
+  static class ConstructorVisitor extends KmConstructorVisitor {
+    private final HashBasedTable<String, String, BitSet> paramHints;
+
+    private BitSet paramHint = new BitSet();
+    private int paramIndex = 0;
+    private final String name = "<init>";
+    private String descriptor = "";
+
+    ConstructorVisitor(HashBasedTable<String, String, BitSet> paramHints) {
+      this.paramHints = paramHints;
+    }
+
+    @Override
+    public KmValueParameterVisitor visitValueParameter(int paramFlags, String paramName) {
+      return new KmValueParameterVisitor() {
+        @Override
+        public KmTypeVisitor visitType(int typeFlags) {
+          if (!IS_NULLABLE.invoke(typeFlags)) {
+            paramHint.set(paramIndex);
+          }
+          paramIndex++;
+          return null;
+        }
+      };
+    }
+
+    @Override
+    public KmConstructorExtensionVisitor visitExtensions(KmExtensionType type) {
+      if (type != JvmConstructorExtensionVisitor.TYPE) {
+        return null;
+      }
+      return new JvmConstructorExtensionVisitor() {
+        @Override
+        public void visit(JvmMethodSignature desc) {
+          assert name.equals(desc.getName());
+          descriptor = desc.getDesc();
+        }
+      };
+    }
+
+    @Override
+    public void visitEnd() {
+      if (descriptor.isEmpty()) {
+        return;
+      }
+      paramHints.put(name, descriptor, paramHint);
+    }
+  }
+
+  static class PropertyVisitor extends KmPropertyVisitor {
+    private final HashBasedTable<String, String, BitSet> paramHints;
+
+    private BitSet paramHint = new BitSet();
+    private int paramIndex = 0;
+    private String name = "";
+    private String descriptor = "";
+
+    PropertyVisitor(HashBasedTable<String, String, BitSet> paramHints) {
+      this.paramHints = paramHints;
+    }
+
+    @Override
+    public KmTypeVisitor visitReturnType(int typeFlags) {
+      if (!IS_NULLABLE.invoke(typeFlags)) {
+        paramHint.set(paramIndex);
+      }
+      paramIndex++;
+      return null;
+    }
+
+    @Override
+    public KmPropertyExtensionVisitor visitExtensions(KmExtensionType type) {
+      if (type != JvmPropertyExtensionVisitor.TYPE) {
+        return null;
+      }
+      return new JvmPropertyExtensionVisitor() {
+        @Override
+        public void visit(
+            JvmFieldSignature fieldDesc,
+            JvmMethodSignature getterDesc,
+            JvmMethodSignature setterDesc) {
+          if (setterDesc != null) {
+            name = setterDesc.getName();
+            descriptor = setterDesc.getDesc();
+          }
+        }
+      };
+    }
+
+    @Override
+    public void visitEnd() {
+      if (name.isEmpty() || descriptor.isEmpty()) {
+        return;
+      }
+      paramHints.put(name, descriptor, paramHint);
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 5b908af..e405bfb 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -25,17 +25,20 @@
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
 
 public class ClassNameMapper implements ProguardMap {
 
   public static class Builder extends ProguardMap.Builder {
-    final ImmutableMap.Builder<String, ClassNamingForNameMapper.Builder> mapBuilder;
+    private final ImmutableMap.Builder<String, ClassNamingForNameMapper.Builder> mapBuilder;
 
     private Builder() {
-      mapBuilder = ImmutableMap.builder();
+      this.mapBuilder = ImmutableMap.builder();
     }
 
     @Override
@@ -150,7 +153,12 @@
   }
 
   public void write(Writer writer) throws IOException {
-    for (ClassNamingForNameMapper naming : classNameMappings.values()) {
+    // Sort classes by their original name such that the generated Proguard map is deterministic
+    // (and easy to navigate manually).
+    List<ClassNamingForNameMapper> classNamingForNameMappers =
+        new ArrayList<>(classNameMappings.values());
+    classNamingForNameMappers.sort(Comparator.comparing(x -> x.originalName));
+    for (ClassNamingForNameMapper naming : classNamingForNameMappers) {
       naming.write(writer);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 01fff01..bf5dd5d 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import java.lang.reflect.GenericSignatureFormatError;
@@ -38,6 +39,7 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
@@ -92,7 +94,18 @@
     states.computeIfAbsent("", k -> topLevelState);
   }
 
-  Map<DexType, DexString> computeRenaming(Timing timing) {
+  static class ClassRenaming {
+    protected final Map<String, String> packageRenaming;
+    protected final Map<DexType, DexString> classRenaming;
+
+    private ClassRenaming(
+        Map<DexType, DexString> classRenaming, Map<String, String> packageRenaming) {
+      this.classRenaming = classRenaming;
+      this.packageRenaming = packageRenaming;
+    }
+  }
+
+  ClassRenaming computeRenaming(Timing timing) {
     // Use deterministic class order to make sure renaming is deterministic.
     Iterable<DexProgramClass> classes = appInfo.classesWithDeterministicOrder();
     // Collect names we have to keep.
@@ -128,7 +141,19 @@
     appInfo.dexItemFactory.forAllTypes(this::renameArrayTypeIfNeeded);
     timing.end();
 
-    return Collections.unmodifiableMap(renaming);
+    return new ClassRenaming(Collections.unmodifiableMap(renaming), getPackageRenaming());
+  }
+
+  private Map<String, String> getPackageRenaming() {
+    ImmutableMap.Builder<String, String> packageRenaming = ImmutableMap.builder();
+    for (Entry<String, Namespace> entry : states.entrySet()) {
+      String originalPackageName = entry.getKey();
+      String minifiedPackageName = entry.getValue().getPackageName();
+      if (!minifiedPackageName.equals(originalPackageName)) {
+        packageRenaming.put(originalPackageName, minifiedPackageName);
+      }
+    }
+    return packageRenaming.build();
   }
 
   private void renameDanglingTypes(DexClass clazz) {
@@ -406,6 +431,7 @@
 
   private class Namespace {
 
+    private final String packageName;
     private final char[] packagePrefix;
     private int typeCounter = 1;
     private int packageCounter = 1;
@@ -417,6 +443,7 @@
     }
 
     Namespace(String packageName, String separator) {
+      this.packageName = packageName;
       this.packagePrefix = ("L" + packageName
           // L or La/b/ (or La/b/C$)
           + (packageName.isEmpty() ? "" : separator))
@@ -425,6 +452,10 @@
       this.classDictionaryIterator = classDictionary.iterator();
     }
 
+    public String getPackageName() {
+      return packageName;
+    }
+
     private String nextSuggestedNameForClass() {
       StringBuilder nextName = new StringBuilder();
       if (classDictionaryIterator.hasNext()) {
@@ -466,7 +497,6 @@
       usedPackagePrefixes.add(candidate);
       return candidate;
     }
-
   }
 
   private class GenericSignatureRewriter implements GenericSignatureAction<DexType> {
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 34fdd9d..f45a3de 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -14,6 +14,7 @@
 import java.io.Writer;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -68,17 +69,17 @@
     /** The parameters are forwarded to MappedRange constructor, see explanation there. */
     @Override
     public void addMappedRange(
-        Range obfuscatedRange,
+        Range minifiedRange,
         MemberNaming.MethodSignature originalSignature,
         Object originalRange,
-        String obfuscatedName) {
+        String renamedName) {
       mappedRangesByName
-          .computeIfAbsent(obfuscatedName, k -> new ArrayList<>())
-          .add(new MappedRange(obfuscatedRange, originalSignature, originalRange, obfuscatedName));
+          .computeIfAbsent(renamedName, k -> new ArrayList<>())
+          .add(new MappedRange(minifiedRange, originalSignature, originalRange, renamedName));
     }
   }
 
-  /** List of MappedRanges that belong to the same obfuscated name. */
+  /** List of MappedRanges that belong to the same renamed name. */
   public static class MappedRangesOfName {
     private final List<MappedRange> mappedRanges;
 
@@ -93,14 +94,14 @@
     public MappedRange firstRangeForLine(int line) {
       MappedRange bestRange = null;
       for (MappedRange range : mappedRanges) {
-        if (range.obfuscatedRange == null) {
+        if (range.minifiedRange == null) {
           if (bestRange == null) {
             // This is an "a() -> b" mapping (no concrete line numbers), remember this if there'll
             // be no better one.
             bestRange = range;
           }
-        } else if (range.obfuscatedRange.contains(line)) {
-          // Concrete obfuscated range found ("x:y:a()[:u[:v]] -> b")
+        } else if (range.minifiedRange.contains(line)) {
+          // Concrete minified range found ("x:y:a()[:u[:v]] -> b")
           return range;
         }
       }
@@ -108,25 +109,25 @@
     }
 
     /**
-     * Search for a MappedRange where the obfuscated range contains the specified {@code line} and
-     * return that and the subsequent MappedRanges with the same obfuscated range. Return general
+     * Search for a MappedRange where the minified range contains the specified {@code line} and
+     * return that and the subsequent MappedRanges with the same minified range. Return general
      * MappedRange ("a() -> b") if no concrete mapping found or empty list if nothing found.
      */
     public List<MappedRange> allRangesForLine(int line) {
       MappedRange noLineRange = null;
       for (int i = 0; i < mappedRanges.size(); ++i) {
         MappedRange rangeI = mappedRanges.get(i);
-        if (rangeI.obfuscatedRange == null) {
+        if (rangeI.minifiedRange == null) {
           if (noLineRange == null) {
             // This is an "a() -> b" mapping (no concrete line numbers), remember this if there'll
             // be no better one.
             noLineRange = rangeI;
           }
-        } else if (rangeI.obfuscatedRange.contains(line)) {
-          // Concrete obfuscated range found ("x:y:a()[:u[:v]] -> b")
+        } else if (rangeI.minifiedRange.contains(line)) {
+          // Concrete minified range found ("x:y:a()[:u[:v]] -> b")
           int j = i + 1;
           for (; j < mappedRanges.size(); ++j) {
-            if (!Objects.equals(mappedRanges.get(j).obfuscatedRange, rangeI.obfuscatedRange)) {
+            if (!Objects.equals(mappedRanges.get(j).minifiedRange, rangeI.minifiedRange)) {
               break;
             }
           }
@@ -165,27 +166,27 @@
 
   /**
    * Mapping from the renamed signature to the naming information for a member.
-   * <p>
-   * A renamed signature is a signature where the member's name has been obfuscated but not the type
+   *
+   * <p>A renamed signature is a signature where the member's name has been renamed but not the type
    * information.
-   **/
+   */
   private final ImmutableMap<MethodSignature, MemberNaming> methodMembers;
   private final ImmutableMap<FieldSignature, MemberNaming> fieldMembers;
 
-  /** Map of obfuscated name -> MappedRangesOfName */
-  public final Map<String, MappedRangesOfName> mappedRangesByName;
+  /** Map of renamed name -> MappedRangesOfName */
+  public final Map<String, MappedRangesOfName> mappedRangesByRenamedName;
 
   private ClassNamingForNameMapper(
       String renamedName,
       String originalName,
       Map<MethodSignature, MemberNaming> methodMembers,
       Map<FieldSignature, MemberNaming> fieldMembers,
-      Map<String, MappedRangesOfName> mappedRangesByName) {
+      Map<String, MappedRangesOfName> mappedRangesByRenamedName) {
     this.renamedName = renamedName;
     this.originalName = originalName;
     this.methodMembers = ImmutableMap.copyOf(methodMembers);
     this.fieldMembers = ImmutableMap.copyOf(fieldMembers);
-    this.mappedRangesByName = mappedRangesByName;
+    this.mappedRangesByRenamedName = mappedRangesByRenamedName;
   }
 
   @Override
@@ -273,16 +274,13 @@
         });
 
     // Sort MappedRanges by sequence number to restore construction order (original Proguard-map
-    // input)
-    List<MappedRange> rangeList = new ArrayList<>();
-    for (MappedRangesOfName ranges : mappedRangesByName.values()) {
-      rangeList.addAll(ranges.mappedRanges);
+    // input).
+    List<MappedRange> mappedRangesSorted = new ArrayList<>();
+    for (MappedRangesOfName ranges : mappedRangesByRenamedName.values()) {
+      mappedRangesSorted.addAll(ranges.mappedRanges);
     }
-    rangeList.sort(
-        (lhs, rhs) -> {
-          return lhs.sequenceNumber - rhs.sequenceNumber;
-        });
-    for (MappedRange range : rangeList) {
+    mappedRangesSorted.sort(Comparator.comparingInt(range -> range.sequenceNumber));
+    for (MappedRange range : mappedRangesSorted) {
       writer.append("    ").append(range.toString()).append('\n');
     }
   }
@@ -313,7 +311,7 @@
         && renamedName.equals(that.renamedName)
         && methodMembers.equals(that.methodMembers)
         && fieldMembers.equals(that.fieldMembers)
-        && mappedRangesByName.equals(that.mappedRangesByName);
+        && mappedRangesByRenamedName.equals(that.mappedRangesByRenamedName);
   }
 
   @Override
@@ -322,27 +320,27 @@
     result = 31 * result + renamedName.hashCode();
     result = 31 * result + methodMembers.hashCode();
     result = 31 * result + fieldMembers.hashCode();
-    result = 31 * result + mappedRangesByName.hashCode();
+    result = 31 * result + mappedRangesByRenamedName.hashCode();
     return result;
   }
 
   /**
-   * MappedRange describes an (original line numbers, signature) <-> (obfuscated line numbers)
+   * MappedRange describes an (original line numbers, signature) <-> (minified line numbers)
    * mapping. It can describe 3 different things:
    *
    * <p>1. The method is renamed. The original source lines are preserved. The corresponding
    * Proguard-map syntax is "a(...) -> b"
    *
-   * <p>2. The source lines of a method in the original range are renumbered to the obfuscated
-   * range. In this case the {@link MappedRange#originalRange} is either a {@code Range} or null,
+   * <p>2. The source lines of a method in the original range are renumbered to the minified range.
+   * In this case the {@link MappedRange#originalRange} is either a {@code Range} or null,
    * indicating that the original range is unknown or is the same as the {@link
-   * MappedRange#obfuscatedRange}. The corresponding Proguard-map syntax is "x:y:a(...) -> b" or
+   * MappedRange#minifiedRange}. The corresponding Proguard-map syntax is "x:y:a(...) -> b" or
    * "x:y:a(...):u:v -> b"
    *
    * <p>3. The source line of a method is the inlining caller of the previous {@code MappedRange}.
    * In this case the {@link MappedRange@originalRange} is either an {@code int} or null, indicating
-   * that the original source line is unknown, or may be identical to a line of the obfuscated
-   * range. The corresponding Proguard-map syntax is "x:y:a(...) -> b" or "x:y:a(...):u -> b"
+   * that the original source line is unknown, or may be identical to a line of the minified range.
+   * The corresponding Proguard-map syntax is "x:y:a(...) -> b" or "x:y:a(...):u -> b"
    */
   public static class MappedRange {
 
@@ -352,10 +350,10 @@
       return nextSequenceNumber++;
     }
 
-    public final Range obfuscatedRange; // Can be null, if so then originalRange must also be null.
+    public final Range minifiedRange; // Can be null, if so then originalRange must also be null.
     public final MethodSignature signature;
     public final Object originalRange; // null, Integer or Range.
-    public final String obfuscatedName;
+    public final String renamedName;
 
     /**
      * The sole purpose of {@link #sequenceNumber} is to preserve the order of members read from a
@@ -364,52 +362,49 @@
     private final int sequenceNumber = getNextSequenceNumber();
 
     private MappedRange(
-        Range obfuscatedRange,
-        MethodSignature signature,
-        Object originalRange,
-        String obfuscatedName) {
+        Range minifiedRange, MethodSignature signature, Object originalRange, String renamedName) {
 
-      assert obfuscatedRange != null || originalRange == null;
+      assert minifiedRange != null || originalRange == null;
       assert originalRange == null
           || originalRange instanceof Integer
           || originalRange instanceof Range;
 
-      this.obfuscatedRange = obfuscatedRange;
+      this.minifiedRange = minifiedRange;
       this.signature = signature;
       this.originalRange = originalRange;
-      this.obfuscatedName = obfuscatedName;
+      this.renamedName = renamedName;
     }
 
-    public int originalLineFromObfuscated(int obfuscatedLineNumber) {
-      if (obfuscatedRange == null) {
+    public int getOriginalLineNumber(int lineNumberAfterMinification) {
+      if (minifiedRange == null) {
         // General mapping without concrete line numbers: "a() -> b"
-        return obfuscatedLineNumber;
+        return lineNumberAfterMinification;
       }
-      assert obfuscatedRange.contains(obfuscatedLineNumber);
+      assert minifiedRange.contains(lineNumberAfterMinification);
       if (originalRange == null) {
         // Concrete identity mapping: "x:y:a() -> b"
-        return obfuscatedLineNumber;
+        return lineNumberAfterMinification;
       } else if (originalRange instanceof Integer) {
         // Inlinee: "x:y:a():u -> b"
         return (int) originalRange;
       } else {
         // "x:y:a():u:v -> b"
         assert originalRange instanceof Range;
-        return ((Range) originalRange).from + obfuscatedLineNumber - obfuscatedRange.from;
+        return ((Range) originalRange).from + lineNumberAfterMinification - minifiedRange.from;
       }
     }
 
     @Override
     public String toString() {
       StringBuilder builder = new StringBuilder();
-      if (obfuscatedRange != null) {
-        builder.append(obfuscatedRange).append(':');
+      if (minifiedRange != null) {
+        builder.append(minifiedRange).append(':');
       }
       builder.append(signature);
       if (originalRange != null) {
         builder.append(":").append(originalRange);
       }
-      builder.append(" -> ").append(obfuscatedName);
+      builder.append(" -> ").append(renamedName);
       return builder.toString();
     }
 
@@ -426,19 +421,19 @@
 
       MappedRange that = (MappedRange) o;
 
-      return Objects.equals(obfuscatedRange, that.obfuscatedRange)
+      return Objects.equals(minifiedRange, that.minifiedRange)
           && Objects.equals(originalRange, that.originalRange)
           && signature.equals(that.signature)
-          && obfuscatedName.equals(that.obfuscatedName);
+          && renamedName.equals(that.renamedName);
     }
 
     @Override
     public int hashCode() {
       // sequenceNumber is intentionally omitted from hashCode since it's not used in equality test.
-      int result = Objects.hashCode(obfuscatedRange);
+      int result = Objects.hashCode(minifiedRange);
       result = 31 * result + Objects.hashCode(originalRange);
       result = 31 * result + signature.hashCode();
-      result = 31 * result + obfuscatedName.hashCode();
+      result = 31 * result + renamedName.hashCode();
       return result;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassRenamingMapper.java b/src/main/java/com/android/tools/r8/naming/ClassRenamingMapper.java
index 6b283f0..71cbc4f 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassRenamingMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassRenamingMapper.java
@@ -1,3 +1,7 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 package com.android.tools.r8.naming;
 
 import com.google.common.collect.BiMap;
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index 2fd66d0..8714b28 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -5,9 +5,12 @@
 
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
 
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.code.ConstString;
 import com.android.tools.r8.code.ConstStringJumbo;
 import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -23,20 +26,18 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardClassFilter;
+import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
 import java.util.Map;
-import java.util.Set;
 
 class IdentifierMinifier {
 
   private final AppInfoWithLiveness appInfo;
   private final ProguardClassFilter adaptClassStrings;
   private final NamingLens lens;
-  private final Set<DexItem> identifierNameStrings;
+  private final Object2BooleanMap<DexItem> identifierNameStrings;
 
   IdentifierMinifier(
-      AppInfoWithLiveness appInfo,
-      ProguardClassFilter adaptClassStrings,
-      NamingLens lens) {
+      AppInfoWithLiveness appInfo, ProguardClassFilter adaptClassStrings, NamingLens lens) {
     this.appInfo = appInfo;
     this.adaptClassStrings = adaptClassStrings;
     this.lens = lens;
@@ -86,17 +87,29 @@
     if (code == null) {
       return;
     }
-    assert code.isDexCode();
-    DexCode dexCode = code.asDexCode();
-    for (Instruction instr : dexCode.instructions) {
-      if (instr instanceof ConstString) {
-        ConstString cnst = (ConstString) instr;
-        DexString dexString = cnst.getString();
-        cnst.BBBB = getRenamedStringLiteral(dexString);
-      } else if (instr instanceof ConstStringJumbo) {
-        ConstStringJumbo cnst = (ConstStringJumbo) instr;
-        DexString dexString = cnst.getString();
-        cnst.BBBBBBBB = getRenamedStringLiteral(dexString);
+    if (code.isDexCode()) {
+      DexCode dexCode = code.asDexCode();
+      for (Instruction instr : dexCode.instructions) {
+        if (instr instanceof ConstString) {
+          ConstString cnst = (ConstString) instr;
+          DexString dexString = cnst.getString();
+          cnst.BBBB = getRenamedStringLiteral(dexString);
+        } else if (instr instanceof ConstStringJumbo) {
+          ConstStringJumbo cnst = (ConstStringJumbo) instr;
+          DexString dexString = cnst.getString();
+          cnst.BBBBBBBB = getRenamedStringLiteral(dexString);
+        }
+      }
+    } else {
+      assert code.isCfCode();
+      CfCode cfCode = code.asCfCode();
+
+      for (CfInstruction instr : cfCode.getInstructions()) {
+        if (instr instanceof CfConstString) {
+          CfConstString cnst = (CfConstString) instr;
+          DexString dexString = cnst.getString();
+          cnst.setString(getRenamedStringLiteral(dexString));
+        }
       }
     }
   }
@@ -153,19 +166,32 @@
     if (code == null) {
       return;
     }
-    assert code.isDexCode();
-    DexCode dexCode = code.asDexCode();
-    for (Instruction instr : dexCode.instructions) {
-      if (instr instanceof ConstString
-          && ((ConstString) instr).getString() instanceof DexItemBasedString) {
-        ConstString cnst = (ConstString) instr;
-        DexItemBasedString itemBasedString = (DexItemBasedString) cnst.getString();
-        cnst.BBBB = materialize(itemBasedString);
-      } else if (instr instanceof ConstStringJumbo
-          && ((ConstStringJumbo) instr).getString() instanceof DexItemBasedString) {
-        ConstStringJumbo cnst = (ConstStringJumbo) instr;
-        DexItemBasedString itemBasedString = (DexItemBasedString) cnst.getString();
-        cnst.BBBBBBBB = materialize(itemBasedString);
+    if (code.isDexCode()) {
+      DexCode dexCode = code.asDexCode();
+      for (Instruction instr : dexCode.instructions) {
+        if (instr instanceof ConstString
+            && ((ConstString) instr).getString() instanceof DexItemBasedString) {
+          ConstString cnst = (ConstString) instr;
+          DexItemBasedString itemBasedString = (DexItemBasedString) cnst.getString();
+          cnst.BBBB = materialize(itemBasedString);
+        } else if (instr instanceof ConstStringJumbo
+            && ((ConstStringJumbo) instr).getString() instanceof DexItemBasedString) {
+          ConstStringJumbo cnst = (ConstStringJumbo) instr;
+          DexItemBasedString itemBasedString = (DexItemBasedString) cnst.getString();
+          cnst.BBBBBBBB = materialize(itemBasedString);
+        }
+      }
+    } else {
+      assert code.isCfCode();
+      CfCode cfCode = code.asCfCode();
+
+      for (CfInstruction instr : cfCode.getInstructions()) {
+        if (instr instanceof CfConstString
+            && ((CfConstString) instr).getString() instanceof DexItemBasedString) {
+          CfConstString cnst = (CfConstString) instr;
+          DexItemBasedString itemBasedString = (DexItemBasedString) cnst.getString();
+          cnst.setString(materialize(itemBasedString));
+        }
       }
     }
   }
@@ -184,5 +210,4 @@
       return lens.lookupName((DexField) itemBasedString.basedOn);
     }
   }
-
 }
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index b0f4dc0..830391a 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -35,17 +35,17 @@
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Streams;
+import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
 import java.util.Arrays;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Objects;
-import java.util.Set;
 import java.util.stream.Collectors;
 
 public class IdentifierNameStringMarker {
   private final AppInfo appInfo;
   private final DexItemFactory dexItemFactory;
-  private final Set<DexItem> identifierNameStrings;
+  private final Object2BooleanMap<DexItem> identifierNameStrings;
   private final InternalOptions options;
 
   public IdentifierNameStringMarker(AppInfoWithLiveness appInfo, InternalOptions options) {
@@ -63,7 +63,7 @@
   }
 
   private void decoupleIdentifierNameStringInField(DexEncodedField encodedField) {
-    if (!identifierNameStrings.contains(encodedField.field)) {
+    if (!identifierNameStrings.containsKey(encodedField.field)) {
       return;
     }
     if (!encodedField.accessFlags.isStatic()) {
@@ -102,22 +102,27 @@
         if (instruction.isStaticPut() || instruction.isInstancePut()) {
           FieldInstruction fieldPut = instruction.asFieldInstruction();
           DexField field = fieldPut.getField();
-          if (!identifierNameStrings.contains(field)) {
+          if (!identifierNameStrings.containsKey(field)) {
             continue;
           }
+          boolean isExplicitRule = identifierNameStrings.getBoolean(field);
           Value in = instruction.isStaticPut()
               ? instruction.asStaticPut().inValue()
               : instruction.asInstancePut().value();
           if (!in.isConstString()) {
-            warnUndeterminedIdentifierIfNecessary(
-                appInfo, options, field, originHolder, instruction, null);
+            if (isExplicitRule) {
+              warnUndeterminedIdentifierIfNecessary(
+                  appInfo, options, field, originHolder, instruction, null);
+            }
             continue;
           }
           DexString original = in.getConstInstruction().asConstString().getValue();
           DexItemBasedString itemBasedString = inferMemberOrTypeFromNameString(appInfo, original);
           if (itemBasedString == null) {
-            warnUndeterminedIdentifierIfNecessary(
-                appInfo, options, field, originHolder, instruction, original);
+            if (isExplicitRule) {
+              warnUndeterminedIdentifierIfNecessary(
+                  appInfo, options, field, originHolder, instruction, original);
+            }
             continue;
           }
           // Move the cursor back to $fieldPut
@@ -163,16 +168,19 @@
         } else if (instruction.isInvokeMethod()) {
           InvokeMethod invoke = instruction.asInvokeMethod();
           DexMethod invokedMethod = invoke.getInvokedMethod();
-          if (!identifierNameStrings.contains(invokedMethod)) {
+          if (!identifierNameStrings.containsKey(invokedMethod)) {
             continue;
           }
+          boolean isExplicitRule = identifierNameStrings.getBoolean(invokedMethod);
           List<Value> ins = invoke.arguments();
           Value[] changes = new Value [ins.size()];
           if (isReflectionMethod(dexItemFactory, invokedMethod)) {
             DexItemBasedString itemBasedString = identifyIdentiferNameString(appInfo, invoke);
             if (itemBasedString == null) {
-              warnUndeterminedIdentifierIfNecessary(
-                  appInfo, options, invokedMethod, originHolder, instruction, null);
+              if (isExplicitRule) {
+                warnUndeterminedIdentifierIfNecessary(
+                    appInfo, options, invokedMethod, originHolder, instruction, null);
+              }
               continue;
             }
             DexType returnType = invoke.getReturnType();
@@ -217,16 +225,20 @@
             for (int i = 0; i < ins.size(); i++) {
               Value in = ins.get(i);
               if (!in.isConstString()) {
-                warnUndeterminedIdentifierIfNecessary(
-                    appInfo, options, invokedMethod, originHolder, instruction, null);
+                if (isExplicitRule) {
+                  warnUndeterminedIdentifierIfNecessary(
+                      appInfo, options, invokedMethod, originHolder, instruction, null);
+                }
                 continue;
               }
               DexString original = in.getConstInstruction().asConstString().getValue();
               DexItemBasedString itemBasedString =
                   inferMemberOrTypeFromNameString(appInfo, original);
               if (itemBasedString == null) {
-                warnUndeterminedIdentifierIfNecessary(
-                    appInfo, options, invokedMethod, originHolder, instruction, original);
+                if (isExplicitRule) {
+                  warnUndeterminedIdentifierIfNecessary(
+                      appInfo, options, invokedMethod, originHolder, instruction, original);
+                }
                 continue;
               }
               // Move the cursor back to $invoke
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index c766c37..094dcaf 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -134,7 +134,12 @@
     }
 
     public static FieldSignature fromDexField(DexField field) {
-      return new FieldSignature(field.name.toSourceString(),
+      return fromDexField(field, false);
+    }
+
+    public static FieldSignature fromDexField(DexField field, boolean withQualifiedName) {
+      return new FieldSignature(
+          withQualifiedName ? field.qualifiedName() : field.name.toSourceString(),
           field.type.toSourceString());
     }
 
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 382ac92..10aa8af 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
 import com.android.tools.r8.naming.MethodNameMinifier.MethodRenaming;
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -42,8 +43,8 @@
   public NamingLens run(Timing timing) {
     assert options.enableMinification;
     timing.begin("MinifyClasses");
-    Map<DexType, DexString> classRenaming =
-        new ClassNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
+    ClassNameMinifier classNameMinifier = new ClassNameMinifier(appInfo, rootSet, options);
+    ClassRenaming classRenaming = classNameMinifier.computeRenaming(timing);
     timing.end();
     timing.begin("MinifyMethods");
     MethodRenaming methodRenaming =
@@ -53,7 +54,12 @@
     Map<DexField, DexString> fieldRenaming =
         new FieldNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
     timing.end();
-    NamingLens lens = new MinifiedRenaming(classRenaming, methodRenaming, fieldRenaming, appInfo);
+    NamingLens lens =
+        new MinifiedRenaming(
+            classRenaming,
+            methodRenaming,
+            fieldRenaming,
+            appInfo);
     timing.begin("MinifyIdentifiers");
     new IdentifierMinifier(appInfo, options.proguardConfiguration.getAdaptClassStrings(), lens)
         .run();
@@ -64,21 +70,28 @@
   private static class MinifiedRenaming extends NamingLens {
 
     private final AppInfo appInfo;
+    private final Map<String, String> packageRenaming;
     private final Map<DexItem, DexString> renaming = new IdentityHashMap<>();
 
     private MinifiedRenaming(
-        Map<DexType, DexString> classRenaming,
+        ClassRenaming classRenaming,
         MethodRenaming methodRenaming,
         Map<DexField, DexString> fieldRenaming,
         AppInfo appInfo) {
       this.appInfo = appInfo;
-      renaming.putAll(classRenaming);
+      this.packageRenaming = classRenaming.packageRenaming;
+      renaming.putAll(classRenaming.classRenaming);
       renaming.putAll(methodRenaming.renaming);
       renaming.putAll(methodRenaming.callSiteRenaming);
       renaming.putAll(fieldRenaming);
     }
 
     @Override
+    public String lookupPackageName(String packageName) {
+      return packageRenaming.getOrDefault(packageName, packageName);
+    }
+
+    @Override
     public DexString lookupDescriptor(DexType type) {
       return renaming.getOrDefault(type, type.descriptor);
     }
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index f1f13b3..47459c0 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -30,6 +30,8 @@
  */
 public abstract class NamingLens {
 
+  public abstract String lookupPackageName(String packageName);
+
   public abstract DexString lookupDescriptor(DexType type);
 
   public abstract String lookupSimpleName(DexType inner, DexString innerName);
@@ -99,6 +101,11 @@
     }
 
     @Override
+    public String lookupPackageName(String packageName) {
+      return packageName;
+    }
+
+    @Override
     void forAllRenamedTypes(Consumer<DexType> consumer) {
       // Intentionally left empty.
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
index 492bcd0..46fdc82 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize;
 
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -88,6 +89,28 @@
     }
 
     @Override
+    public DexField getOriginalFieldSignature(DexField field) {
+      return previousLense.getOriginalFieldSignature(field);
+    }
+
+    @Override
+    public DexMethod getOriginalMethodSignature(DexMethod method) {
+      // TODO(b/79143143): implement this when re-enable bridge analysis.
+      throw new Unimplemented("BridgeLense.getOriginalMethodSignature() not implemented");
+    }
+
+    @Override
+    public DexField getRenamedFieldSignature(DexField originalField) {
+      return previousLense.getRenamedFieldSignature(originalField);
+    }
+
+    @Override
+    public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+      // TODO(b/79143143): implement this when re-enabling bridge analysis.
+      throw new Unimplemented("BridgeLense.getRenamedMethodSignature() not implemented");
+    }
+
+    @Override
     public DexType lookupType(DexType type) {
       return previousLense.lookupType(type);
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 73ebf73..50571f3 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -11,23 +11,16 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.optimize.MethodPoolCollection;
 import com.android.tools.r8.optimize.PublicizerLense.PublicizedLenseBuilder;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
-import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Equivalence;
-import com.google.common.base.Equivalence.Wrapper;
-import java.util.ArrayList;
-import java.util.HashSet;
 import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
 
 public final class ClassAndMemberPublicizer {
   private final DexApplication application;
@@ -36,11 +29,12 @@
   private final PublicizedLenseBuilder lenseBuilder;
 
   private final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
-  private final Map<DexClass, MethodPool> methodPools = new ConcurrentHashMap<>();
+  private final MethodPoolCollection methodPoolCollection;
 
   private ClassAndMemberPublicizer(DexApplication application, AppView appView, RootSet rootSet) {
     this.application = application;
     this.appView = appView;
+    this.methodPoolCollection = new MethodPoolCollection(application);
     this.rootSet = rootSet;
     lenseBuilder = PublicizerLense.createBuilder();
   }
@@ -64,15 +58,7 @@
   private GraphLense run(ExecutorService executorService, Timing timing)
       throws ExecutionException {
     // Phase 1: Collect methods to check if private instance methods don't have conflicts.
-    timing.begin("Phase 1: collectMethods");
-    try {
-      List<Future<?>> futures = new ArrayList<>();
-      application.classes().forEach(clazz ->
-          futures.add(executorService.submit(computeMethodPoolPerClass(clazz))));
-      ThreadUtils.awaitFutures(futures);
-    } finally {
-      timing.end();
-    }
+    methodPoolCollection.buildAll(executorService, timing);
 
     // Phase 2: Visit classes and promote class/member to public if possible.
     timing.begin("Phase 2: promoteToPublic");
@@ -83,35 +69,6 @@
     return lenseBuilder.build(appView);
   }
 
-  private Runnable computeMethodPoolPerClass(DexClass clazz) {
-    return () -> {
-      MethodPool methodPool = methodPools.computeIfAbsent(clazz, k -> new MethodPool());
-      clazz.forEachMethod(encodedMethod -> {
-        // We will add private instance methods when we promote them.
-        if (!encodedMethod.isPrivateMethod() || encodedMethod.isStaticMethod()) {
-          methodPool.seen(equivalence.wrap(encodedMethod.method));
-        }
-      });
-      if (clazz.superType != null) {
-        DexClass superClazz = application.definitionFor(clazz.superType);
-        if (superClazz != null) {
-          MethodPool superPool = methodPools.computeIfAbsent(superClazz, k -> new MethodPool());
-          superPool.linkSubtype(methodPool);
-          methodPool.linkSupertype(superPool);
-        }
-      }
-      if (clazz.isInterface()) {
-        clazz.type.forAllImplementsSubtypes(implementer -> {
-          DexClass subClazz = application.definitionFor(implementer);
-          if (subClazz != null) {
-            MethodPool childPool = methodPools.computeIfAbsent(subClazz, k -> new MethodPool());
-            childPool.linkInterface(methodPool);
-          }
-        });
-      }
-    };
-  }
-
   private void publicizeType(DexType type) {
     DexClass clazz = application.definitionFor(type);
     if (clazz != null && clazz.isProgramClass()) {
@@ -149,7 +106,8 @@
     assert accessFlags.isPrivate();
 
     if (appView.getDexItemFactory().isConstructor(encodedMethod.method)) {
-      // TODO(b/72211928)
+      accessFlags.unsetPrivate();
+      accessFlags.setPublic();
       return false;
     }
 
@@ -166,9 +124,8 @@
         return false;
       }
 
-      MethodPool methodPool = methodPools.get(holder);
-      Wrapper<DexMethod> key = equivalence.wrap(encodedMethod.method);
-      if (methodPool.hasSeen(key)) {
+      boolean wasSeen = methodPoolCollection.markIfNotSeen(holder, encodedMethod.method);
+      if (wasSeen) {
         // We can't do anything further because even renaming is not allowed due to the keep rule.
         if (rootSet.noObfuscation.contains(encodedMethod)) {
           return false;
@@ -176,7 +133,6 @@
         // TODO(b/111118390): Renaming will enable more private instance methods to be publicized.
         return false;
       }
-      methodPool.seen(key);
       lenseBuilder.add(encodedMethod.method);
       accessFlags.unsetPrivate();
       accessFlags.setFinal();
@@ -196,51 +152,4 @@
     accessFlags.setPublic();
     return false;
   }
-
-  // Per-class collection of method signatures, which will be used to determine if a certain method
-  // can be publicized or not.
-  static class MethodPool {
-    private MethodPool superType;
-    private final Set<MethodPool> interfaces = new HashSet<>();
-    private final Set<MethodPool> subTypes = new HashSet<>();
-    private final Set<Wrapper<DexMethod>> methodPool = new HashSet<>();
-
-    MethodPool() {
-    }
-
-    synchronized void linkSupertype(MethodPool superType) {
-      assert this.superType == null;
-      this.superType = superType;
-    }
-
-    synchronized void linkSubtype(MethodPool subType) {
-      boolean added = subTypes.add(subType);
-      assert added;
-    }
-
-    synchronized void linkInterface(MethodPool itf) {
-      boolean added = interfaces.add(itf);
-      assert added;
-    }
-
-    synchronized void seen(Wrapper<DexMethod> method) {
-      boolean added = methodPool.add(method);
-      assert added;
-    }
-
-    boolean hasSeen(Wrapper<DexMethod> method) {
-      return hasSeenUpwardRecursive(method) || hasSeenDownwardRecursive(method);
-    }
-
-    private boolean hasSeenUpwardRecursive(Wrapper<DexMethod> method) {
-      return methodPool.contains(method)
-          || (superType != null && superType.hasSeenUpwardRecursive(method))
-          || interfaces.stream().anyMatch(itf -> itf.hasSeenUpwardRecursive(method));
-    }
-
-    private boolean hasSeenDownwardRecursive(Wrapper<DexMethod> method) {
-      return methodPool.contains(method)
-          || subTypes.stream().anyMatch(subType -> subType.hasSeenDownwardRecursive(method));
-    }
-  }
 }
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 fe098cc..c1d7403 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
@@ -208,14 +208,14 @@
     if (holder == null) {
       return false;
     }
-    Constraint classVisibility =
-        Constraint.deriveConstraint(context, holderType, holder.accessFlags, appInfo);
-    if (classVisibility == Constraint.NEVER) {
+    ConstraintWithTarget classVisibility =
+        ConstraintWithTarget.deriveConstraint(context, holderType, holder.accessFlags, appInfo);
+    if (classVisibility == ConstraintWithTarget.NEVER) {
       return false;
     }
-    Constraint fieldVisibility =
-        Constraint.deriveConstraint(context, holderType, field.accessFlags, appInfo);
-    return fieldVisibility != Constraint.NEVER;
+    ConstraintWithTarget fieldVisibility =
+        ConstraintWithTarget.deriveConstraint(context, holderType, field.accessFlags, appInfo);
+    return fieldVisibility != ConstraintWithTarget.NEVER;
   }
 
   private Map<DexField, Set<DexEncodedMethod>> mergeFieldAccessContexts(
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
index 4ee0cee..98a0da4 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
@@ -38,7 +38,8 @@
       Map<DexMethod, DexMethod> methodMap,
       Map<DexField, DexField> fieldMap,
       GraphLense previousLense) {
-    super(ImmutableMap.of(), methodMap, fieldMap, previousLense, appInfo.dexItemFactory);
+    super(
+        ImmutableMap.of(), methodMap, fieldMap, null, null, previousLense, appInfo.dexItemFactory);
     this.appInfo = appInfo;
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
index 7a9c08f..ad7effd 100644
--- a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
@@ -25,6 +25,8 @@
         ImmutableMap.of(),
         ImmutableMap.of(),
         ImmutableMap.of(),
+        null,
+        null,
         appView.getGraphLense(),
         appView.getAppInfo().dexItemFactory);
     this.appView = appView;
diff --git a/src/main/java/com/android/tools/r8/position/TextPosition.java b/src/main/java/com/android/tools/r8/position/TextPosition.java
index 0dd4090..08d13bc 100644
--- a/src/main/java/com/android/tools/r8/position/TextPosition.java
+++ b/src/main/java/com/android/tools/r8/position/TextPosition.java
@@ -54,12 +54,12 @@
 
   @Override
   public String toString() {
-    return "Offset: " + offset + ", Line: " + line + ", column: " + column;
+    return "offset: " + offset + ", line: " + line + ", column: " + column;
   }
 
   @Override
   public String getDescription() {
-    return "Line: " + line + (column != UNKNOWN_COLUMN ? ", column: " + column: "");
+    return "line " + line + (column != UNKNOWN_COLUMN ? (", column " + column) : "");
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 6a12b60..27d3d3b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -5,8 +5,6 @@
 
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentiferNameString;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
-import static com.android.tools.r8.naming.IdentifierNameStringUtils.warnUndeterminedIdentifierIfNecessary;
-import static com.android.tools.r8.shaking.ProguardConfigurationUtils.buildIdentifierNameStringRule;
 
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.dex.IndexedItemCollection;
@@ -58,6 +56,8 @@
 import com.google.common.collect.Sets;
 import com.google.common.collect.Sets.SetView;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
+import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import java.util.ArrayDeque;
 import java.util.Collection;
@@ -94,6 +94,7 @@
   private boolean tracingMainDex = false;
 
   private final AppInfoWithSubtyping appInfo;
+  private final GraphLense graphLense;
   private final InternalOptions options;
   private RootSet rootSet;
 
@@ -186,10 +187,9 @@
   private final Queue<Action> proguardCompatibilityWorkList = Queues.newArrayDeque();
 
   /**
-   * A set of methods that need code inspection for Proguard compatibility rules.
+   * A set of methods that need code inspection for Java reflection in use.
    */
-  private final Set<DexEncodedMethod> pendingProguardReflectiveCompatibility =
-      Sets.newLinkedHashSet();
+  private final Set<DexEncodedMethod> pendingReflectiveUses = Sets.newLinkedHashSet();
 
   /**
    * A cache for DexMethod that have been marked reachable.
@@ -218,15 +218,23 @@
    */
   private final ProguardConfiguration.Builder compatibility;
 
-  public Enqueuer(AppInfoWithSubtyping appInfo, InternalOptions options,
+  public Enqueuer(
+      AppInfoWithSubtyping appInfo,
+      GraphLense graphLense,
+      InternalOptions options,
       boolean forceProguardCompatibility) {
-    this(appInfo, options, forceProguardCompatibility, null, null);
+    this(appInfo, graphLense, options, forceProguardCompatibility, null, null);
   }
 
-  public Enqueuer(AppInfoWithSubtyping appInfo, InternalOptions options,
+  public Enqueuer(
+      AppInfoWithSubtyping appInfo,
+      GraphLense graphLense,
+      InternalOptions options,
       boolean forceProguardCompatibility,
-      ProguardConfiguration.Builder compatibility, ProtoLiteExtension protoLiteExtension) {
+      ProguardConfiguration.Builder compatibility,
+      ProtoLiteExtension protoLiteExtension) {
     this.appInfo = appInfo;
+    this.graphLense = graphLense;
     this.compatibility = compatibility;
     this.options = options;
     this.protoLiteExtension = protoLiteExtension;
@@ -305,13 +313,10 @@
 
     boolean registerInvokeVirtual(DexMethod method, KeepReason keepReason) {
       if (appInfo.dexItemFactory.classMethods.isReflectiveMemberLookup(method)) {
-        if (forceProguardCompatibility) {
-          // TODO(b/76181966): whether or not add this rule in normal mode.
-          if (identifierNameStrings.add(method) && compatibility != null) {
-            compatibility.addRule(buildIdentifierNameStringRule(method));
-          }
-          pendingProguardReflectiveCompatibility.add(currentMethod);
-        }
+        // Implicitly add -identifiernamestring rule for the Java reflection in use.
+        identifierNameStrings.add(method);
+        // Revisit the current method to implicitly add -keep rule for items with reflective access.
+        pendingReflectiveUses.add(currentMethod);
       }
       if (!registerItemWithTarget(virtualInvokes, method)) {
         return false;
@@ -347,13 +352,10 @@
     boolean registerInvokeStatic(DexMethod method, KeepReason keepReason) {
       if (method == appInfo.dexItemFactory.classMethods.forName
           || appInfo.dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(method)) {
-        if (forceProguardCompatibility) {
-          // TODO(b/76181966): whether or not add this rule in normal mode.
-          if (identifierNameStrings.add(method) && compatibility != null) {
-            compatibility.addRule(buildIdentifierNameStringRule(method));
-          }
-          pendingProguardReflectiveCompatibility.add(currentMethod);
-        }
+        // Implicitly add -identifiernamestring rule for the Java reflection in use.
+        identifierNameStrings.add(method);
+        // Revisit the current method to implicitly add -keep rule for items with reflective access.
+        pendingReflectiveUses.add(currentMethod);
       }
       if (!registerItemWithTarget(staticInvokes, method)) {
         return false;
@@ -1270,15 +1272,15 @@
         }
 
         // Continue fix-point processing while there are additional work items to ensure
-        // Proguard compatibility.
+        // items that are passed to Java reflections are traced.
         if (proguardCompatibilityWorkList.isEmpty()
-            && pendingProguardReflectiveCompatibility.isEmpty()) {
+            && pendingReflectiveUses.isEmpty()) {
           break;
         }
-        pendingProguardReflectiveCompatibility.forEach(this::handleProguardReflectiveBehavior);
+        pendingReflectiveUses.forEach(this::handleReflectiveBehavior);
         workList.addAll(proguardCompatibilityWorkList);
         proguardCompatibilityWorkList.clear();
-        pendingProguardReflectiveCompatibility.clear();
+        pendingReflectiveUses.clear();
       }
       if (Log.ENABLED) {
         Set<DexEncodedMethod> allLive = Sets.newIdentityHashSet();
@@ -1475,15 +1477,15 @@
         Action.markMethodLive(method, KeepReason.dueToProguardCompatibilityKeepRule(rule)));
   }
 
-  private void handleProguardReflectiveBehavior(DexEncodedMethod method) {
+  private void handleReflectiveBehavior(DexEncodedMethod method) {
     DexType originHolder = method.method.holder;
     Origin origin = appInfo.originFor(originHolder);
-    IRCode code = method.buildIR(appInfo, options, origin);
+    IRCode code = method.buildIR(appInfo, graphLense, options, origin);
     code.instructionIterator().forEachRemaining(instr ->
-        handleProguardReflectiveBehavior(instr, originHolder));
+        handleReflectiveBehavior(instr, originHolder));
   }
 
-  private void handleProguardReflectiveBehavior(Instruction instruction, DexType originHolder) {
+  private void handleReflectiveBehavior(Instruction instruction, DexType originHolder) {
     if (!instruction.isInvokeMethod()) {
       return;
     }
@@ -1494,8 +1496,6 @@
     }
     DexItemBasedString itemBasedString = identifyIdentiferNameString(appInfo, invoke);
     if (itemBasedString == null) {
-      warnUndeterminedIdentifierIfNecessary(
-          appInfo, options, invokedMethod, originHolder, instruction, null);
       return;
     }
     if (itemBasedString.basedOn instanceof DexType) {
@@ -1672,19 +1672,21 @@
     /**
      * All methods that should be inlined if possible due to a configuration directive.
      */
-    public final Set<DexItem> alwaysInline;
+    public final Set<DexMethod> alwaysInline;
     /**
      * All methods that *must* be inlined due to a configuration directive (testing only).
      */
-    public final Set<DexItem> forceInline;
+    public final Set<DexMethod> forceInline;
     /**
      * All methods that *must* never be inlined due to a configuration directive (testing only).
      */
-    public final Set<DexItem> neverInline;
+    public final Set<DexMethod> neverInline;
     /**
      * All items with -identifiernamestring rule.
+     * Bound boolean value indicates the rule is explicitly specified by users (<code>true</code>)
+     * or not, i.e., implicitly added by R8 (<code>false</code>).
      */
-    public final Set<DexItem> identifierNameStrings;
+    public final Object2BooleanMap<DexItem> identifierNameStrings;
     /**
      * Set of fields that have been identified as proto-lite fields by the
      * {@link ProtoLiteExtension}.
@@ -1741,8 +1743,8 @@
       this.alwaysInline = enqueuer.rootSet.alwaysInline;
       this.forceInline = enqueuer.rootSet.forceInline;
       this.neverInline = enqueuer.rootSet.neverInline;
-      this.identifierNameStrings =
-          Sets.union(enqueuer.rootSet.identifierNameStrings, enqueuer.identifierNameStrings);
+      this.identifierNameStrings = joinIdentifierNameStrings(
+          enqueuer.rootSet.identifierNameStrings, enqueuer.identifierNameStrings);
       this.protoLiteFields = enqueuer.protoLiteFields;
       this.prunedTypes = Collections.emptySet();
       this.switchMaps = Collections.emptyMap();
@@ -1825,10 +1827,12 @@
       this.noSideEffects = previous.noSideEffects;
       assert lense.assertNotModified(previous.assumedValues.keySet());
       this.assumedValues = previous.assumedValues;
-      assert lense.assertNotModified(previous.alwaysInline);
+      assert lense.assertNotModified(
+          previous.alwaysInline.stream().map(this::definitionFor).filter(Objects::nonNull)
+              .collect(Collectors.toList()));
       this.alwaysInline = previous.alwaysInline;
-      this.forceInline = previous.forceInline;
-      this.neverInline = previous.neverInline;
+      this.forceInline = rewriteMethodsWithRenamedSignature(previous.forceInline, lense);
+      this.neverInline = rewriteMethodsWithRenamedSignature(previous.neverInline, lense);
       this.identifierNameStrings =
           rewriteMixedItemsConservatively(previous.identifierNameStrings, lense);
       // Switchmap classes should never be affected by renaming.
@@ -1913,6 +1917,18 @@
           .collect(ImmutableSortedSet.toImmutableSortedSet(PresortedComparable::slowCompare));
     }
 
+    private Object2BooleanMap<DexItem> joinIdentifierNameStrings(
+        Set<DexItem> explicit, Set<DexItem> implicit) {
+      Object2BooleanMap<DexItem> result = new Object2BooleanArrayMap<>();
+      for (DexItem e : explicit) {
+        result.putIfAbsent(e, true);
+      }
+      for (DexItem i : implicit) {
+        result.putIfAbsent(i, false);
+      }
+      return result;
+    }
+
     private <T extends PresortedComparable<T>> SortedSet<T> toSortedDescriptorSet(
         Set<? extends KeyedDexItem<T>> set) {
       ImmutableSortedSet.Builder<T> builder =
@@ -1940,6 +1956,16 @@
       return builder.build();
     }
 
+    private static ImmutableSortedSet<DexMethod> rewriteMethodsWithRenamedSignature(
+        Set<DexMethod> methods, GraphLense lense) {
+      ImmutableSortedSet.Builder<DexMethod> builder =
+          new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare);
+      for (DexMethod method : methods) {
+        builder.add(lense.getRenamedMethodSignature(method));
+      }
+      return builder.build();
+    }
+
     private static ImmutableSortedSet<DexMethod> rewriteMethodsConservatively(
         Set<DexMethod> original, GraphLense lense) {
       ImmutableSortedSet.Builder<DexMethod> builder =
@@ -2022,6 +2048,32 @@
       return builder.build();
     }
 
+    private static Object2BooleanMap<DexItem> rewriteMixedItemsConservatively(
+        Object2BooleanMap<DexItem> original, GraphLense lense) {
+      Object2BooleanMap<DexItem> result = new Object2BooleanArrayMap<>();
+      for (Object2BooleanMap.Entry<DexItem> entry : original.object2BooleanEntrySet()) {
+        DexItem item = entry.getKey();
+        // TODO(b/67934123) There should be a common interface to perform the dispatch.
+        if (item instanceof DexType) {
+          result.put(lense.lookupType((DexType) item), entry.getBooleanValue());
+        } else if (item instanceof DexMethod) {
+          DexMethod method = (DexMethod) item;
+          if (lense.isContextFreeForMethod(method)) {
+            result.put(lense.lookupMethod(method), entry.getBooleanValue());
+          } else {
+            for (DexMethod candidate: lense.lookupMethodInAllContexts(method)) {
+              result.put(candidate, entry.getBooleanValue());
+            }
+          }
+        } else if (item instanceof DexField) {
+          result.put(lense.lookupField((DexField) item), entry.getBooleanValue());
+        } else {
+          throw new Unreachable();
+        }
+      }
+      return result;
+    }
+
     private static <T> Set<T> mergeSets(Collection<T> first, Collection<T> second) {
       ImmutableSet.Builder<T> builder = ImmutableSet.builder();
       builder.addAll(first);
diff --git a/src/main/java/com/android/tools/r8/shaking/InlineRule.java b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
index 565856c..f263bd6 100644
--- a/src/main/java/com/android/tools/r8/shaking/InlineRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
@@ -4,6 +4,8 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import java.util.List;
 
 public class InlineRule extends ProguardConfigurationRule {
@@ -12,20 +14,27 @@
     ALWAYS, FORCE, NEVER
   }
 
-  public static class Builder extends ProguardConfigurationRule.Builder {
+  public static class Builder extends ProguardConfigurationRule.Builder<InlineRule, Builder> {
 
     private Builder() {
+      super();
     }
 
     Type type;
 
+    @Override
+    public Builder self() {
+      return this;
+    }
+
     public Builder setType(Type type) {
       this.type = type;
       return this;
     }
 
+    @Override
     public InlineRule build() {
-      return new InlineRule(classAnnotation, classAccessFlags,
+      return new InlineRule(origin, getPosition(), source, classAnnotation, classAccessFlags,
           negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
           inheritanceClassName, inheritanceIsExtends, memberRules, type);
     }
@@ -34,6 +43,9 @@
   private final Type type;
 
   private InlineRule(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -45,13 +57,14 @@
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules,
       Type type) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules);
     this.type = type;
   }
 
-  public static InlineRule.Builder builder() {
-    return new InlineRule.Builder();
+  public static Builder builder() {
+    return new Builder();
   }
 
   public Type getType() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java
index 39d0335..4c1f9ef 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java
@@ -3,22 +3,36 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import java.util.List;
 
 public class ProguardAssumeNoSideEffectRule extends ProguardConfigurationRule {
 
-  public static class Builder extends ProguardClassSpecification.Builder {
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<ProguardAssumeNoSideEffectRule, Builder> {
 
-    private Builder() {}
+    private Builder() {
+      super();
+    }
 
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
     public ProguardAssumeNoSideEffectRule build() {
-      return new ProguardAssumeNoSideEffectRule(classAnnotation, classAccessFlags,
-          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
-          inheritanceClassName, inheritanceIsExtends, memberRules);
+      return new ProguardAssumeNoSideEffectRule(origin, getPosition(), source, classAnnotation,
+          classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
+          inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
     }
   }
 
   private ProguardAssumeNoSideEffectRule(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -29,8 +43,9 @@
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java
index 0dafb11..8b8f4dd 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java
@@ -3,21 +3,36 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import java.util.List;
 
 public class ProguardAssumeValuesRule extends ProguardConfigurationRule {
-  public static class Builder extends ProguardClassSpecification.Builder {
 
-    private Builder() {}
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<ProguardAssumeValuesRule, Builder> {
 
+    private Builder() {
+      super();
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
     public ProguardAssumeValuesRule build() {
-      return new ProguardAssumeValuesRule(classAnnotation, classAccessFlags, negatedClassAccessFlags,
-          classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
-          inheritanceIsExtends, memberRules);
+      return new ProguardAssumeValuesRule(origin, getPosition(), source, classAnnotation,
+          classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
+          inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
     }
   }
 
   private ProguardAssumeValuesRule(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -28,8 +43,9 @@
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
index 2461732..664cb25 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
@@ -3,23 +3,36 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import java.util.List;
 
 public class ProguardCheckDiscardRule extends ProguardConfigurationRule {
 
-  public static class Builder extends ProguardConfigurationRule.Builder {
+  public static class Builder extends
+      ProguardConfigurationRule.Builder<ProguardCheckDiscardRule, Builder> {
 
     private Builder() {
+      super();
     }
 
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
     public ProguardCheckDiscardRule build() {
-      return new ProguardCheckDiscardRule(classAnnotation, classAccessFlags,
-          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
-          inheritanceClassName, inheritanceIsExtends, memberRules);
+      return new ProguardCheckDiscardRule(origin, getPosition(), source, classAnnotation,
+          classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
+          inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
     }
   }
 
   private ProguardCheckDiscardRule(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -30,8 +43,9 @@
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules);
   }
 
   public static Builder builder() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
index 8de361e..5d610a8 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
@@ -3,6 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.position.TextPosition;
+import com.android.tools.r8.position.TextRange;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.LinkedList;
@@ -11,8 +15,13 @@
 
 public abstract class ProguardClassSpecification {
 
-  public static class Builder {
+  public abstract static class
+  Builder<C extends ProguardClassSpecification, B extends Builder<C, B>> {
 
+    protected Origin origin;
+    protected Position start;
+    protected Position end;
+    protected String source;
     protected ProguardTypeMatcher classAnnotation;
     protected ProguardAccessFlags classAccessFlags = new ProguardAccessFlags();
     protected ProguardAccessFlags negatedClassAccessFlags = new ProguardAccessFlags();
@@ -25,6 +34,46 @@
     protected List<ProguardMemberRule> memberRules = new LinkedList<>();
 
     protected Builder() {
+      this(Origin.unknown(), Position.UNKNOWN);
+    }
+
+    protected Builder(Origin origin, Position start) {
+      this.origin = origin;
+      this.start = start;
+    }
+
+    public abstract C build();
+
+    public abstract B self();
+
+    public B setOrigin(Origin origin) {
+      this.origin = origin;
+      return self();
+    }
+
+    public B setStart(Position start) {
+      this.start = start;
+      return self();
+    }
+
+    public B setEnd(Position end) {
+      this.end = end;
+      return self();
+    }
+
+    public B setSource(String source) {
+      this.source = source;
+      return self();
+    }
+
+    public Position getPosition() {
+      if (start == null) {
+        return Position.UNKNOWN;
+      }
+      if (end == null || !((start instanceof TextPosition) && (end instanceof TextPosition))) {
+        return start;
+      }
+      return new TextRange((TextPosition) start, (TextPosition) end);
     }
 
     public List<ProguardMemberRule> getMemberRules() {
@@ -117,6 +166,9 @@
     }
   }
 
+  private final Origin origin;
+  private final Position position;
+  private final String source;
   private final ProguardTypeMatcher classAnnotation;
   private final ProguardAccessFlags classAccessFlags;
   private final ProguardAccessFlags negatedClassAccessFlags;
@@ -129,6 +181,9 @@
   private final List<ProguardMemberRule> memberRules;
 
   protected ProguardClassSpecification(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -139,6 +194,9 @@
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
+    this.origin = origin;
+    this.position = position;
+    this.source =source;
     this.classAnnotation = classAnnotation;
     this.classAccessFlags = classAccessFlags;
     this.negatedClassAccessFlags = negatedClassAccessFlags;
@@ -152,6 +210,18 @@
     this.memberRules = memberRules;
   }
 
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  public Position getPosition() {
+    return position;
+  }
+
+  public String getSource() {
+    return source;
+  }
+
   public List<ProguardMemberRule> getMemberRules() {
     return memberRules;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 54b90b2..599d444 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -55,9 +55,10 @@
     private Origin keepParameterNamesOptionOrigin;
     private Position keepParameterNamesOptionPosition;
     private final ProguardClassFilter.Builder adaptClassStrings = ProguardClassFilter.builder();
-    private final ProguardPathFilter.Builder adaptResourceFilenames = ProguardPathFilter.builder();
-    private final ProguardPathFilter.Builder adaptResourceFilecontents =
-        ProguardPathFilter.builder();
+    private final ProguardPathFilter.Builder adaptResourceFilenames =
+        ProguardPathFilter.builder().disable();
+    private final ProguardPathFilter.Builder adaptResourceFileContents =
+        ProguardPathFilter.builder().disable();
     private final ProguardPathFilter.Builder keepDirectories = ProguardPathFilter.builder();
     private boolean forceProguardCompatibility = false;
     private boolean overloadAggressively;
@@ -231,12 +232,20 @@
       adaptClassStrings.addPattern(pattern);
     }
 
+    public void enableAdaptResourceFilenames() {
+      adaptResourceFilenames.enable();
+    }
+
     public void addAdaptResourceFilenames(ProguardPathList pattern) {
       adaptResourceFilenames.addPattern(pattern);
     }
 
-    public void addAdaptResourceFilecontents(ProguardPathList pattern) {
-      adaptResourceFilecontents.addPattern(pattern);
+    public void enableAdaptResourceFileContents() {
+      adaptResourceFileContents.enable();
+    }
+
+    public void addAdaptResourceFileContents(ProguardPathList pattern) {
+      adaptResourceFileContents.addPattern(pattern);
     }
 
     public void addKeepDirectories(ProguardPathList pattern) {
@@ -288,7 +297,7 @@
           keepParameterNames,
           adaptClassStrings.build(),
           adaptResourceFilenames.build(),
-          adaptResourceFilecontents.build(),
+          adaptResourceFileContents.build(),
           keepDirectories.build());
 
       reporter.failIfPendingErrors();
@@ -357,7 +366,7 @@
   private final boolean keepParameterNames;
   private final ProguardClassFilter adaptClassStrings;
   private final ProguardPathFilter adaptResourceFilenames;
-  private final ProguardPathFilter adaptResourceFilecontents;
+  private final ProguardPathFilter adaptResourceFileContents;
   private final ProguardPathFilter keepDirectories;
 
   private ProguardConfiguration(
@@ -395,7 +404,7 @@
       boolean keepParameterNames,
       ProguardClassFilter adaptClassStrings,
       ProguardPathFilter adaptResourceFilenames,
-      ProguardPathFilter adaptResourceFilecontents,
+      ProguardPathFilter adaptResourceFileContents,
       ProguardPathFilter keepDirectories) {
     this.parsedConfiguration = parsedConfiguration;
     this.dexItemFactory = factory;
@@ -431,7 +440,7 @@
     this.keepParameterNames = keepParameterNames;
     this.adaptClassStrings = adaptClassStrings;
     this.adaptResourceFilenames = adaptResourceFilenames;
-    this.adaptResourceFilecontents = adaptResourceFilecontents;
+    this.adaptResourceFileContents = adaptResourceFileContents;
     this.keepDirectories = keepDirectories;
   }
 
@@ -575,8 +584,8 @@
     return adaptResourceFilenames;
   }
 
-  public ProguardPathFilter getAdaptResourceFilecontents() {
-    return adaptResourceFilecontents;
+  public ProguardPathFilter getAdaptResourceFileContents() {
+    return adaptResourceFileContents;
   }
 
   public ProguardPathFilter getKeepDirectories() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 5b22456..d1a0b3d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -228,7 +228,7 @@
       } else if (acceptString("keepparameternames")) {
         configurationBuilder.setKeepParameterNames(true, origin, getPosition(optionStart));
       } else if (acceptString("checkdiscard")) {
-        ProguardCheckDiscardRule rule = parseCheckDiscardRule();
+        ProguardCheckDiscardRule rule = parseCheckDiscardRule(optionStart);
         configurationBuilder.addRule(rule);
       } else if (acceptString("keepdirectories")) {
         // TODO(74279367): Report an error until it's fully supported.
@@ -237,10 +237,10 @@
         }
         parsePathFilter(configurationBuilder::addKeepDirectories);
       } else if (acceptString("keep")) {
-        ProguardKeepRule rule = parseKeepRule();
+        ProguardKeepRule rule = parseKeepRule(optionStart);
         configurationBuilder.addRule(rule);
       } else if (acceptString("whyareyoukeeping")) {
-        ProguardWhyAreYouKeepingRule rule = parseWhyAreYouKeepingRule();
+        ProguardWhyAreYouKeepingRule rule = parseWhyAreYouKeepingRule(optionStart);
         configurationBuilder.addRule(rule);
       } else if (acceptString("dontoptimize")) {
         configurationBuilder.disableOptimization();
@@ -316,10 +316,10 @@
       } else if (acceptString("applymapping")) {
         configurationBuilder.setApplyMappingFile(parseFileName());
       } else if (acceptString("assumenosideeffects")) {
-        ProguardAssumeNoSideEffectRule rule = parseAssumeNoSideEffectsRule();
+        ProguardAssumeNoSideEffectRule rule = parseAssumeNoSideEffectsRule(optionStart);
         configurationBuilder.addRule(rule);
       } else if (acceptString("assumevalues")) {
-        ProguardAssumeValuesRule rule = parseAssumeValuesRule();
+        ProguardAssumeValuesRule rule = parseAssumeValuesRule(optionStart);
         configurationBuilder.addRule(rule);
       } else if (acceptString("include")) {
         // Collect the parsed configuration until the include.
@@ -348,35 +348,29 @@
       } else if (acceptString("packageobfuscationdictionary")) {
         configurationBuilder.setPackageObfuscationDictionary(parseFileName());
       } else if (acceptString("alwaysinline")) {
-        InlineRule rule = parseInlineRule(Type.ALWAYS);
+        InlineRule rule = parseInlineRule(Type.ALWAYS, optionStart);
         configurationBuilder.addRule(rule);
       } else if (allowTestOptions && acceptString("forceinline")) {
-        InlineRule rule = parseInlineRule(Type.FORCE);
+        InlineRule rule = parseInlineRule(Type.FORCE, optionStart);
         configurationBuilder.addRule(rule);
         // Insert a matching -checkdiscard rule to ensure force inlining happens.
         ProguardCheckDiscardRule ruled = rule.asProguardCheckDiscardRule();
         configurationBuilder.addRule(ruled);
       } else if (allowTestOptions && acceptString("neverinline")) {
-        InlineRule rule = parseInlineRule(Type.NEVER);
+        InlineRule rule = parseInlineRule(Type.NEVER, optionStart);
         configurationBuilder.addRule(rule);
       } else if (acceptString("useuniqueclassmembernames")) {
         configurationBuilder.setUseUniqueClassMemberNames(true);
       } else if (acceptString("adaptclassstrings")) {
         parseClassFilter(configurationBuilder::addAdaptClassStringsPattern);
       } else if (acceptString("adaptresourcefilenames")) {
-        // TODO(76377381): Report an error until it's fully supported.
-        if (failOnPartiallyImplementedOptions) {
-          failPartiallyImplementedOption("-adaptresourcefilenames", optionStart);
-        }
+        configurationBuilder.enableAdaptResourceFilenames();
         parsePathFilter(configurationBuilder::addAdaptResourceFilenames);
       } else if (acceptString("adaptresourcefilecontents")) {
-        // TODO(36847655): Report an error until it's fully supported.
-        if (failOnPartiallyImplementedOptions) {
-          failPartiallyImplementedOption("-adaptresourcefilecontents", optionStart);
-        }
-        parsePathFilter(configurationBuilder::addAdaptResourceFilecontents);
+        configurationBuilder.enableAdaptResourceFileContents();
+        parsePathFilter(configurationBuilder::addAdaptResourceFileContents);
       } else if (acceptString("identifiernamestring")) {
-        configurationBuilder.addRule(parseIdentifierNameStringRule());
+        configurationBuilder.addRule(parseIdentifierNameStringRule(optionStart));
       } else if (acceptString("if")) {
         configurationBuilder.addRule(parseIfRule(optionStart));
       } else {
@@ -545,9 +539,11 @@
       }
     }
 
-    private ProguardKeepRule parseKeepRule()
+    private ProguardKeepRule parseKeepRule(Position start)
         throws ProguardRuleParserException {
-      ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder();
+      ProguardKeepRule.Builder keepRuleBuilder = ProguardKeepRule.builder()
+          .setOrigin(origin)
+          .setStart(start);
       parseRuleTypeAndModifiers(keepRuleBuilder);
       parseClassSpec(keepRuleBuilder, false);
       if (keepRuleBuilder.getMemberRules().isEmpty()) {
@@ -560,13 +556,21 @@
         defaultRuleBuilder.setArguments(Collections.emptyList());
         keepRuleBuilder.getMemberRules().add(defaultRuleBuilder.build());
       }
+      Position end = getPosition();
+      keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+      keepRuleBuilder.setEnd(end);
       return keepRuleBuilder.build();
     }
 
-    private ProguardWhyAreYouKeepingRule parseWhyAreYouKeepingRule()
+    private ProguardWhyAreYouKeepingRule parseWhyAreYouKeepingRule(Position start)
         throws ProguardRuleParserException {
-      ProguardWhyAreYouKeepingRule.Builder keepRuleBuilder = ProguardWhyAreYouKeepingRule.builder();
+      ProguardWhyAreYouKeepingRule.Builder keepRuleBuilder = ProguardWhyAreYouKeepingRule.builder()
+          .setOrigin(origin)
+          .setStart(start);
       parseClassSpec(keepRuleBuilder, false);
+      Position end = getPosition();
+      keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+      keepRuleBuilder.setEnd(end);
       return keepRuleBuilder.build();
     }
 
@@ -577,37 +581,56 @@
       return keepRuleBuilder.build();
     }
 
-    private ProguardCheckDiscardRule parseCheckDiscardRule()
+    private ProguardCheckDiscardRule parseCheckDiscardRule(Position start)
         throws ProguardRuleParserException {
-      ProguardCheckDiscardRule.Builder keepRuleBuilder = ProguardCheckDiscardRule.builder();
+      ProguardCheckDiscardRule.Builder keepRuleBuilder = ProguardCheckDiscardRule.builder()
+          .setOrigin(origin)
+          .setStart(start);
       parseClassSpec(keepRuleBuilder, false);
+      Position end = getPosition();
+      keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+      keepRuleBuilder.setEnd(end);
       return keepRuleBuilder.build();
     }
 
-    private InlineRule parseInlineRule(InlineRule.Type type)
+    private InlineRule parseInlineRule(InlineRule.Type type, Position start)
         throws ProguardRuleParserException {
-      InlineRule.Builder keepRuleBuilder = InlineRule.builder().setType(type);
+      InlineRule.Builder keepRuleBuilder = InlineRule.builder()
+          .setOrigin(origin)
+          .setStart(start)
+          .setType(type);
       parseClassSpec(keepRuleBuilder, false);
+      Position end = getPosition();
+      keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+      keepRuleBuilder.setEnd(end);
       return keepRuleBuilder.build();
     }
 
-    private ProguardIdentifierNameStringRule parseIdentifierNameStringRule()
+    private ProguardIdentifierNameStringRule parseIdentifierNameStringRule(Position start)
         throws ProguardRuleParserException {
       ProguardIdentifierNameStringRule.Builder keepRuleBuilder =
-          ProguardIdentifierNameStringRule.builder();
+          ProguardIdentifierNameStringRule.builder()
+              .setOrigin(origin)
+              .setStart(start);
       parseClassSpec(keepRuleBuilder, false);
+      Position end = getPosition();
+      keepRuleBuilder.setSource(getSourceSnippet(contents, start, end));
+      keepRuleBuilder.setEnd(end);
       return keepRuleBuilder.build();
     }
 
     private ProguardIfRule parseIfRule(TextPosition optionStart)
         throws ProguardRuleParserException {
-      ProguardIfRule.Builder ifRuleBuilder = ProguardIfRule.builder();
+      ProguardIfRule.Builder ifRuleBuilder = ProguardIfRule.builder()
+          .setOrigin(origin)
+          .setStart(optionStart);
       parseClassSpec(ifRuleBuilder, false);
 
       // Required a subsequent keep rule.
       skipWhitespace();
+      Position keepStart = getPosition();
       if (acceptString("-keep")) {
-        ProguardKeepRule subsequentRule = parseKeepRule();
+        ProguardKeepRule subsequentRule = parseKeepRule(keepStart);
         ifRuleBuilder.setSubsequentRule(subsequentRule);
         ProguardIfRule ifRule = ifRuleBuilder.build();
         verifyAndLinkBackReferences(ifRule.getWildcards());
@@ -636,8 +659,11 @@
       }
     }
 
-    private void parseClassSpec(
-        ProguardConfigurationRule.Builder builder, boolean allowValueSpecification)
+    private
+    <C extends ProguardClassSpecification, B extends ProguardClassSpecification.Builder<C, B>>
+    void parseClassSpec(
+        ProguardClassSpecification.Builder<C, B> builder,
+        boolean allowValueSpecification)
         throws ProguardRuleParserException {
       parseClassFlagsAndAnnotations(builder);
       parseClassType(builder);
@@ -646,8 +672,7 @@
       parseMemberRules(builder, allowValueSpecification);
     }
 
-    private void parseRuleTypeAndModifiers(ProguardKeepRule.Builder builder)
-        throws ProguardRuleParserException {
+    private void parseRuleTypeAndModifiers(ProguardKeepRule.Builder builder) {
       if (acceptString("names")) {
         builder.setType(ProguardKeepRuleType.KEEP);
         builder.getModifiersBuilder().setAllowsShrinking(true);
@@ -800,12 +825,15 @@
           ClassOrType.CLASS, dexItemFactory));
     }
 
-    private void parseMemberRules(ProguardClassSpecification.Builder classSpecificationBuilder,
+    private
+    <C extends ProguardClassSpecification, B extends ProguardClassSpecification.Builder<C, B>>
+    void parseMemberRules(
+        ProguardClassSpecification.Builder<C, B> classSpecificationBuilder,
         boolean allowValueSpecification)
         throws ProguardRuleParserException {
       skipWhitespace();
       if (!eof() && acceptChar('{')) {
-        ProguardMemberRule rule = null;
+        ProguardMemberRule rule;
         while ((rule = parseMemberRule(allowValueSpecification)) != null) {
           classSpecificationBuilder.getMemberRules().add(rule);
         }
@@ -1086,16 +1114,27 @@
       return fileFilter;
     }
 
-    private ProguardAssumeNoSideEffectRule parseAssumeNoSideEffectsRule()
+    private ProguardAssumeNoSideEffectRule parseAssumeNoSideEffectsRule(Position start)
         throws ProguardRuleParserException {
-      ProguardAssumeNoSideEffectRule.Builder builder = ProguardAssumeNoSideEffectRule.builder();
+      ProguardAssumeNoSideEffectRule.Builder builder = ProguardAssumeNoSideEffectRule.builder()
+          .setOrigin(origin)
+          .setStart(start);
       parseClassSpec(builder, true);
+      Position end = getPosition();
+      builder.setSource(getSourceSnippet(contents, start, end));
+      builder.setEnd(end);
       return builder.build();
     }
 
-    private ProguardAssumeValuesRule parseAssumeValuesRule() throws ProguardRuleParserException {
-      ProguardAssumeValuesRule.Builder builder = ProguardAssumeValuesRule.builder();
+    private ProguardAssumeValuesRule parseAssumeValuesRule(Position start)
+        throws ProguardRuleParserException {
+      ProguardAssumeValuesRule.Builder builder = ProguardAssumeValuesRule.builder()
+          .setOrigin(origin)
+          .setStart(start);
       parseClassSpec(builder, true);
+      Position end = getPosition();
+      builder.setSource(getSourceSnippet(contents, start, end));
+      builder.setEnd(end);
       return builder.build();
     }
 
@@ -1223,6 +1262,7 @@
     private IdentifierPatternWithWildcards acceptIdentifierWithBackreference(IdentifierType kind) {
       ImmutableList.Builder<ProguardWildcard> wildcardsCollector = ImmutableList.builder();
       StringBuilder currentAsterisks = null;
+      int asteriskCount = 0;
       StringBuilder currentBackreference = null;
       skipWhitespace();
       int start = position;
@@ -1264,19 +1304,35 @@
           }
         } else if (currentAsterisks != null) {
           if (current == '*') {
+            // only '*', '**', and '***' are allowed.
+            // E.g., '****' should be regarded as two separate wildcards (e.g., '***' and '*')
+            if (asteriskCount >= 3) {
+              wildcardsCollector.add(new ProguardWildcard.Pattern(currentAsterisks.toString()));
+              currentAsterisks = new StringBuilder();
+              asteriskCount = 0;
+            }
             currentAsterisks.append((char) current);
+            asteriskCount++;
             end += Character.charCount(current);
             continue;
           } else {
             wildcardsCollector.add(new ProguardWildcard.Pattern(currentAsterisks.toString()));
             currentAsterisks = null;
+            asteriskCount = 0;
           }
         }
         // From now on, neither in asterisk collecting state nor back reference collecting state.
         assert currentAsterisks == null && currentBackreference == null;
         if (current == '*') {
-          currentAsterisks = new StringBuilder();
-          currentAsterisks.append((char) current);
+          if (kind == IdentifierType.CLASS_NAME) {
+            // '**' and '***' are only allowed in type name.
+            currentAsterisks = new StringBuilder();
+            currentAsterisks.append((char) current);
+            asteriskCount = 1;
+          } else {
+            // For member names, regard '**' or '***' as separate single-asterisk wildcards.
+            wildcardsCollector.add(new ProguardWildcard.Pattern(String.valueOf((char) current)));
+          }
           end += Character.charCount(current);
         } else if (current == '?' || current == '%') {
           wildcardsCollector.add(new ProguardWildcard.Pattern(String.valueOf((char) current)));
@@ -1549,7 +1605,24 @@
     private int getColumn() {
       return position - lineStartPosition + 1 /* column starts at 1 */;
     }
-  }
+
+    private String getSourceSnippet(String source, Position start, Position end) {
+      return start instanceof TextPosition && end instanceof TextPosition
+          ? getTextSourceSnippet(source, (TextPosition) start, (TextPosition) end)
+          : null;
+      }
+    }
+
+    private String getTextSourceSnippet(String source, TextPosition start, TextPosition end) {
+      long length = end.getOffset() - start.getOffset();
+      if (start.getOffset() < 0 || end.getOffset() < 0
+          || start.getOffset() >= source.length() || end.getOffset() > source.length()
+          || length <= 0) {
+        return null;
+      } else {
+        return source.substring((int) start.getOffset(), (int) end.getOffset());
+      }
+    }
 
   static class IdentifierPatternWithWildcards {
     final String pattern;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index e4c5691..f7282ff 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.Iterables;
 import java.util.Collections;
@@ -11,6 +13,9 @@
 
 public abstract class ProguardConfigurationRule extends ProguardClassSpecification {
   ProguardConfigurationRule(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -21,8 +26,9 @@
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules);
   }
 
   abstract String typeString();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
index 07ffd85..8ff7670 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
@@ -3,22 +3,35 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import java.util.List;
 
 public class ProguardIdentifierNameStringRule extends ProguardConfigurationRule {
 
-  public static class Builder extends ProguardConfigurationRule.Builder {
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<ProguardIdentifierNameStringRule, Builder> {
     private Builder() {
+      super();
     }
 
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
     public ProguardIdentifierNameStringRule build() {
-      return new ProguardIdentifierNameStringRule(classAnnotation, classAccessFlags,
-          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
-          inheritanceClassName, inheritanceIsExtends, memberRules);
+      return new ProguardIdentifierNameStringRule(origin, getPosition(), source, classAnnotation,
+          classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
+          inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
     }
   }
 
   private ProguardIdentifierNameStringRule(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -29,8 +42,9 @@
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules);
   }
 
   public static Builder builder() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index 47f26ed..54a3f2f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -3,18 +3,29 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import com.google.common.collect.Iterables;
 import java.util.List;
 import java.util.stream.Collectors;
 
-public class ProguardIfRule extends ProguardKeepRule {
+public class ProguardIfRule extends ProguardKeepRuleBase {
 
   final ProguardKeepRule subsequentRule;
 
-  public static class Builder extends ProguardKeepRule.Builder {
+  public static class Builder extends ProguardKeepRuleBase.Builder<ProguardIfRule, Builder> {
 
     ProguardKeepRule subsequentRule = null;
 
+    protected Builder() {
+      super();
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
     public void setSubsequentRule(ProguardKeepRule rule) {
       subsequentRule = rule;
     }
@@ -22,13 +33,17 @@
     @Override
     public ProguardIfRule build() {
       assert subsequentRule != null : "Option -if without a subsequent rule.";
-      return new ProguardIfRule(classAnnotation, classAccessFlags,
+      return new ProguardIfRule(origin, getPosition(), source, classAnnotation, classAccessFlags,
           negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
           inheritanceClassName, inheritanceIsExtends, memberRules, subsequentRule);
     }
   }
 
-  private ProguardIfRule(ProguardTypeMatcher classAnnotation,
+  private ProguardIfRule(
+      Origin origin,
+      Position position,
+      String source,
+      ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags, boolean classTypeNegated,
       ProguardClassType classType, ProguardClassNameList classNames,
@@ -36,8 +51,9 @@
       ProguardTypeMatcher inheritanceClassName, boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules,
       ProguardKeepRule subsequentRule) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules,
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules,
         ProguardKeepRuleType.CONDITIONAL, ProguardKeepRuleModifiers.builder().build());
     this.subsequentRule = subsequentRule;
   }
@@ -51,9 +67,11 @@
     return Iterables.concat(super.getWildcards(), subsequentRule.getWildcards());
   }
 
-  @Override
   protected ProguardIfRule materialize() {
     return new ProguardIfRule(
+        Origin.unknown(),
+        Position.UNKNOWN,
+        null,
         getClassAnnotation(),
         getClassAccessFlags(),
         getNegatedClassAccessFlags(),
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java
index 66fdd50..767ecf7 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepPackageNamesRule.java
@@ -3,23 +3,36 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import java.util.List;
 
 public class ProguardKeepPackageNamesRule extends ProguardConfigurationRule {
 
-  public static class Builder extends ProguardConfigurationRule.Builder {
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<ProguardKeepPackageNamesRule, Builder> {
 
     private Builder() {
+      super();
     }
 
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
     public ProguardKeepPackageNamesRule build() {
-      return new ProguardKeepPackageNamesRule(classAnnotation, classAccessFlags,
-          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
-          inheritanceClassName, inheritanceIsExtends, memberRules);
+      return new ProguardKeepPackageNamesRule(origin, getPosition(), source, classAnnotation,
+          classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
+          inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
     }
   }
 
   private ProguardKeepPackageNamesRule(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -30,12 +43,13 @@
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules);
   }
 
-  public static ProguardKeepPackageNamesRule.Builder builder() {
-    return new ProguardKeepPackageNamesRule.Builder();
+  public static Builder builder() {
+    return new Builder();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
index 6651dae..3c1fb2d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
@@ -3,39 +3,37 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
-public class ProguardKeepRule extends ProguardConfigurationRule {
+public class ProguardKeepRule extends ProguardKeepRuleBase {
 
-  public static class Builder extends ProguardClassSpecification.Builder {
+  public static class Builder extends ProguardKeepRuleBase.Builder<ProguardKeepRule, Builder> {
 
-    private ProguardKeepRuleType type;
-    private final ProguardKeepRuleModifiers.Builder modifiersBuilder =
-        ProguardKeepRuleModifiers.builder();
-
-    protected Builder() {}
-
-    public void setType(ProguardKeepRuleType type) {
-      this.type = type;
+    protected Builder() {
+      super();
     }
 
-    public ProguardKeepRuleModifiers.Builder getModifiersBuilder() {
-      return modifiersBuilder;
+    @Override
+    public Builder self() {
+      return this;
     }
 
+    @Override
     public ProguardKeepRule build() {
-      return new ProguardKeepRule(classAnnotation, classAccessFlags, negatedClassAccessFlags,
-          classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
-          inheritanceIsExtends, memberRules, type, modifiersBuilder.build());
+      return new ProguardKeepRule(origin, getPosition(), source, classAnnotation, classAccessFlags,
+          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
+          inheritanceClassName, inheritanceIsExtends, memberRules, type, modifiersBuilder.build());
     }
   }
 
-  private final ProguardKeepRuleType type;
-  private final ProguardKeepRuleModifiers modifiers;
-
   protected ProguardKeepRule(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -48,10 +46,9 @@
       List<ProguardMemberRule> memberRules,
       ProguardKeepRuleType type,
       ProguardKeepRuleModifiers modifiers) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
-    this.type = type;
-    this.modifiers = modifiers;
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules, type, modifiers);
   }
 
   /**
@@ -61,16 +58,11 @@
     return new Builder();
   }
 
-  public ProguardKeepRuleType getType() {
-    return type;
-  }
-
-  public ProguardKeepRuleModifiers getModifiers() {
-    return modifiers;
-  }
-
   protected ProguardKeepRule materialize() {
     return new ProguardKeepRule(
+        Origin.unknown(),
+        Position.UNKNOWN,
+        null,
         getClassAnnotation(),
         getClassAccessFlags(),
         getNegatedClassAccessFlags(),
@@ -93,25 +85,9 @@
       return false;
     }
     ProguardKeepRule that = (ProguardKeepRule) o;
-
-    if (type != that.type) {
-      return false;
-    }
-    if (!modifiers.equals(that.modifiers)) {
-      return false;
-    }
     return super.equals(that);
   }
 
-  @Override
-  public int hashCode() {
-    // Used multiplier 3 to avoid too much overflow when computing hashCode.
-    int result = type.hashCode();
-    result = 3 * result + modifiers.hashCode();
-    result = 3 * result + super.hashCode();
-    return result;
-  }
-
   static void appendNonEmpty(StringBuilder builder, String pre, Object item, String post) {
     if (item == null) {
       return;
@@ -128,16 +104,6 @@
     }
   }
 
-  @Override
-  String typeString() {
-    return type.toString();
-  }
-
-  @Override
-  String modifierString() {
-    return modifiers.toString();
-  }
-
   public static ProguardKeepRule defaultKeepAllRule(
       Consumer<ProguardKeepRuleModifiers.Builder> modifiers) {
     ProguardKeepRule.Builder builder = ProguardKeepRule.builder();
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
new file mode 100644
index 0000000..f67634e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2016, 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;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class ProguardKeepRuleBase extends ProguardConfigurationRule {
+
+  public static abstract class Builder<C extends ProguardKeepRuleBase, B extends Builder<C, B>>
+      extends ProguardConfigurationRule.Builder<C, B> {
+
+    protected ProguardKeepRuleType type;
+    protected final ProguardKeepRuleModifiers.Builder modifiersBuilder =
+        ProguardKeepRuleModifiers.builder();
+
+    protected Builder() {
+      super();
+    }
+
+    public void setType(ProguardKeepRuleType type) {
+      this.type = type;
+    }
+
+    public ProguardKeepRuleModifiers.Builder getModifiersBuilder() {
+      return modifiersBuilder;
+    }
+  }
+
+  private final ProguardKeepRuleType type;
+  private final ProguardKeepRuleModifiers modifiers;
+
+  protected ProguardKeepRuleBase(
+      Origin origin,
+      Position position,
+      String source,
+      ProguardTypeMatcher classAnnotation,
+      ProguardAccessFlags classAccessFlags,
+      ProguardAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      ProguardClassNameList classNames,
+      ProguardTypeMatcher inheritanceAnnotation,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      List<ProguardMemberRule> memberRules,
+      ProguardKeepRuleType type,
+      ProguardKeepRuleModifiers modifiers) {
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules);
+    this.type = type;
+    this.modifiers = modifiers;
+  }
+
+  public ProguardKeepRuleType getType() {
+    return type;
+  }
+
+  public ProguardKeepRuleModifiers getModifiers() {
+    return modifiers;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof ProguardKeepRuleBase)) {
+      return false;
+    }
+    ProguardKeepRuleBase that = (ProguardKeepRuleBase) o;
+
+    if (type != that.type) {
+      return false;
+    }
+    if (!modifiers.equals(that.modifiers)) {
+      return false;
+    }
+    return super.equals(that);
+  }
+
+  @Override
+  public int hashCode() {
+    // Used multiplier 3 to avoid too much overflow when computing hashCode.
+    int result = type.hashCode();
+    result = 3 * result + modifiers.hashCode();
+    result = 3 * result + super.hashCode();
+    return result;
+  }
+
+  static void appendNonEmpty(StringBuilder builder, String pre, Object item, String post) {
+    if (item == null) {
+      return;
+    }
+    String text = item.toString();
+    if (!text.isEmpty()) {
+      if (pre != null) {
+        builder.append(pre);
+      }
+      builder.append(text);
+      if (post != null) {
+        builder.append(post);
+      }
+    }
+  }
+
+  @Override
+  String typeString() {
+    return type.toString();
+  }
+
+  @Override
+  String modifierString() {
+    return modifiers.toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardPathFilter.java b/src/main/java/com/android/tools/r8/shaking/ProguardPathFilter.java
index cb2e117..b3771ef 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardPathFilter.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardPathFilter.java
@@ -7,6 +7,7 @@
 import com.google.common.collect.ImmutableList;
 
 public class ProguardPathFilter {
+  private final boolean enabled;
   private final ImmutableList<ProguardPathList> patterns;
 
   public static Builder builder() {
@@ -14,6 +15,7 @@
   }
 
   public static class Builder {
+    private boolean enabled = true;
     private final ImmutableList.Builder<ProguardPathList> patterns = ImmutableList.builder();
 
     private Builder() {
@@ -24,23 +26,41 @@
       return this;
     }
 
+    public Builder disable() {
+      enabled = false;
+      return this;
+    }
+
+    public Builder enable() {
+      enabled = true;
+      return this;
+    }
+
     ProguardPathFilter build() {
-      return new ProguardPathFilter(patterns.build());
+      return new ProguardPathFilter(patterns.build(), enabled);
     }
   }
 
-  private ProguardPathFilter(ImmutableList<ProguardPathList> patterns) {
+  private ProguardPathFilter(ImmutableList<ProguardPathList> patterns, boolean enabled) {
+    this.enabled = enabled;
     if (patterns.isEmpty()) {
       this.patterns = ImmutableList.of(ProguardPathList.emptyList());
     } else {
+      assert enabled;
       this.patterns = patterns;
     }
   }
 
+  public boolean isEnabled() {
+    return enabled;
+  }
+
   public boolean matches(String path) {
-    for (ProguardPathList pattern : patterns) {
-      if (pattern.matches(path)) {
-        return true;
+    if (enabled) {
+      for (ProguardPathList pattern : patterns) {
+        if (pattern.matches(path)) {
+          return true;
+        }
       }
     }
     return false;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
index 73b5538..87200fb 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
@@ -3,23 +3,36 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import java.util.List;
 
 public class ProguardWhyAreYouKeepingRule extends ProguardConfigurationRule {
 
-  public static class Builder extends ProguardConfigurationRule.Builder {
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<ProguardWhyAreYouKeepingRule, Builder> {
 
     private Builder() {
+      super();
     }
 
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
     public ProguardWhyAreYouKeepingRule build() {
-      return new ProguardWhyAreYouKeepingRule(classAnnotation, classAccessFlags,
-          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
-          inheritanceClassName, inheritanceIsExtends, memberRules);
+      return new ProguardWhyAreYouKeepingRule(origin, getPosition(), source, classAnnotation,
+          classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
+          inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
     }
   }
 
   private ProguardWhyAreYouKeepingRule(
+      Origin origin,
+      Position position,
+      String source,
       ProguardTypeMatcher classAnnotation,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
@@ -30,12 +43,13 @@
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
-        classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
+        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
+        inheritanceIsExtends, memberRules);
   }
 
-  public static ProguardWhyAreYouKeepingRule.Builder builder() {
-    return new ProguardWhyAreYouKeepingRule.Builder();
+  public static Builder builder() {
+    return new Builder();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index c95cfe9..473c990 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -59,9 +59,9 @@
   private final Set<ProguardConfigurationRule> rulesThatUseExtendsOrImplementsWrong =
       Sets.newIdentityHashSet();
   private final Set<DexItem> checkDiscarded = Sets.newIdentityHashSet();
-  private final Set<DexItem> alwaysInline = Sets.newIdentityHashSet();
-  private final Set<DexItem> forceInline = Sets.newIdentityHashSet();
-  private final Set<DexItem> neverInline = Sets.newIdentityHashSet();
+  private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet();
+  private final Set<DexMethod> forceInline = Sets.newIdentityHashSet();
+  private final Set<DexMethod> neverInline = Sets.newIdentityHashSet();
   private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking =
       new IdentityHashMap<>();
   private final Map<DexItem, ProguardMemberRule> noSideEffects = new IdentityHashMap<>();
@@ -727,13 +727,19 @@
     } else if (context instanceof InlineRule) {
       switch (((InlineRule) context).getType()) {
         case ALWAYS:
-          alwaysInline.add(item);
+          if (item instanceof DexEncodedMethod) {
+            alwaysInline.add(((DexEncodedMethod) item).method);
+          }
           break;
         case FORCE:
-          forceInline.add(item);
+          if (item instanceof DexEncodedMethod) {
+            forceInline.add(((DexEncodedMethod) item).method);
+          }
           break;
         case NEVER:
-          neverInline.add(item);
+          if (item instanceof DexEncodedMethod) {
+            neverInline.add(((DexEncodedMethod) item).method);
+          }
           break;
         default:
           throw new Unreachable();
@@ -755,9 +761,9 @@
     public final Set<DexItem> reasonAsked;
     public final Set<DexItem> keepPackageName;
     public final Set<DexItem> checkDiscarded;
-    public final Set<DexItem> alwaysInline;
-    public final Set<DexItem> forceInline;
-    public final Set<DexItem> neverInline;
+    public final Set<DexMethod> alwaysInline;
+    public final Set<DexMethod> forceInline;
+    public final Set<DexMethod> neverInline;
     public final Map<DexItem, ProguardMemberRule> noSideEffects;
     public final Map<DexItem, ProguardMemberRule> assumedValues;
     private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking;
@@ -792,9 +798,9 @@
         Set<DexItem> reasonAsked,
         Set<DexItem> keepPackageName,
         Set<DexItem> checkDiscarded,
-        Set<DexItem> alwaysInline,
-        Set<DexItem> forceInline,
-        Set<DexItem> neverInline,
+        Set<DexMethod> alwaysInline,
+        Set<DexMethod> forceInline,
+        Set<DexMethod> neverInline,
         Map<DexItem, ProguardMemberRule> noSideEffects,
         Map<DexItem, ProguardMemberRule> assumedValues,
         Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking,
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 4d072c4..8dec498 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo.ResolutionResult;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
@@ -22,6 +23,7 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.Builder;
+import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
 import com.android.tools.r8.graph.JarCode;
 import com.android.tools.r8.graph.KeyedDexItem;
 import com.android.tools.r8.graph.MethodAccessFlags;
@@ -29,7 +31,9 @@
 import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.MethodPoolCollection;
+import com.android.tools.r8.ir.optimize.MethodPoolCollection.MethodPool;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.logging.Log;
@@ -57,8 +61,14 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.MethodNode;
 
 /**
  * Merges Supertypes with a single implementation into their single subtype.
@@ -150,7 +160,9 @@
 
   private final DexApplication application;
   private final AppInfoWithLiveness appInfo;
+  private final ExecutorService executorService;
   private final GraphLense graphLense;
+  private final MethodPoolCollection methodPoolCollection;
   private final Timing timing;
   private Collection<DexMethod> invokes;
 
@@ -170,11 +182,16 @@
   private final VerticalClassMergerGraphLense.Builder renamedMembersLense;
 
   public VerticalClassMerger(
-      DexApplication application, AppView<AppInfoWithLiveness> appView, Timing timing) {
+      DexApplication application,
+      AppView<AppInfoWithLiveness> appView,
+      ExecutorService executorService,
+      Timing timing) {
     this.application = application;
     this.appInfo = appView.getAppInfo();
+    this.executorService = executorService;
     this.graphLense = appView.getGraphLense();
-    this.renamedMembersLense = VerticalClassMergerGraphLense.builder(appInfo);
+    this.methodPoolCollection = new MethodPoolCollection(application);
+    this.renamedMembersLense = new VerticalClassMergerGraphLense.Builder();
     this.timing = timing;
 
     Iterable<DexProgramClass> classes = application.classesWithDeterministicOrder();
@@ -237,7 +254,7 @@
     }
   }
 
-  private void extractPinnedItems(Iterable<DexItem> items, AbortReason reason) {
+  private <T extends DexItem> void extractPinnedItems(Iterable<T> items, AbortReason reason) {
     for (DexItem item : items) {
       if (item instanceof DexType || item instanceof DexClass) {
         DexType type = item instanceof DexType ? (DexType) item : ((DexClass) item).type;
@@ -520,14 +537,20 @@
     }
   }
 
-  public GraphLense run() {
+  public GraphLense run() throws ExecutionException {
     timing.begin("merge");
     GraphLense mergingGraphLense = mergeClasses(graphLense);
     timing.end();
     timing.begin("fixup");
     GraphLense result = new TreeFixer().fixupTypeReferences(mergingGraphLense);
     timing.end();
-    assert result.assertNotModified(appInfo.alwaysInline);
+    assert result.assertNotModified(
+        appInfo
+            .alwaysInline
+            .stream()
+            .map(appInfo::definitionFor)
+            .filter(Objects::nonNull)
+            .collect(Collectors.toList()));
     assert result.assertNotModified(appInfo.noSideEffects.keySet());
     // TODO(christofferqa): Enable this assert.
     // assert result.assertNotModified(appInfo.pinnedItems);
@@ -561,7 +584,7 @@
     }
   }
 
-  private GraphLense mergeClasses(GraphLense graphLense) {
+  private GraphLense mergeClasses(GraphLense graphLense) throws ExecutionException {
     Deque<DexProgramClass> worklist = new ArrayDeque<>();
     Set<DexProgramClass> seenBefore = new HashSet<>();
 
@@ -636,7 +659,7 @@
     if (Log.ENABLED) {
       Log.debug(getClass(), "Merged %d classes.", numberOfMerges);
     }
-    return renamedMembersLense.build(graphLense, mergedClasses, application.dexItemFactory);
+    return renamedMembersLense.build(graphLense, mergedClasses, appInfo);
   }
 
   private boolean methodResolutionMayChange(DexClass source, DexClass target) {
@@ -718,7 +741,7 @@
     private final DexClass source;
     private final DexClass target;
     private final VerticalClassMergerGraphLense.Builder deferredRenamings =
-        VerticalClassMergerGraphLense.builder(appInfo);
+        new VerticalClassMergerGraphLense.Builder();
     private boolean abortMerge = false;
 
     private ClassMerger(DexClass source, DexClass target) {
@@ -726,7 +749,7 @@
       this.target = target;
     }
 
-    public boolean merge() {
+    public boolean merge() throws ExecutionException {
       // Merge the class [clazz] into [targetClass] by adding all methods to
       // targetClass that are not currently contained.
       // Step 1: Merge methods
@@ -758,6 +781,7 @@
                   directMethod.isClassInitializer() ? Rename.NEVER : Rename.IF_NEEDED);
           add(directMethods, resultingDirectMethod, MethodSignatureEquivalence.get());
           deferredRenamings.map(directMethod.method, resultingDirectMethod.method);
+          deferredRenamings.recordMove(directMethod.method, resultingDirectMethod.method);
 
           if (!directMethod.isStaticMethod()) {
             blockRedirectionOfSuperCalls(resultingDirectMethod.method);
@@ -787,17 +811,43 @@
             DexEncodedMethod resultingVirtualMethod =
                 renameMethod(virtualMethod, availableMethodSignatures, Rename.NEVER);
             deferredRenamings.map(virtualMethod.method, resultingVirtualMethod.method);
+            deferredRenamings.recordMove(virtualMethod.method, resultingVirtualMethod.method);
             add(virtualMethods, resultingVirtualMethod, MethodSignatureEquivalence.get());
             continue;
           }
         }
 
-        // This virtual method could be called directly from a sub class via an invoke-super
-        // instruction. Therefore, we translate this virtual method into a direct method, such that
-        // relevant invoke-super instructions can be rewritten into invoke-direct instructions.
-        DexEncodedMethod resultingDirectMethod =
-            renameMethod(virtualMethod, availableMethodSignatures, Rename.ALWAYS);
-        makePrivate(resultingDirectMethod);
+        DexEncodedMethod resultingDirectMethod;
+        if (source.accessFlags.isInterface()) {
+          // Moving a default interface method into its subtype. This method could be hit directly
+          // via an invoke-super instruction from any of the transitive subtypes of this interface,
+          // due to the way invoke-super works on default interface methods. In order to be able
+          // to hit this method directly after the merge, we need to make it public, and find a
+          // method name that does not collide with one in the hierarchy of this class.
+          MethodPool methodPoolForTarget =
+              methodPoolCollection.buildForHierarchy(target, executorService, timing);
+          resultingDirectMethod =
+              renameMethod(
+                  virtualMethod,
+                  method ->
+                      availableMethodSignatures.test(method)
+                          && !methodPoolForTarget.hasSeen(
+                              MethodSignatureEquivalence.get().wrap(method)),
+                  Rename.ALWAYS,
+                  getStaticProto(virtualMethod.method.holder, virtualMethod.method.proto));
+          makeStatic(resultingDirectMethod);
+
+          // Update method pool collection now that we are adding a new public method.
+          methodPoolForTarget.seen(resultingDirectMethod.method);
+        } else {
+          // This virtual method could be called directly from a sub class via an invoke-super in-
+          // struction. Therefore, we translate this virtual method into a direct method, such that
+          // relevant invoke-super instructions can be rewritten into invoke-direct instructions.
+          resultingDirectMethod =
+              renameMethod(virtualMethod, availableMethodSignatures, Rename.ALWAYS);
+          makePrivate(resultingDirectMethod);
+        }
+
         add(directMethods, resultingDirectMethod, MethodSignatureEquivalence.get());
 
         // Record that invoke-super instructions in the target class should be redirected to the
@@ -811,11 +861,12 @@
           // Note that this method is added independently of whether it will actually be used. If
           // it turns out that the method is never used, it will be removed by the final round
           // of tree shaking.
-          shadowedBy = buildBridgeMethod(virtualMethod, resultingDirectMethod.method);
+          shadowedBy = buildBridgeMethod(virtualMethod, resultingDirectMethod);
           add(virtualMethods, shadowedBy, MethodSignatureEquivalence.get());
         }
 
         deferredRenamings.map(virtualMethod.method, shadowedBy.method);
+        deferredRenamings.recordMove(virtualMethod.method, resultingDirectMethod.method);
       }
 
       if (abortMerge) {
@@ -903,7 +954,8 @@
         // rewrite any invocations on the form "invoke-super J.m()" to "invoke-direct C.m$I()",
         // if I has a supertype J. This is due to the fact that invoke-super instructions that
         // resolve to a method on an interface never hit an implementation below that interface.
-        deferredRenamings.mapVirtualMethodToDirectInType(oldTarget, newTarget, target.type);
+        deferredRenamings.mapVirtualMethodToDirectInType(
+            oldTarget, new GraphLenseLookupResult(newTarget, Type.STATIC), target.type);
       } else {
         // If we merge class B into class C, and class C contains an invocation super.m(), then it
         // is insufficient to rewrite "invoke-super B.m()" to "invoke-direct C.m$B()" (the method
@@ -922,7 +974,7 @@
                   || appInfo.lookupSuperTarget(signatureInHolder, holder.type) != null;
           if (resolutionSucceeds) {
             deferredRenamings.mapVirtualMethodToDirectInType(
-                signatureInHolder, newTarget, target.type);
+                signatureInHolder, new GraphLenseLookupResult(newTarget, Type.DIRECT), target.type);
           } else {
             break;
           }
@@ -944,7 +996,9 @@
                       || appInfo.lookupSuperTarget(signatureInHolder, holder.type) != null;
               if (resolutionSucceededBeforeMerge) {
                 deferredRenamings.mapVirtualMethodToDirectInType(
-                    signatureInType, newTarget, target.type);
+                    signatureInType,
+                    new GraphLenseLookupResult(newTarget, Type.DIRECT),
+                    target.type);
               }
             }
           }
@@ -974,23 +1028,38 @@
     }
 
     private DexEncodedMethod buildBridgeMethod(
-        DexEncodedMethod method, DexMethod invocationTarget) {
+        DexEncodedMethod method, DexEncodedMethod invocationTarget) {
       DexType holder = target.type;
-      DexProto proto = invocationTarget.proto;
+      DexProto proto = method.method.proto;
       DexString name = method.method.name;
       MethodAccessFlags accessFlags = method.accessFlags.copy();
       accessFlags.setBridge();
       accessFlags.setSynthetic();
       accessFlags.unsetAbstract();
-      DexEncodedMethod bridge = new DexEncodedMethod(
-          application.dexItemFactory.createMethod(holder, proto, name),
-          accessFlags,
-          DexAnnotationSet.empty(),
-          ParameterAnnotationsList.empty(),
-          new SynthesizedCode(
-              new ForwardMethodSourceCode(holder, proto, holder, invocationTarget, Type.DIRECT),
-              registry -> registry.registerInvokeDirect(invocationTarget)),
-          method.hasClassFileVersion() ? method.getClassFileVersion() : -1);
+      SynthesizedCode code;
+      if (invocationTarget.isPrivateMethod()) {
+        assert !invocationTarget.isStaticMethod();
+        code =
+            new SynthesizedCode(
+                new ForwardMethodSourceCode(
+                    holder, proto, holder, invocationTarget.method, Type.DIRECT),
+                registry -> registry.registerInvokeDirect(invocationTarget.method));
+      } else {
+        assert invocationTarget.isStaticMethod();
+        code =
+            new SynthesizedCode(
+                new ForwardMethodSourceCode(
+                    holder, proto, null, invocationTarget.method, Type.STATIC),
+                registry -> registry.registerInvokeStatic(invocationTarget.method));
+      }
+      DexEncodedMethod bridge =
+          new DexEncodedMethod(
+              application.dexItemFactory.createMethod(holder, proto, name),
+              accessFlags,
+              DexAnnotationSet.empty(),
+              ParameterAnnotationsList.empty(),
+              code,
+              method.hasClassFileVersion() ? method.getClassFileVersion() : -1);
       if (method.getOptimizationInfo().isPublicized()) {
         // The bridge is now the public method serving the role of the original method, and should
         // reflect that this method was publicized.
@@ -1097,6 +1166,7 @@
       DexEncodedMethod result = method.toTypeSubstitutedMethod(newSignature);
       result.markForceInline();
       deferredRenamings.map(method.method, result.method);
+      deferredRenamings.recordMove(method.method, result.method);
       // Renamed constructors turn into ordinary private functions. They can be private, as
       // they are only references from their direct subclass, which they were merged into.
       result.accessFlags.unsetConstructor();
@@ -1106,6 +1176,14 @@
 
     private DexEncodedMethod renameMethod(
         DexEncodedMethod method, Predicate<DexMethod> availableMethodSignatures, Rename strategy) {
+      return renameMethod(method, availableMethodSignatures, strategy, method.method.proto);
+    }
+
+    private DexEncodedMethod renameMethod(
+        DexEncodedMethod method,
+        Predicate<DexMethod> availableMethodSignatures,
+        Rename strategy,
+        DexProto newProto) {
       // We cannot handle renaming static initializers yet and constructors should have been
       // renamed already.
       assert !method.accessFlags.isConstructor() || strategy == Rename.NEVER;
@@ -1115,8 +1193,7 @@
       DexMethod newSignature;
       switch (strategy) {
         case IF_NEEDED:
-          newSignature =
-              application.dexItemFactory.createMethod(target.type, method.method.proto, oldName);
+          newSignature = application.dexItemFactory.createMethod(target.type, newProto, oldName);
           if (availableMethodSignatures.test(newSignature)) {
             break;
           }
@@ -1126,15 +1203,13 @@
           int count = 1;
           do {
             DexString newName = getFreshName(oldName.toSourceString(), count, oldHolder);
-            newSignature =
-                application.dexItemFactory.createMethod(target.type, method.method.proto, newName);
+            newSignature = application.dexItemFactory.createMethod(target.type, newProto, newName);
             count++;
           } while (!availableMethodSignatures.test(newSignature));
           break;
 
         case NEVER:
-          newSignature =
-              application.dexItemFactory.createMethod(target.type, method.method.proto, oldName);
+          newSignature = application.dexItemFactory.createMethod(target.type, newProto, oldName);
           assert availableMethodSignatures.test(newSignature);
           break;
 
@@ -1173,6 +1248,24 @@
     method.accessFlags.setPrivate();
   }
 
+  private static void makeStatic(DexEncodedMethod method) {
+    method.accessFlags.setStatic();
+
+    Code code = method.getCode();
+    if (code.isJarCode()) {
+      MethodNode node = code.asJarCode().getNode();
+      node.access |= Opcodes.ACC_STATIC;
+      node.desc = method.method.proto.toDescriptorString();
+    }
+  }
+
+  private DexProto getStaticProto(DexType receiverType, DexProto proto) {
+    DexType[] parameterTypes = new DexType[proto.parameters.size() + 1];
+    parameterTypes[0] = receiverType;
+    System.arraycopy(proto.parameters.values, 0, parameterTypes, 1, proto.parameters.size());
+    return appInfo.dexItemFactory.createProto(proto.returnType, parameterTypes);
+  }
+
   private class TreeFixer {
 
     private final Builder lense = GraphLense.builder();
@@ -1405,13 +1498,13 @@
     // that we always return true here in these cases.
     if (method.getCode().isJarCode()) {
       JarCode jarCode = method.getCode().asJarCode();
-      Constraint constraint =
+      ConstraintWithTarget constraint =
           jarCode.computeInliningConstraint(
               method,
               appInfo,
               new SingleTypeMapperGraphLense(method.method.holder, invocationContext),
               invocationContext);
-      return constraint == Constraint.NEVER;
+      return constraint == ConstraintWithTarget.NEVER;
     }
     // TODO(christofferqa): For non-jar code we currently cannot guarantee that markForceInline()
     // will succeed.
@@ -1429,6 +1522,26 @@
     }
 
     @Override
+    public DexField getOriginalFieldSignature(DexField field) {
+      throw new Unreachable();
+    }
+
+    @Override
+    public DexMethod getOriginalMethodSignature(DexMethod method) {
+      throw new Unreachable();
+    }
+
+    @Override
+    public DexField getRenamedFieldSignature(DexField originalField) {
+      throw new Unreachable();
+    }
+
+    @Override
+    public DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
+      throw new Unreachable();
+    }
+
+    @Override
     public DexType lookupType(DexType type) {
       return type == source ? target : mergedClasses.getOrDefault(type, type);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index fa10215..4e09a9e 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -14,6 +14,9 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.ir.code.Invoke.Type;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import java.util.HashMap;
@@ -48,42 +51,49 @@
   private final AppInfo appInfo;
 
   private final Set<DexMethod> mergedMethods;
-  private final Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps;
+  private final Map<DexType, Map<DexMethod, GraphLenseLookupResult>>
+      contextualVirtualToDirectMethodMaps;
 
   public VerticalClassMergerGraphLense(
       AppInfo appInfo,
       Map<DexField, DexField> fieldMap,
       Map<DexMethod, DexMethod> methodMap,
       Set<DexMethod> mergedMethods,
-      Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps,
+      Map<DexType, Map<DexMethod, GraphLenseLookupResult>> contextualVirtualToDirectMethodMaps,
+      BiMap<DexField, DexField> originalFieldSignatures,
+      BiMap<DexMethod, DexMethod> originalMethodSignatures,
       GraphLense previousLense) {
-    super(ImmutableMap.of(), methodMap, fieldMap, previousLense, appInfo.dexItemFactory);
+    super(
+        ImmutableMap.of(),
+        methodMap,
+        fieldMap,
+        originalFieldSignatures,
+        originalMethodSignatures,
+        previousLense,
+        appInfo.dexItemFactory);
     this.appInfo = appInfo;
     this.mergedMethods = mergedMethods;
     this.contextualVirtualToDirectMethodMaps = contextualVirtualToDirectMethodMaps;
   }
 
-  public static Builder builder(AppInfo appInfo) {
-    return new Builder(appInfo);
-  }
-
   @Override
   public GraphLenseLookupResult lookupMethod(
       DexMethod method, DexEncodedMethod context, Type type) {
     assert isContextFreeForMethod(method) || (context != null && type != null);
     GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
     if (previous.getType() == Type.SUPER && !mergedMethods.contains(context.method)) {
-      Map<DexMethod, DexMethod> virtualToDirectMethodMap =
+      Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap =
           contextualVirtualToDirectMethodMaps.get(context.method.holder);
       if (virtualToDirectMethodMap != null) {
-        DexMethod directMethod = virtualToDirectMethodMap.get(previous.getMethod());
-        if (directMethod != null) {
+        GraphLenseLookupResult lookup = virtualToDirectMethodMap.get(previous.getMethod());
+        if (lookup != null) {
           // If the super class A of the enclosing class B (i.e., context.method.holder)
           // has been merged into B during vertical class merging, and this invoke-super instruction
           // was resolving to a method in A, then the target method has been changed to a direct
           // method and moved into B, so that we need to use an invoke-direct instruction instead of
-          // invoke-super.
-          return new GraphLenseLookupResult(directMethod, Type.DIRECT);
+          // invoke-super (or invoke-static, if the method was originally a default interface
+          // method).
+          return lookup;
         }
       }
     }
@@ -102,11 +112,11 @@
     ImmutableSet.Builder<DexMethod> builder = ImmutableSet.builder();
     for (DexMethod previous : previousLense.lookupMethodInAllContexts(method)) {
       builder.add(methodMap.getOrDefault(previous, previous));
-      for (Map<DexMethod, DexMethod> virtualToDirectMethodMap :
+      for (Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap :
           contextualVirtualToDirectMethodMaps.values()) {
-        DexMethod directMethod = virtualToDirectMethodMap.get(previous);
-        if (directMethod != null) {
-          builder.add(directMethod);
+        GraphLenseLookupResult lookup = virtualToDirectMethodMap.get(previous);
+        if (lookup != null) {
+          builder.add(lookup.getMethod());
         }
       }
     }
@@ -124,7 +134,7 @@
       return false;
     }
     DexMethod previous = previousLense.lookupMethod(method);
-    for (Map<DexMethod, DexMethod> virtualToDirectMethodMap :
+    for (Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap :
         contextualVirtualToDirectMethodMaps.values()) {
       if (virtualToDirectMethodMap.containsKey(previous)) {
         return false;
@@ -134,62 +144,109 @@
   }
 
   public static class Builder {
-    private final AppInfo appInfo;
 
-    protected final Map<DexField, DexField> fieldMap = new HashMap<>();
+    protected final BiMap<DexField, DexField> fieldMap = HashBiMap.create();
     protected final Map<DexMethod, DexMethod> methodMap = new HashMap<>();
     private final ImmutableSet.Builder<DexMethod> mergedMethodsBuilder = ImmutableSet.builder();
-    private final Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps =
-        new HashMap<>();
+    private final Map<DexType, Map<DexMethod, GraphLenseLookupResult>>
+        contextualVirtualToDirectMethodMaps = new HashMap<>();
 
-    private Builder(AppInfo appInfo) {
-      this.appInfo = appInfo;
-    }
+    private final Map<DexMethod, DexMethod> originalMethodSignatures = HashBiMap.create();
 
     public GraphLense build(
-        GraphLense previousLense,
-        Map<DexType, DexType> mergedClasses,
-        DexItemFactory dexItemFactory) {
+        GraphLense previousLense, Map<DexType, DexType> mergedClasses, AppInfo appInfo) {
       if (fieldMap.isEmpty()
           && methodMap.isEmpty()
           && contextualVirtualToDirectMethodMaps.isEmpty()) {
         return previousLense;
       }
+      Map<DexProto, DexProto> cache = new HashMap<>();
+      BiMap<DexField, DexField> originalFieldSignatures = fieldMap.inverse();
       return new VerticalClassMergerGraphLense(
           appInfo,
           fieldMap,
           methodMap,
           getMergedMethodSignaturesAfterClassMerging(
-              mergedMethodsBuilder.build(), mergedClasses, dexItemFactory),
+              mergedMethodsBuilder.build(), mergedClasses, appInfo.dexItemFactory, cache),
           contextualVirtualToDirectMethodMaps,
+          getOriginalFieldSignaturesAfterClassMerging(
+              originalFieldSignatures, mergedClasses, appInfo.dexItemFactory),
+          getOriginalMethodSignaturesAfterClassMerging(
+              originalMethodSignatures, mergedClasses, appInfo.dexItemFactory, cache),
           previousLense);
     }
 
     // After we have recorded that a method "a.b.c.Foo;->m(A, B, C)V" was merged into another class,
     // it could be that the class B was merged into its subclass B'. In that case we update the
     // signature to "a.b.c.Foo;->m(A, B', C)V".
-    private Set<DexMethod> getMergedMethodSignaturesAfterClassMerging(
+    private static Set<DexMethod> getMergedMethodSignaturesAfterClassMerging(
         Set<DexMethod> mergedMethods,
         Map<DexType, DexType> mergedClasses,
-        DexItemFactory dexItemFactory) {
+        DexItemFactory dexItemFactory,
+        Map<DexProto, DexProto> cache) {
       ImmutableSet.Builder<DexMethod> result = ImmutableSet.builder();
-      Map<DexProto, DexProto> cache = new HashMap<>();
       for (DexMethod signature : mergedMethods) {
-        DexType newHolder = mergedClasses.getOrDefault(signature.holder, signature.holder);
-        DexProto newProto =
-            dexItemFactory.applyClassMappingToProto(
-                signature.proto, type -> mergedClasses.getOrDefault(type, type), cache);
-        if (signature.holder.equals(newHolder) && signature.proto.equals(newProto)) {
-          result.add(signature);
-        } else {
-          result.add(dexItemFactory.createMethod(newHolder, newProto, signature.name));
-        }
+        result.add(
+            getMethodSignatureAfterClassMerging(signature, mergedClasses, dexItemFactory, cache));
       }
       return result.build();
     }
 
+    private static BiMap<DexField, DexField> getOriginalFieldSignaturesAfterClassMerging(
+        Map<DexField, DexField> originalFieldSignatures,
+        Map<DexType, DexType> mergedClasses,
+        DexItemFactory dexItemFactory) {
+      ImmutableBiMap.Builder<DexField, DexField> result = ImmutableBiMap.builder();
+      for (Map.Entry<DexField, DexField> entry : originalFieldSignatures.entrySet()) {
+        result.put(
+            getFieldSignatureAfterClassMerging(entry.getKey(), mergedClasses, dexItemFactory),
+            entry.getValue());
+      }
+      return result.build();
+    }
+
+    private static BiMap<DexMethod, DexMethod> getOriginalMethodSignaturesAfterClassMerging(
+        Map<DexMethod, DexMethod> originalMethodSignatures,
+        Map<DexType, DexType> mergedClasses,
+        DexItemFactory dexItemFactory,
+        Map<DexProto, DexProto> cache) {
+      ImmutableBiMap.Builder<DexMethod, DexMethod> result = ImmutableBiMap.builder();
+      for (Map.Entry<DexMethod, DexMethod> entry : originalMethodSignatures.entrySet()) {
+        result.put(
+            getMethodSignatureAfterClassMerging(
+                entry.getKey(), mergedClasses, dexItemFactory, cache),
+            entry.getValue());
+      }
+      return result.build();
+    }
+
+    private static DexField getFieldSignatureAfterClassMerging(
+        DexField signature, Map<DexType, DexType> mergedClasses, DexItemFactory dexItemFactory) {
+      DexType newClass = mergedClasses.getOrDefault(signature.clazz, signature.clazz);
+      DexType newType = mergedClasses.getOrDefault(signature.type, signature.type);
+      if (signature.clazz.equals(newClass) && signature.type.equals(newType)) {
+        return signature;
+      }
+      return dexItemFactory.createField(newClass, newType, signature.name);
+    }
+
+    private static DexMethod getMethodSignatureAfterClassMerging(
+        DexMethod signature,
+        Map<DexType, DexType> mergedClasses,
+        DexItemFactory dexItemFactory,
+        Map<DexProto, DexProto> cache) {
+      DexType newHolder = mergedClasses.getOrDefault(signature.holder, signature.holder);
+      DexProto newProto =
+          dexItemFactory.applyClassMappingToProto(
+              signature.proto, type -> mergedClasses.getOrDefault(type, type), cache);
+      if (signature.holder.equals(newHolder) && signature.proto.equals(newProto)) {
+        return signature;
+      }
+      return dexItemFactory.createMethod(newHolder, newProto, signature.name);
+    }
+
     public boolean hasMappingForSignatureInContext(DexType context, DexMethod signature) {
-      Map<DexMethod, DexMethod> virtualToDirectMethodMap =
+      Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap =
           contextualVirtualToDirectMethodMaps.get(context);
       if (virtualToDirectMethodMap != null) {
         return virtualToDirectMethodMap.containsKey(signature);
@@ -209,8 +266,13 @@
       methodMap.put(from, to);
     }
 
-    public void mapVirtualMethodToDirectInType(DexMethod from, DexMethod to, DexType type) {
-      Map<DexMethod, DexMethod> virtualToDirectMethodMap =
+    public void recordMove(DexMethod from, DexMethod to) {
+      originalMethodSignatures.put(to, from);
+    }
+
+    public void mapVirtualMethodToDirectInType(
+        DexMethod from, GraphLenseLookupResult to, DexType type) {
+      Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap =
           contextualVirtualToDirectMethodMaps.computeIfAbsent(type, key -> new HashMap<>());
       virtualToDirectMethodMap.put(from, to);
     }
@@ -219,9 +281,12 @@
       fieldMap.putAll(builder.fieldMap);
       methodMap.putAll(builder.methodMap);
       mergedMethodsBuilder.addAll(builder.mergedMethodsBuilder.build());
+      originalMethodSignatures.putAll(builder.originalMethodSignatures);
       for (DexType context : builder.contextualVirtualToDirectMethodMaps.keySet()) {
-        Map<DexMethod, DexMethod> current = contextualVirtualToDirectMethodMaps.get(context);
-        Map<DexMethod, DexMethod> other = builder.contextualVirtualToDirectMethodMaps.get(context);
+        Map<DexMethod, GraphLenseLookupResult> current =
+            contextualVirtualToDirectMethodMaps.get(context);
+        Map<DexMethod, GraphLenseLookupResult> other =
+            builder.contextualVirtualToDirectMethodMaps.get(context);
         if (current != null) {
           current.putAll(other);
         } else {
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index 127debe..135e961 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -9,6 +9,7 @@
  * Android API level description
  */
 public enum AndroidApiLevel {
+  Q(29),  // Speculative, this can change.
   P(28),
   O_MR1(27),
   O(26),
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 1b026c0..4dff93c 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -10,6 +10,9 @@
 import com.android.tools.r8.ArchiveClassFileProvider;
 import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.DataResourceProvider.Visitor;
 import com.android.tools.r8.DexFilePerClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DirectoryClassFileProvider;
@@ -267,6 +270,7 @@
 
     private final List<ProgramResourceProvider> programResourceProviders = new ArrayList<>();
     private final List<ProgramResource> programResources = new ArrayList<>();
+    private final List<DataEntryResource> dataResources = new ArrayList<>();
     private final Map<ProgramResource, String> programResourcesMainDescriptor = new HashMap<>();
     private final List<ClassFileResourceProvider> classpathResourceProviders = new ArrayList<>();
     private final List<ClassFileResourceProvider> libraryResourceProviders = new ArrayList<>();
@@ -464,6 +468,12 @@
       return this;
     }
 
+    /** Add resource data. */
+    public Builder addDataResource(DataEntryResource dataResource) {
+      addDataResources(dataResource);
+      return this;
+    }
+
     /**
      * Set proguard-map output data.
      *
@@ -529,17 +539,34 @@
      * Build final AndroidApp.
      */
     public AndroidApp build() {
-      if (!programResources.isEmpty()) {
+      if (!programResources.isEmpty() || !dataResources.isEmpty()) {
         // If there are individual program resources move them to a dedicated provider.
-        final List<ProgramResource> resources = ImmutableList.copyOf(programResources);
+        final List<ProgramResource> finalProgramResources = ImmutableList.copyOf(programResources);
+        final List<DataEntryResource> finalDataResources = ImmutableList.copyOf(dataResources);
         programResourceProviders.add(
             new ProgramResourceProvider() {
               @Override
-              public Collection<ProgramResource> getProgramResources() throws ResourceException {
-                return resources;
+              public Collection<ProgramResource> getProgramResources() {
+                return finalProgramResources;
+              }
+
+              @Override
+              public DataResourceProvider getDataResourceProvider() {
+                if (!finalDataResources.isEmpty()) {
+                  return new DataResourceProvider() {
+                    @Override
+                    public void accept(Visitor visitor) {
+                      for (DataEntryResource dataResource : finalDataResources) {
+                        visitor.visit(dataResource);
+                      }
+                    }
+                  };
+                }
+                return null;
               }
             });
         programResources.clear();
+        dataResources.clear();
       }
       return new AndroidApp(
           ImmutableList.copyOf(programResourceProviders),
@@ -560,9 +587,7 @@
       } else if (isClassFile(file)) {
         addProgramResources(ProgramResource.fromFile(Kind.CF, file));
       } else if (isArchive(file)) {
-        ArchiveResourceProvider archiveResourceProvider = new ArchiveResourceProvider(
-            FilteredClassPath.unfiltered(file), ignoreDexInArchive);
-        addProgramResourceProvider(archiveResourceProvider);
+        addProgramResourceProvider(ArchiveResourceProvider.fromArchive(file, ignoreDexInArchive));
       } else {
         throw new CompilationError("Unsupported source file type", new PathOrigin(file));
       }
@@ -576,6 +601,14 @@
       programResources.addAll(resources);
     }
 
+    private void addDataResources(DataEntryResource... resources) {
+      addDataResources(Arrays.asList(resources));
+    }
+
+    private void addDataResources(Collection<DataEntryResource> resources) {
+      this.dataResources.addAll(resources);
+    }
+
     private void addClasspathOrLibraryProvider(
         Path file, List<ClassFileResourceProvider> providerList) throws IOException {
       if (!Files.exists(file)) {
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
index a611105..e92d0a2 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveResourceProvider.java
@@ -21,6 +21,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -36,6 +37,10 @@
   private final FilteredClassPath archive;
   private final boolean ignoreDexInArchive;
 
+  public static ArchiveResourceProvider fromArchive(Path archive, boolean ignoreDexInArchive) {
+    return new ArchiveResourceProvider(FilteredClassPath.unfiltered(archive), ignoreDexInArchive);
+  }
+
   ArchiveResourceProvider(FilteredClassPath archive, boolean ignoreDexInArchive) {
     assert isArchive(archive.getPath());
     origin = new PathOrigin(archive.getPath());
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
index 31dc439..d177b36 100644
--- a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
+++ b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
@@ -9,12 +9,16 @@
 import com.android.tools.r8.dexsplitter.DexSplitter.FeatureJar;
 import com.android.tools.r8.origin.PathOrigin;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
 
 /**
  * Provides a mappings of classes to modules. The structure of the input file is as follows:
@@ -43,6 +47,7 @@
 public final class FeatureClassMapping {
 
   HashMap<String, String> parsedRules = new HashMap<>(); // Already parsed rules.
+  HashMap<String, String> parseNonClassRules = new HashMap<>();
   boolean usesOnlyExactMappings = true;
 
   HashSet<FeaturePredicate> mappings = new HashSet<>();
@@ -122,6 +127,19 @@
           String javaType = DescriptorUtils.descriptorToJavaType(javaDescriptor);
           mapping.addMapping(javaType, featureJar.getOutputName());
         }
+        try (ZipFile zipfile = new ZipFile(jarPath.toString(), StandardCharsets.UTF_8)) {
+          Enumeration<? extends ZipEntry> entries = zipfile.entries();
+          while (entries.hasMoreElements()) {
+            ZipEntry entry = entries.nextElement();
+            String name = entry.getName();
+            if (!ZipUtils.isClassFile(name)) {
+              mapping.addNonClassMapping(name, featureJar.getOutputName());
+            }
+          }
+        } catch (IOException e) {
+          reporter.error(new ExceptionDiagnostic(e, new JarFileOrigin(jarPath)));
+          throw new AbortException();
+        }
       }
       assert mapping.usesOnlyExactMappings;
       return mapping;
@@ -135,6 +153,11 @@
     addRule(clazz, feature, 0);
   }
 
+  public void addNonClassMapping(String name, String feature) {
+    // If a non-class file is present in multiple features put the resource in the base.
+    parseNonClassRules.put(name, parseNonClassRules.containsKey(name) ? baseName : feature);
+  }
+
   FeatureClassMapping(List<String> lines) throws FeatureMappingException {
     for (int i = 0; i < lines.size(); i++) {
       String line = lines.get(i);
@@ -161,6 +184,10 @@
     }
   }
 
+  public String featureForNonClass(String nonClass) {
+    return parseNonClassRules.getOrDefault(nonClass, baseName);
+  }
+
   private void parseAndAdd(String line, int lineNumber) throws FeatureMappingException {
     if (line.startsWith(COMMENT)) {
       return; // Ignore comments
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 eb8ee0e..d3c9e6d 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -84,6 +84,7 @@
       enableNonNullTracking = false;
       enableInlining = false;
       enableClassInlining = false;
+      enableClassStaticizer = false;
       enableSwitchMapRemoval = false;
       outline.enabled = false;
       enableValuePropagation = false;
@@ -102,6 +103,7 @@
   public boolean enableInlining =
       !Version.isDev() || System.getProperty("com.android.tools.r8.disableinlining") == null;
   public boolean enableClassInlining = true;
+  public boolean enableClassStaticizer = true;
   public int classInliningInstructionLimit = 50;
   public int inliningInstructionLimit = 5;
   public boolean enableSwitchMapRemoval = true;
@@ -447,7 +449,9 @@
     public boolean placeExceptionalBlocksLast = false;
     public boolean dontCreateMarkerInD8 = false;
     public boolean forceJumboStringProcessing = false;
+    public boolean nondeterministicCycleElimination = false;
     public Set<Inliner.Reason> validInliningReasons = null;
+    public boolean suppressExperimentalCfBackendWarning = false;
   }
 
   public boolean canUseInvokePolymorphicOnVarHandle() {
@@ -535,7 +539,7 @@
   //
   // See b/69364976 and b/77996377.
   public boolean canHaveBoundsCheckEliminationBug() {
-    return minApiLevel <= AndroidApiLevel.L.getLevel();
+    return minApiLevel < AndroidApiLevel.M.getLevel();
   }
 
   // MediaTek JIT compilers for KitKat phones did not implement the not
@@ -629,7 +633,7 @@
   //
   // See b/78493232 and b/80118070.
   public boolean canHaveArtStringNewInitBug() {
-    return minApiLevel <= AndroidApiLevel.P.getLevel();
+    return minApiLevel < AndroidApiLevel.Q.getLevel();
   }
 
   // Dalvik tracing JIT may perform invalid optimizations when int/float values are converted to
@@ -637,6 +641,6 @@
   //
   // See b/77496850.
   public boolean canHaveNumberConversionRegisterAllocationBug() {
-    return minApiLevel <= AndroidApiLevel.K.getLevel();
+    return minApiLevel < AndroidApiLevel.L.getLevel();
   }
 }
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 7d7baf8..66490b2 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.ClassNaming;
@@ -35,6 +36,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.Function;
 import java.util.function.Supplier;
 
 public class LineNumberOptimizer {
@@ -202,8 +204,10 @@
   }
 
   public static ClassNameMapper run(
-      DexApplication application, NamingLens namingLens, boolean identityMapping) {
-    IdentityHashMap<DexString, List<DexProgramClass>> classesOfFiles = new IdentityHashMap<>();
+      DexApplication application,
+      GraphLense graphLense,
+      NamingLens namingLens,
+      boolean identityMapping) {
     ClassNameMapper.Builder classNameMapperBuilder = ClassNameMapper.builder();
     // Collect which files contain which classes that need to have their line numbers optimized.
     for (DexProgramClass clazz : application.classes()) {
@@ -213,8 +217,8 @@
         continue;
       }
 
-      IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByName =
-          groupMethodsByName(namingLens, clazz);
+      IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByRenamedName =
+          groupMethodsByRenamedName(namingLens, clazz);
 
       // At this point we don't know if we really need to add this class to the builder.
       // It depends on whether any methods/fields are renamed or some methods contain positions.
@@ -231,10 +235,13 @@
       addClassToClassNaming(clazz, renamedClassName, onDemandClassNamingBuilder);
 
       // First transfer renamed fields to classNamingBuilder.
-      addFieldsToClassNaming(namingLens, clazz, onDemandClassNamingBuilder);
+      addFieldsToClassNaming(graphLense, namingLens, clazz, onDemandClassNamingBuilder);
 
-      // Then process the methods.
-      for (List<DexEncodedMethod> methods : methodsByName.values()) {
+      // Then process the methods, ordered by renamed name.
+      List<DexString> renamedMethodNames = new ArrayList<>(methodsByRenamedName.keySet());
+      renamedMethodNames.sort(DexString::slowCompareTo);
+      for (DexString methodName : renamedMethodNames) {
+        List<DexEncodedMethod> methods = methodsByRenamedName.get(methodName);
         if (methods.size() > 1) {
           // If there are multiple methods with the same name (overloaded) then sort them for
           // deterministic behaviour: the algorithm will assign new line numbers in this order.
@@ -259,7 +266,9 @@
             }
           }
 
-          MethodSignature originalSignature = MethodSignature.fromDexMethod(method.method);
+          DexMethod originalMethod = graphLense.getOriginalMethodSignature(method.method);
+          MethodSignature originalSignature =
+              MethodSignature.fromDexMethod(originalMethod, originalMethod.holder != clazz.type);
 
           DexString obfuscatedNameDexString = namingLens.lookupName(method.method);
           String obfuscatedName = obfuscatedNameDexString.toString();
@@ -267,7 +276,8 @@
           // Add simple "a() -> b" mapping if we won't have any other with concrete line numbers
           if (mappedPositions.isEmpty()) {
             // But only if it's been renamed.
-            if (obfuscatedNameDexString != method.method.name) {
+            if (obfuscatedNameDexString != originalMethod.name
+                || originalMethod.holder != clazz.type) {
               onDemandClassNamingBuilder
                   .get()
                   .addMappedRange(null, originalSignature, null, obfuscatedName);
@@ -276,7 +286,16 @@
           }
 
           Map<DexMethod, MethodSignature> signatures = new IdentityHashMap<>();
-          signatures.put(method.method, originalSignature);
+          signatures.put(originalMethod, originalSignature);
+          Function<DexMethod, MethodSignature> getOriginalMethodSignature =
+              m -> {
+                DexMethod original = graphLense.getOriginalMethodSignature(m);
+                return signatures.computeIfAbsent(
+                    original,
+                    key ->
+                        MethodSignature.fromDexMethod(
+                            original, original.holder != clazz.getType()));
+              };
 
           MemberNaming memberNaming = new MemberNaming(originalSignature, obfuscatedName);
           onDemandClassNamingBuilder.get().addMemberEntry(memberNaming);
@@ -307,23 +326,14 @@
             ClassNaming.Builder classNamingBuilder = onDemandClassNamingBuilder.get();
             classNamingBuilder.addMappedRange(
                 obfuscatedRange,
-                signatures.computeIfAbsent(
-                    firstPosition.method,
-                    m ->
-                        MethodSignature.fromDexMethod(
-                            m, firstPosition.method.holder != clazz.getType())),
+                getOriginalMethodSignature.apply(firstPosition.method),
                 originalRange,
                 obfuscatedName);
             Position caller = firstPosition.caller;
             while (caller != null) {
-              Position finalCaller = caller;
               classNamingBuilder.addMappedRange(
                   obfuscatedRange,
-                  signatures.computeIfAbsent(
-                      caller.method,
-                      m ->
-                          MethodSignature.fromDexMethod(
-                              m, finalCaller.method.holder != clazz.getType())),
+                  getOriginalMethodSignature.apply(caller.method),
                   Math.max(caller.line, 0), // Prevent against "no-position".
                   obfuscatedName);
               caller = caller.callerPosition;
@@ -379,42 +389,37 @@
     }
   }
 
-  private static void addFieldsToClassNaming(NamingLens namingLens, DexProgramClass clazz,
+  private static void addFieldsToClassNaming(
+      GraphLense graphLense,
+      NamingLens namingLens,
+      DexProgramClass clazz,
       Supplier<Builder> onDemandClassNamingBuilder) {
     clazz.forEachField(
         dexEncodedField -> {
           DexField dexField = dexEncodedField.field;
+          DexField originalField = graphLense.getOriginalFieldSignature(dexField);
           DexString renamedName = namingLens.lookupName(dexField);
-          if (renamedName != dexField.name) {
-            FieldSignature signature =
-                new FieldSignature(dexField.name.toString(), dexField.type.toString());
-            MemberNaming memberNaming = new MemberNaming(signature, renamedName.toString());
+          if (renamedName != originalField.name || originalField.clazz != clazz.type) {
+            FieldSignature originalSignature =
+                FieldSignature.fromDexField(originalField, originalField.clazz != clazz.type);
+            MemberNaming memberNaming = new MemberNaming(originalSignature, renamedName.toString());
             onDemandClassNamingBuilder.get().addMemberEntry(memberNaming);
           }
         });
   }
 
-  private static IdentityHashMap<DexString, List<DexEncodedMethod>> groupMethodsByName(
+  private static IdentityHashMap<DexString, List<DexEncodedMethod>> groupMethodsByRenamedName(
       NamingLens namingLens, DexProgramClass clazz) {
-    IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByName =
+    IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByRenamedName =
         new IdentityHashMap<>(clazz.directMethods().length + clazz.virtualMethods().length);
-    clazz.forEachMethod(
-        method -> {
-          // Add method only if renamed or contains positions.
-          if (namingLens.lookupName(method.method) != method.method.name
-              || doesContainPositions(method)) {
-            methodsByName.compute(
-                method.method.name,
-                (name, methods) -> {
-                  if (methods == null) {
-                    methods = new ArrayList<>();
-                  }
-                  methods.add(method);
-                  return methods;
-                });
-          }
-        });
-    return methodsByName;
+    for (DexEncodedMethod method : clazz.methods()) {
+      // Add method only if renamed or contains positions.
+      DexString renamedName = namingLens.lookupName(method.method);
+      if (renamedName != method.method.name || doesContainPositions(method)) {
+        methodsByRenamedName.computeIfAbsent(renamedName, key -> new ArrayList<>()).add(method);
+      }
+    }
+    return methodsByRenamedName;
   }
 
   private static boolean doesContainPositions(DexEncodedMethod method) {
diff --git a/src/test/examples/adaptresourcefilenames/A.java b/src/test/examples/adaptresourcefilenames/A.java
new file mode 100644
index 0000000..c83e8c5
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/A.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package adaptresourcefilenames;
+
+public class A {
+
+  public void method() {
+    System.out.println("In A.method()");
+  }
+}
diff --git a/src/test/examples/adaptresourcefilenames/B.java b/src/test/examples/adaptresourcefilenames/B.java
new file mode 100644
index 0000000..79725cb
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/B.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package adaptresourcefilenames;
+
+public class B extends A {
+
+  private Inner inner = new Inner();
+
+  public static class Inner {
+
+    public void method() {
+      System.out.println("In Inner.method()");
+    }
+  }
+
+  public void method() {
+    System.out.println("In B.method()");
+    super.method();
+    inner.method();
+  }
+}
diff --git a/src/test/examples/adaptresourcefilenames/TestClass.java b/src/test/examples/adaptresourcefilenames/TestClass.java
new file mode 100644
index 0000000..a5e96e5
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/TestClass.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package adaptresourcefilenames;
+
+import adaptresourcefilenames.pkg.C;
+import adaptresourcefilenames.pkg.innerpkg.D;
+
+public class TestClass {
+
+  public static void main(String[] args) {
+    new B().method();
+    new C().method();
+    new D().method();
+  }
+}
diff --git a/src/test/examples/adaptresourcefilenames/pkg/C.java b/src/test/examples/adaptresourcefilenames/pkg/C.java
new file mode 100644
index 0000000..93d4446
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/pkg/C.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package adaptresourcefilenames.pkg;
+
+public class C {
+
+  public void method() {
+    System.out.println("In C.method()");
+  }
+}
diff --git a/src/test/examples/adaptresourcefilenames/pkg/innerpkg/D.java b/src/test/examples/adaptresourcefilenames/pkg/innerpkg/D.java
new file mode 100644
index 0000000..2107547
--- /dev/null
+++ b/src/test/examples/adaptresourcefilenames/pkg/innerpkg/D.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package adaptresourcefilenames.pkg.innerpkg;
+
+public class D {
+
+  public void method() {
+    System.out.println("In D.method()");
+  }
+}
diff --git a/src/test/examples/assumevalues6/Assumevalues.java b/src/test/examples/assumevalues6/Assumevalues.java
new file mode 100644
index 0000000..b3ad1de
--- /dev/null
+++ b/src/test/examples/assumevalues6/Assumevalues.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2018 the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package assumevalues6;
+
+public class Assumevalues {
+  public static int field = 0;
+  public static int field2 = 2;
+  public static int field3 = 2;
+
+  public static void main(String[] args) {
+    if (0 > field) {
+      System.out.println("NOPE1");
+    }
+    if (field < 0) {
+      System.out.println("NOPE2");
+    }
+    if (field3 == 0) {
+      System.out.println("NOPE3");
+    }
+    if (field3 != 0) {
+      System.out.println("YUP1");
+    }
+    if (2 < field) {
+      System.out.println("NOPE4");
+    }
+    if (field > 2) {
+      System.out.println("NOPE5");
+    }
+    if (field2 < field) {
+      System.out.println("NOPE6");
+    }
+    if (field > field2) {
+      System.out.println("NOPE7");
+    }
+    if (field <= field2) {
+      System.out.println("YUP2");
+    }
+    if (field2 >= field) {
+      System.out.println("YUP3");
+    }
+    System.out.println("OK");
+  }
+}
diff --git a/src/test/examples/assumevalues6/keep-rules.txt b/src/test/examples/assumevalues6/keep-rules.txt
new file mode 100644
index 0000000..4910a9d
--- /dev/null
+++ b/src/test/examples/assumevalues6/keep-rules.txt
@@ -0,0 +1,16 @@
+# Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep class assumevalues6.Assumevalues {
+  void main(...);
+}
+
+# Mark some fields with value ranges.
+-assumevalues public class assumevalues6.Assumevalues {
+  int field return 0..2;
+  int field2 return 2..4;
+  int field3 return 2..2;
+}
diff --git a/src/test/examples/classmerging/CallGraphCycleTest.java b/src/test/examples/classmerging/CallGraphCycleTest.java
new file mode 100644
index 0000000..40347d6
--- /dev/null
+++ b/src/test/examples/classmerging/CallGraphCycleTest.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package classmerging;
+
+public class CallGraphCycleTest {
+
+  public static void main(String[] args) {
+    new B(true);
+  }
+
+  public static class A {
+
+    public A(boolean instantiateB) {
+      if (instantiateB) {
+        new B(false);
+      }
+      System.out.println("A(" + instantiateB + ")");
+    }
+  }
+
+  public static class B extends A {
+
+    public B(boolean instantiateBinA) {
+      super(instantiateBinA);
+      System.out.println("B(" + instantiateBinA + ")");
+    }
+  }
+}
diff --git a/src/test/examples/classmerging/ProguardFieldMapTest.java b/src/test/examples/classmerging/ProguardFieldMapTest.java
new file mode 100644
index 0000000..0cbf8a1
--- /dev/null
+++ b/src/test/examples/classmerging/ProguardFieldMapTest.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package classmerging;
+
+public class ProguardFieldMapTest {
+
+  public static void main(String[] args) {
+    B b = new B();
+    b.test();
+  }
+
+  public static class A {
+
+    public String f = "A.f";
+  }
+
+  public static class B extends A {
+
+    public void test() {
+      System.out.println(f);
+    }
+  }
+}
diff --git a/src/test/examples/classmerging/ProguardMethodMapTest.java b/src/test/examples/classmerging/ProguardMethodMapTest.java
new file mode 100644
index 0000000..9ce0ad7
--- /dev/null
+++ b/src/test/examples/classmerging/ProguardMethodMapTest.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package classmerging;
+
+public class ProguardMethodMapTest {
+
+  public static void main(String[] args) {
+    B b = new B();
+    b.method();
+  }
+
+  public static class A {
+
+    public void method() {
+      System.out.println("In A.method()");
+    }
+  }
+
+  public static class B extends A {
+
+    @Override
+    public void method() {
+      System.out.println("In B.method()");
+      super.method();
+    }
+  }
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 4ba014e..3a70504 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -7,6 +7,9 @@
 -keep public class classmerging.Test {
   public static void main(...);
 }
+-keep public class classmerging.CallGraphCycleTest {
+  public static void main(...);
+}
 -keep public class classmerging.ClassWithNativeMethodTest {
   public static void main(...);
 }
@@ -34,6 +37,12 @@
 -keep public class classmerging.PinnedParameterTypesTest$TestClass {
   public static void method(...);
 }
+-keep public class classmerging.ProguardFieldMapTest {
+  public static void main(...);
+}
+-keep public class classmerging.ProguardMethodMapTest {
+  public static void main(...);
+}
 -keep public class classmerging.SimpleInterfaceAccessTest {
   public static void main(...);
 }
diff --git a/src/test/examples/identifiernamestring/keep-rules-3.txt b/src/test/examples/identifiernamestring/keep-rules-3.txt
index aa23772..1c69314 100644
--- a/src/test/examples/identifiernamestring/keep-rules-3.txt
+++ b/src/test/examples/identifiernamestring/keep-rules-3.txt
@@ -11,7 +11,7 @@
 
 -dontshrink
 
--identifiernamestring class * {
+-identifiernamestring class **.R {
   static java.lang.reflect.Field *(java.lang.Class, java.lang.String);
   static java.lang.reflect.Method *(java.lang.Class, java.lang.String, java.lang.Class[]);
 }
diff --git a/src/test/examples/inlining/A.java b/src/test/examples/inlining/A.java
index dd857c8..a7aa4d2 100644
--- a/src/test/examples/inlining/A.java
+++ b/src/test/examples/inlining/A.java
@@ -6,15 +6,21 @@
 class A {
 
   int a;
+  int b;
 
   A(int a) {
     this.a = a;
+    this.b = a + 1;
   }
 
   int a() {
     return a;
   }
 
+  int b() {
+    return b;
+  }
+
   int cannotInline(int v) {
     // Cannot inline due to recursion.
     if (v > 0) {
diff --git a/src/test/examples/inlining/Nullability.java b/src/test/examples/inlining/Nullability.java
index 74747d0..95422f0 100644
--- a/src/test/examples/inlining/Nullability.java
+++ b/src/test/examples/inlining/Nullability.java
@@ -43,8 +43,8 @@
   }
 
   int conditionalOperator(A a) {
-    // a is not null when a.a() is invoked.
-    return a != null ? a.a() : -1;
+    // a is not null when a.b() is invoked.
+    return a != null ? a.b() : -1;
   }
 
   int notInlinableOnThrow(Throwable t) throws Throwable {
@@ -97,8 +97,8 @@
     // instructions with side effects.
     if (a != null && result != 0) {
       // Thus, the invocation below is the first instruction with side effect.
-      // Also, a is not null here, hence a.a() is inlinable.
-      result *= a.a();
+      // Also, a is not null here, hence a.b() is inlinable.
+      result *= a.b();
     }
     return result;
   }
diff --git a/src/test/examples/shaking1/print-mapping-cf.ref b/src/test/examples/shaking1/print-mapping-cf.ref
new file mode 100644
index 0000000..9bf8013
--- /dev/null
+++ b/src/test/examples/shaking1/print-mapping-cf.ref
@@ -0,0 +1,7 @@
+shaking1.Shaking -> shaking1.Shaking:
+shaking1.Used -> a.a:
+    java.lang.String name -> a
+    1:1:java.lang.String method():17:17 -> a
+    1:1:void main(java.lang.String[]):8:8 -> main
+    1:2:void <init>(java.lang.String):12:13 -> <init>
+    1:1:java.lang.String aMethodThatIsNotUsedButKept():21:21 -> aMethodThatIsNotUsedButKept
diff --git a/src/test/examplesAndroidN/dexsplitsample/TextFile.txt b/src/test/examplesAndroidN/dexsplitsample/TextFile.txt
new file mode 100644
index 0000000..0bad44c
--- /dev/null
+++ b/src/test/examplesAndroidN/dexsplitsample/TextFile.txt
@@ -0,0 +1,9 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
+et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
+aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
+culpa qui officia deserunt mollit anim id est laborum.
diff --git a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
index 4f749b5..70e8445 100644
--- a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
@@ -303,6 +303,7 @@
     R8Command command =
         R8Command.builder()
             .addProgramFiles(inputJar)
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setMode(CompilationMode.DEBUG)
             .setOutput(outputJar, OutputMode.ClassFile)
             .build();
diff --git a/src/test/java/com/android/tools/r8/R8CFExamplesTests.java b/src/test/java/com/android/tools/r8/R8CFExamplesTests.java
index 377bfc0..2dbfb37 100644
--- a/src/test/java/com/android/tools/r8/R8CFExamplesTests.java
+++ b/src/test/java/com/android/tools/r8/R8CFExamplesTests.java
@@ -101,7 +101,7 @@
     Path outputJar = temp.getRoot().toPath().resolve(outputName);
     ToolHelper.runR8(
         R8Command.builder()
-            .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setMode(mode)
             .addProgramFiles(inputJar)
             .setOutput(outputJar, OutputMode.ClassFile)
diff --git a/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
index b3950b4..f0e758d 100644
--- a/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
@@ -8,8 +8,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -37,7 +36,7 @@
       for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
         builder = transformation.apply(builder);
       }
-      builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P));
+      builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
       R8Command command =
           builder.addProgramFiles(inputFile).setOutput(out, OutputMode.ClassFile).build();
       ToolHelper.runR8(command, this::combinedOptionConsumer);
@@ -62,8 +61,8 @@
       }
 
       if (!dexInspectorChecks.isEmpty()) {
-        DexInspector inspector = new DexInspector(out);
-        for (Consumer<DexInspector> check : dexInspectorChecks) {
+        CodeInspector inspector = new CodeInspector(out);
+        for (Consumer<CodeInspector> check : dexInspectorChecks) {
           check.accept(inspector);
         }
       }
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 7328c6e..180a5f8 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -12,18 +12,17 @@
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.DexVm.Kind;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.ArtErrorParser;
 import com.android.tools.r8.utils.ArtErrorParser.ArtErrorInfo;
-import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.base.Charsets;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableListMultimap;
@@ -2060,10 +2059,10 @@
 
   private void failWithDexDiff(File originalFile, File processedFile)
       throws IOException, ExecutionException {
-    DexInspector inspectOriginal =
-        new DexInspector(originalFile.toPath().toAbsolutePath());
-    DexInspector inspectProcessed =
-        new DexInspector(processedFile.toPath().toAbsolutePath());
+    CodeInspector inspectOriginal =
+        new CodeInspector(originalFile.toPath().toAbsolutePath());
+    CodeInspector inspectProcessed =
+        new CodeInspector(processedFile.toPath().toAbsolutePath());
     StringBuilder builderOriginal = new StringBuilder();
     StringBuilder builderProcessed = new StringBuilder();
     inspectOriginal.forAllClasses((clazz) -> builderOriginal.append(clazz.dumpMethods()));
@@ -2078,10 +2077,10 @@
     List<ComparisonFailure> errors;
     try {
       // Parse all the verification errors.
-      DexInspector processed = new DexInspector(processedFile.toPath());
-      DexInspector original = DEX_COMPARE_WITH_DEX_REFERENCE_ON_FAILURE
-          ? new DexInspector(referenceFile.toPath())
-          : new DexInspector(inputFiles.stream().map(Paths::get).collect(Collectors.toList()));
+      CodeInspector processed = new CodeInspector(processedFile.toPath());
+      CodeInspector original = DEX_COMPARE_WITH_DEX_REFERENCE_ON_FAILURE
+          ? new CodeInspector(referenceFile.toPath())
+          : new CodeInspector(inputFiles.stream().map(Paths::get).collect(Collectors.toList()));
       List<ArtErrorInfo> errorInfo = ArtErrorParser.parse(verificationError.getMessage());
       errors = ListUtils.map(errorInfo, (error) ->
           new ComparisonFailure(
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index f20335b..71f4559 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -4,12 +4,16 @@
 
 package com.android.tools.r8;
 
+import static org.junit.Assert.assertEquals;
+
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.OffOrAuto;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
@@ -94,6 +98,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 179, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -101,6 +106,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 23, "lambdadesugaring"))
         .run();
   }
 
@@ -112,6 +118,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 179, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -119,6 +126,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 23, "lambdadesugaring"))
         .run();
   }
 
@@ -131,6 +139,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -139,6 +148,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
         .run();
   }
 
@@ -151,6 +161,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 40, "lambdadesugaringnplus"))
         .run();
 
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
@@ -159,9 +170,21 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = true)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS_N_PLUS, Origin.unknown()))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 5, "lambdadesugaringnplus"))
         .run();
   }
 
+  private void checkLambdaCount(CodeInspector inspector, int expectedCount, String prefix) {
+    int count = 0;
+    for (FoundClassSubject clazz : inspector.allClasses()) {
+      if (clazz.isSynthesizedJavaLambdaClass() &&
+          clazz.getOriginalName().startsWith(prefix)) {
+        count++;
+      }
+    }
+    assertEquals(expectedCount, count);
+  }
+
   class R8TestRunner extends TestRunner<R8TestRunner> {
 
     R8TestRunner(String testName, String packageName, String mainClass) {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 3d3b4e7..008c6b9 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -161,11 +161,15 @@
         break;
       }
       case R8: {
-        R8Command command =
-            addInputFile(R8Command.builder())
-                .setOutput(getOutputFile(), outputMode)
-                .setMode(mode)
-                .build();
+          R8Command command =
+              addInputFile(R8Command.builder())
+                  .addLibraryFiles(
+                      output == Output.CF
+                          ? ToolHelper.getJava8RuntimeJar()
+                          : ToolHelper.getDefaultAndroidJar())
+                  .setOutput(getOutputFile(), outputMode)
+                  .setMode(mode)
+                  .build();
         ExceptionUtils.withR8CompilationHandler(
             command.getReporter(),
             () ->
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index b0876c9..f638575 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -14,14 +14,14 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
-import com.android.tools.r8.utils.DexInspector.FoundMethodSubject;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
-import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OffOrAuto;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.io.ByteStreams;
@@ -49,7 +49,6 @@
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
-import org.junit.rules.TestWatcher;
 
 public abstract class RunExamplesAndroidOTest
       <B extends BaseCommand.Builder<? extends BaseCommand, B>> {
@@ -63,7 +62,7 @@
     AndroidApiLevel androidJarVersion = null;
 
     final List<Consumer<InternalOptions>> optionConsumers = new ArrayList<>();
-    final List<Consumer<DexInspector>> dexInspectorChecks = new ArrayList<>();
+    final List<Consumer<CodeInspector>> dexInspectorChecks = new ArrayList<>();
     final List<UnaryOperator<B>> builderTransformations = new ArrayList<>();
 
     TestRunner(String testName, String packageName, String mainClass) {
@@ -74,7 +73,7 @@
 
     abstract C self();
 
-    C withDexCheck(Consumer<DexInspector> check) {
+    C withDexCheck(Consumer<CodeInspector> check) {
       dexInspectorChecks.add(check);
       return self();
     }
@@ -156,8 +155,8 @@
       }
 
       if (!dexInspectorChecks.isEmpty()) {
-        DexInspector inspector = new DexInspector(out);
-        for (Consumer<DexInspector> check : dexInspectorChecks) {
+        CodeInspector inspector = new CodeInspector(out);
+        for (Consumer<CodeInspector> check : dexInspectorChecks) {
           check.accept(inspector);
         }
       }
@@ -496,10 +495,10 @@
             mainDexClasses);
 
     // Collect main dex types.
-    DexInspector fullInspector =  getMainDexInspector(fullDexes);
-    DexInspector indexedIntermediateInspector =
+    CodeInspector fullInspector =  getMainDexInspector(fullDexes);
+    CodeInspector indexedIntermediateInspector =
         getMainDexInspector(dexesThroughIndexedIntermediate);
-    DexInspector filePerInputClassIntermediateInspector =
+    CodeInspector filePerInputClassIntermediateInspector =
         getMainDexInspector(dexesThroughFilePerInputClassIntermediate);
     Collection<String> fullMainClasses = new HashSet<>();
     fullInspector.forAllClasses(
@@ -579,12 +578,12 @@
     }
   }
 
-  protected DexInspector getMainDexInspector(Path zip)
+  protected CodeInspector getMainDexInspector(Path zip)
       throws ZipException, IOException, ExecutionException {
     try (ZipFile zipFile = new ZipFile(zip.toFile(), StandardCharsets.UTF_8)) {
       try (InputStream in =
           zipFile.getInputStream(zipFile.getEntry(ToolHelper.DEFAULT_DEX_FILENAME))) {
-        return new DexInspector(
+        return new CodeInspector(
             AndroidApp.builder()
                 .addDexProgramData(ByteStreams.toByteArray(in), Origin.unknown())
                 .build());
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
index 4701ac3..7f15eca 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
@@ -13,13 +13,13 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
-import com.android.tools.r8.utils.DexInspector.FoundMethodSubject;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OffOrAuto;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.io.ByteStreams;
@@ -57,7 +57,7 @@
     Integer androidJarVersion = null;
 
     final List<Consumer<InternalOptions>> optionConsumers = new ArrayList<>();
-    final List<Consumer<DexInspector>> dexInspectorChecks = new ArrayList<>();
+    final List<Consumer<CodeInspector>> dexInspectorChecks = new ArrayList<>();
     final List<UnaryOperator<B>> builderTransformations = new ArrayList<>();
 
     TestRunner(String testName, String packageName, String mainClass) {
@@ -68,7 +68,7 @@
 
     abstract C self();
 
-    C withDexCheck(Consumer<DexInspector> check) {
+    C withDexCheck(Consumer<CodeInspector> check) {
       dexInspectorChecks.add(check);
       return self();
     }
@@ -150,8 +150,8 @@
       }
 
       if (!dexInspectorChecks.isEmpty()) {
-        DexInspector inspector = new DexInspector(out);
-        for (Consumer<DexInspector> check : dexInspectorChecks) {
+        CodeInspector inspector = new CodeInspector(out);
+        for (Consumer<CodeInspector> check : dexInspectorChecks) {
           check.accept(inspector);
         }
       }
@@ -255,12 +255,12 @@
     }
   }
 
-  protected DexInspector getMainDexInspector(Path zip)
+  protected CodeInspector getMainDexInspector(Path zip)
       throws ZipException, IOException, ExecutionException {
     try (ZipFile zipFile = new ZipFile(zip.toFile(), StandardCharsets.UTF_8)) {
       try (InputStream in =
           zipFile.getInputStream(zipFile.getEntry(ToolHelper.DEFAULT_DEX_FILENAME))) {
-        return new DexInspector(
+        return new CodeInspector(
             AndroidApp.builder()
                 .addDexProgramData(ByteStreams.toByteArray(in), Origin.unknown())
                 .build());
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
index 60ebf39..f149cba 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
@@ -14,15 +14,15 @@
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
-import com.android.tools.r8.utils.DexInspector.FoundMethodSubject;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.OffOrAuto;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import java.io.IOException;
@@ -54,7 +54,7 @@
     Integer androidJarVersion = null;
 
     final List<Consumer<InternalOptions>> optionConsumers = new ArrayList<>();
-    final List<Consumer<DexInspector>> dexInspectorChecks = new ArrayList<>();
+    final List<Consumer<CodeInspector>> dexInspectorChecks = new ArrayList<>();
     final List<UnaryOperator<B>> builderTransformations = new ArrayList<>();
 
     TestRunner(String testName, String packageName, String mainClass) {
@@ -65,7 +65,7 @@
 
     abstract C self();
 
-    C withDexCheck(Consumer<DexInspector> check) {
+    C withDexCheck(Consumer<CodeInspector> check) {
       dexInspectorChecks.add(check);
       return self();
     }
@@ -147,8 +147,8 @@
       }
 
       if (!dexInspectorChecks.isEmpty()) {
-        DexInspector inspector = new DexInspector(out);
-        for (Consumer<DexInspector> check : dexInspectorChecks) {
+        CodeInspector inspector = new CodeInspector(out);
+        for (Consumer<CodeInspector> check : dexInspectorChecks) {
           check.accept(inspector);
         }
       }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 8adddfd..d6abe44 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.DataResourceProvider.Visitor;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.code.Instruction;
@@ -20,14 +21,14 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.PreloadedClassFileProvider;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
 import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -40,6 +41,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -54,6 +56,11 @@
 
 public class TestBase {
 
+  protected enum Backend {
+    CF,
+    DEX
+  };
+
   // Actually running Proguard should only be during development.
   private static final boolean RUN_PROGUARD = System.getProperty("run_proguard") != null;
   // Actually running r8.jar in a forked process.
@@ -180,17 +187,25 @@
    * Create a temporary JAR file containing the specified test classes.
    */
   protected Path jarTestClasses(Class... classes) throws IOException {
+    return jarTestClasses(Arrays.asList(classes), null);
+  }
+
+  /** Create a temporary JAR file containing the specified test classes and data resources. */
+  protected Path jarTestClasses(Iterable<Class> classes, List<DataEntryResource> dataResources)
+      throws IOException {
     Path jar = File.createTempFile("junit", ".jar", temp.getRoot()).toPath();
     try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar.toFile()))) {
       addTestClassesToJar(out, classes);
+      if (dataResources != null) {
+        addDataResourcesToJar(out, dataResources);
+      }
     }
     return jar;
   }
 
-  /**
-   * Create a temporary JAR file containing the specified test classes.
-   */
-  protected void addTestClassesToJar(JarOutputStream out, Class... classes) throws IOException {
+  /** Create a temporary JAR file containing the specified test classes. */
+  protected void addTestClassesToJar(JarOutputStream out, Iterable<Class> classes)
+      throws IOException {
     for (Class clazz : classes) {
       try (FileInputStream in =
           new FileInputStream(ToolHelper.getClassFileForTestClass(clazz).toFile())) {
@@ -201,6 +216,67 @@
     }
   }
 
+  /** Create a temporary JAR file containing the specified data resources. */
+  protected void addDataResourcesToJar(JarOutputStream out, List<DataEntryResource> dataResources)
+      throws IOException {
+    try {
+      for (DataEntryResource dataResource : dataResources) {
+        out.putNextEntry(new ZipEntry(dataResource.getName()));
+        ByteStreams.copy(dataResource.getByteStream(), out);
+        out.closeEntry();
+      }
+    } catch (ResourceException e) {
+      throw new IOException("Resource error", e);
+    }
+  }
+
+  /**
+   * Creates a new, temporary JAR that contains all the entries from the given JAR as well as the
+   * specified data resources. The given JAR is left unchanged.
+   */
+  protected Path addDataResourcesToExistingJar(
+      Path existingJar, List<DataEntryResource> dataResources) throws IOException {
+    Path newJar = File.createTempFile("app", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+    try (JarOutputStream out = new JarOutputStream(new FileOutputStream(newJar.toFile()))) {
+      ArchiveProgramResourceProvider.fromArchive(existingJar)
+          .readArchive(
+              (entry, stream) -> {
+                out.putNextEntry(new ZipEntry(entry.getEntryName()));
+                ByteStreams.copy(stream, out);
+                out.closeEntry();
+              });
+      addDataResourcesToJar(out, dataResources);
+    }
+    return newJar;
+  }
+
+  /** Returns a list containing all the data resources in the given app. */
+  public static List<DataEntryResource> getDataResources(AndroidApp app) throws ResourceException {
+    List<DataEntryResource> dataResources = new ArrayList<>();
+    for (ProgramResourceProvider programResourceProvider : app.getProgramResourceProviders()) {
+      dataResources.addAll(getDataResources(programResourceProvider.getDataResourceProvider()));
+    }
+    return dataResources;
+  }
+
+  public static List<DataEntryResource> getDataResources(DataResourceProvider dataResourceProvider)
+      throws ResourceException {
+    List<DataEntryResource> dataResources = new ArrayList<>();
+    if (dataResourceProvider != null) {
+      dataResourceProvider.accept(
+          new Visitor() {
+            @Override
+            public void visit(DataDirectoryResource directory) {}
+
+            @Override
+            public void visit(DataEntryResource file) {
+              dataResources.add(file);
+            }
+          });
+    }
+    return dataResources;
+  }
+
   /**
    * Create a temporary JAR file containing all test classes in a package.
    */
@@ -528,6 +604,25 @@
     return result.stdout;
   }
 
+  /** Run application on Java with the specified main class and provided arguments. */
+  protected String runOnJava(AndroidApp app, Class mainClass, String... args) throws IOException {
+    return runOnJava(app, mainClass, Arrays.asList(args));
+  }
+
+  /** Run application on Java with the specified main class and provided arguments. */
+  protected String runOnJava(AndroidApp app, Class mainClass, List<String> args)
+      throws IOException {
+    return runOnJava(app, mainClass.getCanonicalName(), args);
+  }
+
+  /** Run application on Java with the specified main class and provided arguments. */
+  protected String runOnJava(AndroidApp app, String mainClass, List<String> args)
+      throws IOException {
+    Path out = File.createTempFile("junit", ".zip", temp.getRoot()).toPath();
+    app.writeToZip(out, OutputMode.ClassFile);
+    return ToolHelper.runJava(out, mainClass).stdout;
+  }
+
   protected ProcessResult runOnJavaRaw(String main, byte[]... classes) throws IOException {
     return runOnJavaRaw(main, Arrays.asList(classes));
   }
@@ -575,7 +670,7 @@
   }
 
   protected DexEncodedMethod getMethod(
-      DexInspector inspector,
+      CodeInspector inspector,
       String className,
       String returnType,
       String methodName,
@@ -594,7 +689,7 @@
       String methodName,
       List<String> parameters) {
     try {
-      DexInspector inspector = new DexInspector(application);
+      CodeInspector inspector = new CodeInspector(application);
       return getMethod(inspector, className, returnType, methodName, parameters);
     } catch (Exception e) {
       return null;
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 2667ce2..2af67a4 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -73,11 +74,13 @@
   public static final String LIBS_DIR = BUILD_DIR + "libs/";
   public static final String TESTS_DIR = "src/test/";
   public static final String EXAMPLES_DIR = TESTS_DIR + "examples/";
+  public static final String EXAMPLES_ANDROID_N_DIR = TESTS_DIR + "examplesAndroidN/";
   public static final String EXAMPLES_ANDROID_O_DIR = TESTS_DIR + "examplesAndroidO/";
   public static final String EXAMPLES_ANDROID_P_DIR = TESTS_DIR + "examplesAndroidP/";
   public static final String EXAMPLES_KOTLIN_DIR = TESTS_DIR + "examplesKotlin/";
   public static final String TESTS_BUILD_DIR = BUILD_DIR + "test/";
   public static final String EXAMPLES_BUILD_DIR = TESTS_BUILD_DIR + "examples/";
+  public static final String EXAMPLES_CF_DIR = EXAMPLES_BUILD_DIR + "classes/";
   public static final String EXAMPLES_KOTLIN_BUILD_DIR = TESTS_BUILD_DIR + "examplesKotlin/";
   public static final String EXAMPLES_KOTLIN_RESOURCE_DIR =
       TESTS_BUILD_DIR + "kotlinR8TestResources/";
@@ -474,8 +477,9 @@
   public static byte[] getClassAsBytes(Class clazz) throws IOException {
     String s = clazz.getSimpleName() + ".class";
     Class outer = clazz.getEnclosingClass();
-    if (outer != null) {
+    while (outer != null) {
       s = outer.getSimpleName() + '$' + s;
+      outer = outer.getEnclosingClass();
     }
     return ByteStreams.toByteArray(clazz.getResourceAsStream(s));
   }
@@ -532,6 +536,10 @@
     return getArtDir(version) + "/" + binary;
   }
 
+  public static Path getJava8RuntimeJar() {
+    return Paths.get(JAVA_8_RUNTIME);
+  }
+
   public static Path getDefaultAndroidJar() {
     return getAndroidJar(AndroidApiLevel.getDefault());
   }
@@ -746,8 +754,11 @@
   }
 
   public static List<Path> getClassFilesForTestPackage(Package pkg) throws IOException {
-    Path dir = ToolHelper.getPackageDirectoryForTestPackage(pkg);
-    return Files.walk(dir)
+    return getClassFilesForTestDirectory(ToolHelper.getPackageDirectoryForTestPackage(pkg));
+  }
+
+  public static List<Path> getClassFilesForTestDirectory(Path directory) throws IOException {
+    return Files.walk(directory)
         .filter(path -> path.toString().endsWith(".class"))
         .collect(Collectors.toList());
   }
@@ -816,6 +827,13 @@
         .setProguardMapConsumer(StringConsumer.emptyConsumer());
   }
 
+  public static R8Command.Builder prepareR8CommandBuilder(
+      AndroidApp app, ProgramConsumer programConsumer, DiagnosticsHandler diagnosticsHandler) {
+    return R8Command.builder(app, diagnosticsHandler)
+        .setProgramConsumer(programConsumer)
+        .setProguardMapConsumer(StringConsumer.emptyConsumer());
+  }
+
   public static AndroidApp runR8(AndroidApp app) throws IOException {
     return runR8WithProgramConsumer(app, DexIndexedConsumer.emptyConsumer());
   }
@@ -1603,6 +1621,7 @@
         Executors.newSingleThreadExecutor(),
         application,
         null,
+        GraphLense.getIdentityLense(),
         NamingLens.getIdentityLens(),
         null,
         options,
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTestBase.java b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTestBase.java
new file mode 100644
index 0000000..80541e8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTestBase.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.accessrelaxation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+abstract class AccessRelaxationTestBase extends TestBase {
+
+  static R8Command.Builder loadProgramFiles(Iterable<Class> classes) {
+    R8Command.Builder builder = R8Command.builder();
+    for (Class clazz : classes) {
+      builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
+    }
+    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+    builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
+    return builder;
+  }
+
+  static R8Command.Builder loadProgramFiles(Package p, Class... classes) throws Exception {
+    R8Command.Builder builder = R8Command.builder();
+    builder.addProgramFiles(ToolHelper.getClassFilesForTestPackage(p));
+    for (Class clazz : classes) {
+      builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
+    }
+    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+    builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
+    return builder;
+  }
+
+  void compareJvmAndArt(AndroidApp app, Class mainClass) throws Exception {
+    // Run on Jvm.
+    String jvmOutput = runOnJava(mainClass);
+
+    // Run on Art to check generated code against verifier.
+    String artOutput = runOnArt(app, mainClass);
+
+    String adjustedArtOutput = artOutput.replace(
+        "java.lang.IncompatibleClassChangeError", "java.lang.IllegalAccessError");
+    assertEquals(jvmOutput, adjustedArtOutput);
+  }
+
+  static void assertPublic(CodeInspector codeInspector, Class clazz, MethodSignature signature) {
+    ClassSubject classSubject = codeInspector.clazz(clazz);
+    assertThat(classSubject, isPresent());
+    MethodSubject methodSubject = classSubject.method(signature);
+    assertThat(methodSubject, isPublic());
+  }
+
+  static void assertNotPublic(CodeInspector codeInspector, Class clazz, MethodSignature signature) {
+    ClassSubject classSubject = codeInspector.clazz(clazz);
+    assertThat(classSubject, isPresent());
+    MethodSubject methodSubject = classSubject.method(signature);
+    assertThat(methodSubject, not(isPublic()));
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java
new file mode 100644
index 0000000..d90ae45
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/ConstructorRelaxationTest.java
@@ -0,0 +1,198 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.accessrelaxation;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import java.util.List;
+import org.junit.Test;
+
+class L1 {
+  private final String x;
+
+  private L1(String x) {
+    this.x = x;
+  }
+
+  private L1() {
+    this("private_x");
+  }
+
+  static L1 create() {
+    return new L1();
+  }
+
+  L1(int i) {
+    this(String.valueOf(i));
+  }
+
+  @Override
+  public String toString() {
+    return x;
+  }
+}
+
+class L2_1 extends L1 {
+  private String y;
+
+  private L2_1() {
+    this(21);
+    this.y = "private_L2_1_y";
+  }
+
+  L2_1(int i) {
+    super(i);
+    this.y = "L2_1_y";
+  }
+
+  private L2_1(String y) {
+    this(21);
+    this.y = y;
+  }
+
+  static L2_1 create(String y) {
+    return new L2_1(y);
+  }
+
+  @Override
+  public String toString() {
+    return super.toString() + "_" + y;
+  }
+}
+
+class L2_2 extends L1 {
+  private String y;
+
+  private L2_2(int i) {
+    super(i);
+    this.y = "private_L2_2_y";
+  }
+
+  L2_2(String y) {
+    this(22);
+    this.y = y;
+  }
+
+  static L2_1 create() {
+    return new L2_1(22);
+  }
+
+  @Override
+  public String toString() {
+    return super.toString() + "_" + y;
+  }
+}
+
+class L3_1 extends L2_1 {
+  private final String z;
+
+  private L3_1(int i) {
+    this(String.valueOf(i));
+  }
+
+  private L3_1(String z) {
+    super(31);
+    this.z = z;
+  }
+
+  static L3_1 create(int i) {
+    return new L3_1(i);
+  }
+
+  @Override
+  public String toString() {
+    return super.toString() + "_" + z;
+  }
+}
+
+class L3_2 extends L2_2 {
+  private String z;
+
+  private L3_2() {
+    super("private_L3_2_y");
+    this.z = "private_L3_2_z";
+  }
+
+  private L3_2(int i) {
+    super(String.valueOf(i));
+    this.z = "private_L3_2_z" + "_" + i;
+  }
+
+  L3_2(String z) {
+    this(32);
+    this.z = z;
+  }
+
+  static L3_2 create(String z) {
+    return new L3_2(z);
+  }
+
+  @Override
+  public String toString() {
+    return super.toString() + "_" + z;
+  }
+}
+
+class CtorTestMain {
+  public static void main(String[] args) {
+    System.out.println(L1.create());
+    System.out.println(L2_1.create("main_y"));
+    System.out.println(L2_2.create());
+    System.out.println(L3_1.create(41));
+    System.out.println(L3_2.create("main_z"));
+  }
+}
+
+public final class ConstructorRelaxationTest extends AccessRelaxationTestBase {
+  private static final String INIT= "<init>";
+  private static final List<Class> CLASSES =
+      ImmutableList.of(L1.class, L2_1.class, L2_2.class, L3_1.class, L3_2.class);
+
+  @Test
+  public void test() throws Exception {
+    Class mainClass = CtorTestMain.class;
+    R8Command.Builder builder =
+        loadProgramFiles(Iterables.concat(CLASSES, ImmutableList.of(mainClass)));
+    builder.addProguardConfiguration(
+        ImmutableList.of(
+            "-keep class " + mainClass.getCanonicalName() + "{",
+            "  public static void main(java.lang.String[]);",
+            "}",
+            "",
+            "-keep class *.L* {",
+            "  <init>(...);",
+            "}",
+            "",
+            "-dontobfuscate",
+            "-allowaccessmodification"
+        ),
+        Origin.unknown());
+
+    AndroidApp app = ToolHelper.runR8(builder.build(), options -> {
+      options.enableInlining = false;
+      options.enableClassMerging = false;
+    });
+    compareJvmAndArt(app, mainClass);
+
+    CodeInspector codeInspector = new CodeInspector(app);
+    for (Class clazz : CLASSES) {
+      ClassSubject classSubject = codeInspector.clazz(clazz);
+      assertThat(classSubject, isPresent());
+      classSubject.getDexClass().forEachMethod(m -> {
+        assertTrue(!m.isInstanceInitializer() || m.isPublicMethod());
+      });
+    }
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java
index 1f97c11..787568b 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/InvokeTypeConversionTest.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.accessrelaxation;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -24,8 +24,8 @@
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.function.Consumer;
@@ -62,7 +62,7 @@
   private void run(
       SmaliBuilder builder,
       String expectedException,
-      Consumer<DexInspector> inspectorConsumer) throws Exception {
+      Consumer<CodeInspector> inspectorConsumer) throws Exception {
     AndroidApp app = buildApplication(builder);
     List<String> pgConfigs = ImmutableList.of(
         keepMainProguardConfiguration(CLASS_NAME),
@@ -82,7 +82,7 @@
       assertEquals(1, artResult.exitCode);
       assertThat(artResult.stderr, containsString(expectedException));
     }
-    DexInspector inspector = new DexInspector(processedApp);
+    CodeInspector inspector = new CodeInspector(processedApp);
     inspectorConsumer.accept(inspector);
   }
 
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
similarity index 61%
rename from src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
rename to src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
index 53a8c31..5b641dc 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/NonConstructorRelaxationTest.java
@@ -4,17 +4,9 @@
 
 package com.android.tools.r8.accessrelaxation;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPublic;
-import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
 
-import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8Command;
-import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.VmTestRunner;
 import com.android.tools.r8.accessrelaxation.privateinstance.Base;
 import com.android.tools.r8.accessrelaxation.privateinstance.Sub1;
 import com.android.tools.r8.accessrelaxation.privateinstance.Sub2;
@@ -26,56 +18,13 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
-import org.junit.runner.RunWith;
 
-@RunWith(VmTestRunner.class)
-public class AccessRelaxationTest extends TestBase {
+public final class NonConstructorRelaxationTest extends AccessRelaxationTestBase {
   private static final String STRING = "java.lang.String";
 
-  private static R8Command.Builder loadProgramFiles(Package p, Class... classes) throws Exception {
-    R8Command.Builder builder = R8Command.builder();
-    builder.addProgramFiles(ToolHelper.getClassFilesForTestPackage(p));
-    for (Class clazz : classes) {
-      builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
-    }
-    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
-    return builder;
-  }
-
-  private void compareJvmAndArt(AndroidApp app, Class mainClass) throws Exception {
-    // Run on Jvm.
-    String jvmOutput = runOnJava(mainClass);
-
-    // Run on Art to check generated code against verifier.
-    String artOutput = runOnArt(app, mainClass);
-
-    String adjustedArtOutput = artOutput.replace(
-        "java.lang.IncompatibleClassChangeError", "java.lang.IllegalAccessError");
-    assertEquals(jvmOutput, adjustedArtOutput);
-  }
-
-  private static void assertPublic(
-      DexInspector dexInspector, Class clazz, MethodSignature signature) {
-    ClassSubject classSubject = dexInspector.clazz(clazz);
-    assertThat(classSubject, isPresent());
-    MethodSubject methodSubject = classSubject.method(signature);
-    assertThat(methodSubject, isPublic());
-  }
-
-  private static void assertNotPublic(
-      DexInspector dexInspector, Class clazz, MethodSignature signature) {
-    ClassSubject classSubject = dexInspector.clazz(clazz);
-    assertThat(classSubject, isPresent());
-    MethodSubject methodSubject = classSubject.method(signature);
-    assertThat(methodSubject, not(isPublic()));
-  }
-
   @Test
   public void testStaticMethodRelaxation() throws Exception {
     Class mainClass = C.class;
@@ -113,20 +62,20 @@
     AndroidApp app = ToolHelper.runR8(builder.build());
     compareJvmAndArt(app, mainClass);
 
-    DexInspector dexInspector = new DexInspector(app);
-    assertPublic(dexInspector, A.class,
+    CodeInspector codeInspector = new CodeInspector(app);
+    assertPublic(codeInspector, A.class,
         new MethodSignature("baz", STRING, ImmutableList.of()));
-    assertPublic(dexInspector, A.class,
+    assertPublic(codeInspector, A.class,
         new MethodSignature("bar", STRING, ImmutableList.of()));
-    assertPublic(dexInspector, A.class,
+    assertPublic(codeInspector, A.class,
         new MethodSignature("bar", STRING, ImmutableList.of("int")));
-    assertPublic(dexInspector, A.class,
+    assertPublic(codeInspector, A.class,
         new MethodSignature("blah", STRING, ImmutableList.of("int")));
 
-    assertPublic(dexInspector, B.class,
+    assertPublic(codeInspector, B.class,
         new MethodSignature("blah", STRING, ImmutableList.of("int")));
 
-    assertPublic(dexInspector, BB.class,
+    assertPublic(codeInspector, BB.class,
         new MethodSignature("blah", STRING, ImmutableList.of("int")));
   }
 
@@ -161,24 +110,24 @@
     AndroidApp app = ToolHelper.runR8(builder.build());
     compareJvmAndArt(app, mainClass);
 
-    DexInspector dexInspector = new DexInspector(app);
-    assertPublic(dexInspector, Base.class,
+    CodeInspector codeInspector = new CodeInspector(app);
+    assertPublic(codeInspector, Base.class,
         new MethodSignature("foo", STRING, ImmutableList.of()));
 
     // Base#foo?() can't be publicized due to Itf<1>#foo<1>().
-    assertNotPublic(dexInspector, Base.class,
+    assertNotPublic(codeInspector, Base.class,
         new MethodSignature("foo1", STRING, ImmutableList.of()));
-    assertNotPublic(dexInspector, Base.class,
+    assertNotPublic(codeInspector, Base.class,
         new MethodSignature("foo2", STRING, ImmutableList.of()));
 
     // Sub?#bar1(int) can be publicized as they don't bother each other.
-    assertPublic(dexInspector, Sub1.class,
+    assertPublic(codeInspector, Sub1.class,
         new MethodSignature("bar1", STRING, ImmutableList.of("int")));
-    assertPublic(dexInspector, Sub2.class,
+    assertPublic(codeInspector, Sub2.class,
         new MethodSignature("bar1", STRING, ImmutableList.of("int")));
 
     // Sub2#bar2(int) is unique throughout the hierarchy, hence publicized.
-    assertPublic(dexInspector, Sub2.class,
+    assertPublic(codeInspector, Sub2.class,
         new MethodSignature("bar2", STRING, ImmutableList.of("int")));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/bisect/BisectTest.java b/src/test/java/com/android/tools/r8/bisect/BisectTest.java
index 82a4a94..5252f08 100644
--- a/src/test/java/com/android/tools/r8/bisect/BisectTest.java
+++ b/src/test/java/com/android/tools/r8/bisect/BisectTest.java
@@ -14,9 +14,9 @@
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -81,7 +81,7 @@
   }
 
   private Result command(DexApplication application) {
-    DexInspector inspector = new DexInspector(application);
+    CodeInspector inspector = new CodeInspector(application);
     if (inspector
         .clazz(ERRONEOUS_CLASS)
         .method(erroneousMethodSignature.returnType,
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
index cc8633e4..37d3914 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/B77836766.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.bridgeremoval;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
@@ -16,9 +16,9 @@
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import org.junit.Test;
@@ -129,7 +129,7 @@
 
     AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
 
-    DexInspector inspector = new DexInspector(processedApp);
+    CodeInspector inspector = new CodeInspector(processedApp);
     ClassSubject absSubject = inspector.clazz(absCls.name);
     assertThat(absSubject, isPresent());
     ClassSubject cls1Subject = inspector.clazz(cls1.name);
@@ -236,7 +236,7 @@
 
     AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
 
-    DexInspector inspector = new DexInspector(processedApp);
+    CodeInspector inspector = new CodeInspector(processedApp);
     ClassSubject baseSubject = inspector.clazz(baseCls.name);
     assertThat(baseSubject, isPresent());
     ClassSubject cls1Subject = inspector.clazz(cls1.name);
@@ -332,7 +332,7 @@
 
     AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
 
-    DexInspector inspector = new DexInspector(processedApp);
+    CodeInspector inspector = new CodeInspector(processedApp);
     ClassSubject baseSubject = inspector.clazz(baseCls.name);
     assertThat(baseSubject, isPresent());
     ClassSubject subSubject = inspector.clazz(subCls.name);
@@ -408,7 +408,7 @@
     String proguardConfig = keepMainProguardConfiguration(mainClass.name, false, false);
     AndroidApp processedApp = runAndVerifyOnJvmAndArt(jasminBuilder, mainClassName, proguardConfig);
 
-    DexInspector inspector = new DexInspector(processedApp);
+    CodeInspector inspector = new CodeInspector(processedApp);
     ClassSubject baseSubject = inspector.clazz(cls.name);
     assertThat(baseSubject, isPresent());
 
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/EmptyBridgeTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/EmptyBridgeTest.java
index 160ad59..9c525b5 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/EmptyBridgeTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/EmptyBridgeTest.java
@@ -3,16 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.bridgeremoval;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
@@ -53,7 +53,7 @@
             + "-keep class " + absClassName + "{ *; }";
     AndroidApp processedApp = compileWithR8(jasminBuilder.build(), proguardConfig);
 
-    DexInspector inspector = new DexInspector(processedApp);
+    CodeInspector inspector = new CodeInspector(processedApp);
     ClassSubject classSubject = inspector.clazz(absClassName);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method("void", "emptyBridge", ImmutableList.of());
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
index 60df7d2..c19b308 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.bridgeremoval;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
@@ -17,9 +17,9 @@
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.lang.reflect.Method;
 import java.nio.file.Path;
@@ -34,7 +34,7 @@
         Outer.class,
         Main.class);
     String proguardConfig = keepMainProguardConfiguration(Main.class, true, obfuscate);
-    DexInspector inspector = new DexInspector(compileWithR8(classes, proguardConfig));
+    CodeInspector inspector = new CodeInspector(compileWithR8(classes, proguardConfig));
 
     List<Method> removedMethods = ImmutableList.of(
         Outer.SubClass.class.getMethod("method"),
@@ -116,7 +116,7 @@
     String artResult = runOnArt(optimizedApp, mainClassName);
     assertEquals(javaResult.stdout, artResult);
 
-    DexInspector inspector = new DexInspector(optimizedApp);
+    CodeInspector inspector = new CodeInspector(optimizedApp);
 
     ClassSubject classSubject = inspector.clazz(superClass.name);
     assertThat(classSubject, isPresent());
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
index 55ff3a2..4535b6f 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
@@ -7,8 +7,8 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.lang.reflect.Method;
 import java.util.List;
@@ -32,7 +32,7 @@
         SimpleObservableList.class,
         Main.class);
     String proguardConfig = keepMainAllowAccessModification(Main.class, obfuscate);
-    DexInspector inspector = new DexInspector(compileWithR8(classes, proguardConfig));
+    CodeInspector inspector = new CodeInspector(compileWithR8(classes, proguardConfig));
 
     // The bridge for registerObserver cannot be removed.
     Method registerObserver =
diff --git a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTest.java b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTest.java
index 8a0a3b7..72d6fa4 100644
--- a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTest.java
+++ b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTest.java
@@ -4,9 +4,6 @@
 
 package com.android.tools.r8.cf;
 
-import java.util.*;
-import org.objectweb.asm.*;
-
 public class AlwaysNullGetItemTest {
   public static void main(String[] args) {
     try {
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapTest.java b/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
index 7fdad0d..90b64900 100644
--- a/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
@@ -102,7 +102,7 @@
     ToolHelper.runR8(
         R8Command.builder()
             .setMode(mode)
-            .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outputJar, true))
             .addProgramFiles(inputJar)
             .addProguardConfigurationFiles(pgConfigFile)
diff --git a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
index fd696bc..450e6d0 100644
--- a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
@@ -19,11 +19,10 @@
 import com.android.tools.r8.graph.DexCode.Try;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
 import it.unimi.dsi.fastutil.ints.IntSet;
-import java.nio.file.Paths;
 import java.util.Set;
 import org.junit.Test;
 
@@ -52,7 +51,7 @@
     AndroidApp inputApp =
         AndroidApp.builder()
             .addClassProgramData(inputBytes, Origin.unknown())
-            .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .build();
     assertEquals(2, countCatchHandlers(inputApp));
     AndroidApp outputDexApp = ToolHelper.runR8(inputApp);
@@ -64,7 +63,7 @@
   }
 
   private int countCatchHandlers(AndroidApp inputApp) throws Exception {
-    DexInspector inspector = new DexInspector(inputApp, o -> o.enableCfFrontend = true);
+    CodeInspector inspector = new CodeInspector(inputApp, o -> o.enableCfFrontend = true);
     DexClass dexClass = inspector.clazz(TestClass.class).getDexClass();
     Code code = dexClass.virtualMethods()[0].getCode();
     if (code.isCfCode()) {
diff --git a/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
index 6c9d7b6..7db1d61 100644
--- a/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
@@ -18,9 +18,8 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.Collections;
 import java.util.List;
 import org.junit.Assert;
@@ -52,7 +51,7 @@
     R8.run(
         R8Command.builder()
             .setMode(CompilationMode.DEBUG)
-            .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setProgramConsumer(appBuilder.wrapClassFileConsumer(new ArchiveConsumer(outPath)))
             .addClassProgramData(inputClass, Origin.unknown())
             .build());
@@ -70,7 +69,7 @@
 
   private static CfInvokeDynamic findFirstInMethod(AndroidApp app) throws Exception {
     String returnType = "void";
-    DexInspector inspector = new DexInspector(app, o -> o.enableCfFrontend = true);
+    CodeInspector inspector = new CodeInspector(app, o -> o.enableCfFrontend = true);
     List<String> args = Collections.singletonList(String[].class.getTypeName());
     DexEncodedMethod method = inspector.clazz(CLASS).method(returnType, METHOD, args).getMethod();
     CfCode code = method.getCode().asCfCode();
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java b/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java
index a213a91..9adf616 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleDump.java
@@ -6,7 +6,13 @@
 
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMap.Builder;
-import org.objectweb.asm.*;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
 
 // The method MethodHandleDump.transform() translates methods in MethodHandleTest that look like
 //     MethodType.methodType(TYPES)
diff --git a/src/test/java/com/android/tools/r8/cf/NestedExceptionTestRunner.java b/src/test/java/com/android/tools/r8/cf/NestedExceptionTestRunner.java
index c0a5455..a60eda9 100644
--- a/src/test/java/com/android/tools/r8/cf/NestedExceptionTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/NestedExceptionTestRunner.java
@@ -29,7 +29,7 @@
         R8Command.builder()
             .setMode(CompilationMode.DEBUG)
             .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
-            .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setProgramConsumer(sink.wrapClassFileConsumer(null))
             .build());
     Path out = temp.newFolder().toPath().resolve("test.jar");
diff --git a/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
index 7565dee..e4f9249 100644
--- a/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.graph.JarCode;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidAppConsumers;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.ArrayList;
 import java.util.Collections;
 import org.junit.Test;
@@ -37,7 +37,7 @@
             .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
             .setProgramConsumer(a.wrapClassFileConsumer(null))
             .build());
-    DexInspector inspector = new DexInspector(a.build());
+    CodeInspector inspector = new CodeInspector(a.build());
     DexEncodedMethod method =
         inspector.clazz(CLASS).method("void", "noop", Collections.emptyList()).getMethod();
     ArrayList<AbstractInsnNode> insns = new ArrayList<>();
diff --git a/src/test/java/com/android/tools/r8/cf/UninitializedInFrameDump.java b/src/test/java/com/android/tools/r8/cf/UninitializedInFrameDump.java
index bac896e..4f3f615 100644
--- a/src/test/java/com/android/tools/r8/cf/UninitializedInFrameDump.java
+++ b/src/test/java/com/android/tools/r8/cf/UninitializedInFrameDump.java
@@ -4,8 +4,12 @@
 
 package com.android.tools.r8.cf;
 
-import java.util.*;
-import org.objectweb.asm.*;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
 
 public class UninitializedInFrameDump implements Opcodes {
 
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index b6489b0..6322840 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -3,9 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.classmerging;
 
+import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
 import static com.android.tools.r8.smali.SmaliBuilder.buildCode;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
@@ -14,6 +16,7 @@
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringConsumer.FileConsumer;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
@@ -21,6 +24,10 @@
 import com.android.tools.r8.VmTestRunner.IgnoreForRangeOfVmVersions;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.code.MoveException;
+import com.android.tools.r8.debug.DebugTestBase;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
+import com.android.tools.r8.debug.DexDebugTestConfig;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.jasmin.JasminBuilder;
@@ -29,13 +36,16 @@
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.base.Joiner;
+import com.google.common.base.Predicates;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -70,6 +80,7 @@
     options.enableClassMerging = true;
     options.enableClassInlining = false;
     options.enableMinification = false;
+    options.testing.nondeterministicCycleElimination = true;
   }
 
   private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
@@ -82,11 +93,11 @@
             .setDisableMinification(true)
             .build(),
         optionsConsumer);
-    inspector = new DexInspector(
+    inspector = new CodeInspector(
         Paths.get(temp.getRoot().getCanonicalPath()).resolve("classes.dex"));
   }
 
-  private DexInspector inspector;
+  private CodeInspector inspector;
 
   private final List<String> CAN_BE_MERGED = ImmutableList.of(
       "classmerging.GenericInterface",
@@ -96,7 +107,7 @@
   );
 
   @Test
-  public void testClassesHaveBeenMerged() throws Exception {
+  public void testClassesHaveBeenMerged() throws Throwable {
     runR8(EXAMPLE_KEEP, this::configure);
     // GenericInterface should be merged into GenericInterfaceImpl.
     for (String candidate : CAN_BE_MERGED) {
@@ -108,15 +119,38 @@
   }
 
   @Test
-  public void testClassesHaveNotBeenMerged() throws Exception {
+  public void testClassesHaveNotBeenMerged() throws Throwable {
     runR8(DONT_OPTIMIZE, null);
     for (String candidate : CAN_BE_MERGED) {
       assertTrue(inspector.clazz(candidate).isPresent());
     }
   }
 
+  // This test has a cycle in the call graph consisting of the methods A.<init> and B.<init>.
+  // When nondeterministicCycleElimination is enabled, we shuffle the nodes in the call graph
+  // before the cycle elimination. Therefore, it is nondeterministic if the cycle detector will
+  // detect the cycle upon the call edge A.<init> to B.<init>, or B.<init> to A.<init>. In order
+  // increase the likelihood that this test encounters both orderings, the test is repeated 5 times.
+  // Assuming that the chance of observing one of the two orderings is 50%, this test therefore has
+  // approximately 3% chance of succeeding although there is an issue.
   @Test
-  public void testConflictInGeneratedName() throws Exception {
+  public void testCallGraphCycle() throws Throwable {
+    String main = "classmerging.CallGraphCycleTest";
+    Path[] programFiles =
+        new Path[] {
+          CF_DIR.resolve("CallGraphCycleTest.class"),
+          CF_DIR.resolve("CallGraphCycleTest$A.class"),
+          CF_DIR.resolve("CallGraphCycleTest$B.class")
+        };
+    Set<String> preservedClassNames =
+        ImmutableSet.of("classmerging.CallGraphCycleTest", "classmerging.CallGraphCycleTest$B");
+    for (int i = 0; i < 5; i++) {
+      runTest(main, programFiles, preservedClassNames::contains);
+    }
+  }
+
+  @Test
+  public void testConflictInGeneratedName() throws Throwable {
     String main = "classmerging.ConflictInGeneratedNameTest";
     Path[] programFiles =
         new Path[] {
@@ -128,7 +162,7 @@
         ImmutableSet.of(
             "classmerging.ConflictInGeneratedNameTest",
             "classmerging.ConflictInGeneratedNameTest$B");
-    DexInspector inspector =
+    CodeInspector inspector =
         runTestOnInput(
             main,
             readProgramFiles(programFiles),
@@ -138,7 +172,9 @@
               configure(options);
               // Avoid that direct methods in B get inlined.
               options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
-            });
+            },
+            // Disable debug testing since the test has a method with "$classmerging$" in the name.
+            null);
 
     ClassSubject clazzSubject = inspector.clazz("classmerging.ConflictInGeneratedNameTest$B");
     assertThat(clazzSubject, isPresent());
@@ -180,14 +216,14 @@
   }
 
   @Test
-  public void testConflictWasDetected() throws Exception {
+  public void testConflictWasDetected() throws Throwable {
     runR8(EXAMPLE_KEEP, this::configure);
     assertTrue(inspector.clazz("classmerging.ConflictingInterface").isPresent());
     assertTrue(inspector.clazz("classmerging.ConflictingInterfaceImpl").isPresent());
   }
 
   @Test
-  public void testFieldCollision() throws Exception {
+  public void testFieldCollision() throws Throwable {
     String main = "classmerging.FieldCollisionTest";
     Path[] programFiles =
         new Path[] {
@@ -203,7 +239,7 @@
   }
 
   @Test
-  public void testLambdaRewriting() throws Exception {
+  public void testLambdaRewriting() throws Throwable {
     String main = "classmerging.LambdaRewritingTest";
     Path[] programFiles =
         new Path[] {
@@ -226,7 +262,7 @@
   }
 
   @Test
-  public void testMethodCollision() throws Exception {
+  public void testMethodCollision() throws Throwable {
     String main = "classmerging.MethodCollisionTest";
     Path[] programFiles =
         new Path[] {
@@ -251,7 +287,7 @@
   }
 
   @Test
-  public void testNestedDefaultInterfaceMethodsTest() throws Exception {
+  public void testNestedDefaultInterfaceMethodsTest() throws Throwable {
     String main = "classmerging.NestedDefaultInterfaceMethodsTest";
     Path[] programFiles =
         new Path[] {
@@ -270,7 +306,7 @@
   }
 
   @Test
-  public void testNestedDefaultInterfaceMethodsWithCDumpTest() throws Exception {
+  public void testNestedDefaultInterfaceMethodsWithCDumpTest() throws Throwable {
     String main = "classmerging.NestedDefaultInterfaceMethodsTest";
     Path[] programFiles =
         new Path[] {
@@ -295,7 +331,7 @@
   }
 
   @Test
-  public void testPinnedParameterTypes() throws Exception {
+  public void testPinnedParameterTypes() throws Throwable {
     String main = "classmerging.PinnedParameterTypesTest";
     Path[] programFiles =
         new Path[] {
@@ -318,7 +354,210 @@
   }
 
   @Test
-  public void testSuperCallWasDetected() throws Exception {
+  public void testProguardFieldMap() throws Throwable {
+    String main = "classmerging.ProguardFieldMapTest";
+    Path[] programFiles =
+        new Path[] {
+          CF_DIR.resolve("ProguardFieldMapTest.class"),
+          CF_DIR.resolve("ProguardFieldMapTest$A.class"),
+          CF_DIR.resolve("ProguardFieldMapTest$B.class")
+        };
+
+    // Try without vertical class merging, to check that output is as expected.
+    String expectedProguardMapWithoutClassMerging =
+        Joiner.on(System.lineSeparator())
+            .join(
+                "classmerging.ProguardFieldMapTest -> classmerging.ProguardFieldMapTest:",
+                "    1:2:void main(java.lang.String[]):10:11 -> main",
+                "classmerging.ProguardFieldMapTest$A -> classmerging.ProguardFieldMapTest$A:",
+                "    1:1:void <init>():14:14 -> <init>",
+                "    2:2:void <init>():16:16 -> <init>",
+                "classmerging.ProguardFieldMapTest$B -> classmerging.ProguardFieldMapTest$B:",
+                "    1:1:void <init>():19:19 -> <init>",
+                "    1:1:void test():22:22 -> test");
+    runTestOnInput(
+        main,
+        readProgramFiles(programFiles),
+        Predicates.alwaysTrue(),
+        getProguardConfig(EXAMPLE_KEEP),
+        options -> {
+          configure(options);
+          options.enableClassMerging = false;
+          options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+          options.proguardMapConsumer =
+              (proguardMap, handler) ->
+                  assertThat(proguardMap, containsString(expectedProguardMapWithoutClassMerging));
+        });
+
+    // Try with vertical class merging.
+    String expectedProguardMapWithClassMerging =
+        Joiner.on(System.lineSeparator())
+            .join(
+                "classmerging.ProguardFieldMapTest -> classmerging.ProguardFieldMapTest:",
+                "    1:2:void main(java.lang.String[]):10:11 -> main",
+                "classmerging.ProguardFieldMapTest$B -> classmerging.ProguardFieldMapTest$B:",
+                "    java.lang.String classmerging.ProguardFieldMapTest$A.f -> f",
+                "    1:1:void classmerging.ProguardFieldMapTest$A.<init>():14:14 -> <init>",
+                "    1:1:void <init>():19 -> <init>",
+                "    2:2:void classmerging.ProguardFieldMapTest$A.<init>():16:16 -> <init>",
+                "    2:2:void <init>():19 -> <init>",
+                "    1:1:void test():22:22 -> test");
+    Set<String> preservedClassNames =
+        ImmutableSet.of("classmerging.ProguardFieldMapTest", "classmerging.ProguardFieldMapTest$B");
+    runTestOnInput(
+        main,
+        readProgramFiles(programFiles),
+        preservedClassNames::contains,
+        getProguardConfig(EXAMPLE_KEEP),
+        options -> {
+          configure(options);
+          options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+          options.proguardMapConsumer =
+              (proguardMap, handler) ->
+                  assertThat(proguardMap, containsString(expectedProguardMapWithClassMerging));
+        });
+  }
+
+  @Test
+  public void testProguardMethodMap() throws Throwable {
+    String main = "classmerging.ProguardMethodMapTest";
+    Path[] programFiles =
+        new Path[] {
+          CF_DIR.resolve("ProguardMethodMapTest.class"),
+          CF_DIR.resolve("ProguardMethodMapTest$A.class"),
+          CF_DIR.resolve("ProguardMethodMapTest$B.class")
+        };
+
+    // Try without vertical class merging, to check that output is as expected.
+    String expectedProguardMapWithoutClassMerging =
+        Joiner.on(System.lineSeparator())
+            .join(
+                "classmerging.ProguardMethodMapTest -> classmerging.ProguardMethodMapTest:",
+                "    1:2:void main(java.lang.String[]):10:11 -> main",
+                "classmerging.ProguardMethodMapTest$A -> classmerging.ProguardMethodMapTest$A:",
+                "    1:1:void <init>():14:14 -> <init>",
+                "    1:1:void method():17:17 -> method",
+                "classmerging.ProguardMethodMapTest$B -> classmerging.ProguardMethodMapTest$B:",
+                "    1:1:void <init>():21:21 -> <init>",
+                "    1:2:void method():25:26 -> method");
+    runTestOnInput(
+        main,
+        readProgramFiles(programFiles),
+        Predicates.alwaysTrue(),
+        getProguardConfig(EXAMPLE_KEEP),
+        options -> {
+          configure(options);
+          options.enableClassMerging = false;
+          options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+          options.proguardMapConsumer =
+              (proguardMap, handler) ->
+                  assertThat(proguardMap, containsString(expectedProguardMapWithoutClassMerging));
+        });
+
+    // Try with vertical class merging.
+    String expectedProguardMapWithClassMerging =
+        Joiner.on(System.lineSeparator())
+            .join(
+                "classmerging.ProguardMethodMapTest -> classmerging.ProguardMethodMapTest:",
+                "    1:2:void main(java.lang.String[]):10:11 -> main",
+                "classmerging.ProguardMethodMapTest$B -> classmerging.ProguardMethodMapTest$B:",
+                // TODO(christofferqa): Should this be "...<init>():14 -> <init>" to reflect that
+                // A.<init> has been inlined into B.<init>?
+                "    1:1:void classmerging.ProguardMethodMapTest$A.<init>():14:14 -> <init>",
+                // TODO(christofferqa): Should this be " ...<init>():21:21 -> <init>"?
+                "    1:1:void <init>():21 -> <init>",
+                "    1:2:void method():25:26 -> method",
+                "    1:1:void classmerging.ProguardMethodMapTest$A.method():17:17 -> "
+                    + "method$classmerging$ProguardMethodMapTest$A");
+    Set<String> preservedClassNames =
+        ImmutableSet.of(
+            "classmerging.ProguardMethodMapTest", "classmerging.ProguardMethodMapTest$B");
+    runTestOnInput(
+        main,
+        readProgramFiles(programFiles),
+        preservedClassNames::contains,
+        getProguardConfig(EXAMPLE_KEEP),
+        options -> {
+          configure(options);
+          options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+          options.proguardMapConsumer =
+              (proguardMap, handler) ->
+                  assertThat(proguardMap, containsString(expectedProguardMapWithClassMerging));
+        });
+  }
+
+  @Test
+  public void testProguardMethodMapAfterInlining() throws Throwable {
+    String main = "classmerging.ProguardMethodMapTest";
+    Path[] programFiles =
+        new Path[] {
+          CF_DIR.resolve("ProguardMethodMapTest.class"),
+          CF_DIR.resolve("ProguardMethodMapTest$A.class"),
+          CF_DIR.resolve("ProguardMethodMapTest$B.class")
+        };
+
+    // Try without vertical class merging, to check that output is as expected.
+    String expectedProguardMapWithoutClassMerging =
+        Joiner.on(System.lineSeparator())
+            .join(
+                "classmerging.ProguardMethodMapTest -> classmerging.ProguardMethodMapTest:",
+                "    1:2:void main(java.lang.String[]):10:11 -> main",
+                "classmerging.ProguardMethodMapTest$A -> classmerging.ProguardMethodMapTest$A:",
+                "    1:1:void <init>():14:14 -> <init>",
+                "classmerging.ProguardMethodMapTest$B -> classmerging.ProguardMethodMapTest$B:",
+                "    1:1:void <init>():21:21 -> <init>",
+                "    1:1:void method():25:25 -> method",
+                "    2:2:void classmerging.ProguardMethodMapTest$A.method():17:17 -> method",
+                "    2:2:void method():26 -> method");
+    runTestOnInput(
+        main,
+        readProgramFiles(programFiles),
+        Predicates.alwaysTrue(),
+        getProguardConfig(
+            EXAMPLE_KEEP,
+            "-forceinline class classmerging.ProguardMethodMapTest$A { public void method(); }"),
+        options -> {
+          configure(options);
+          options.enableClassMerging = false;
+          options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+          options.proguardMapConsumer =
+              (proguardMap, handler) ->
+                  assertThat(proguardMap, containsString(expectedProguardMapWithoutClassMerging));
+        });
+
+    // Try with vertical class merging.
+    String expectedProguardMapWithClassMerging =
+        Joiner.on(System.lineSeparator())
+            .join(
+                "classmerging.ProguardMethodMapTest -> classmerging.ProguardMethodMapTest:",
+                "    1:2:void main(java.lang.String[]):10:11 -> main",
+                "classmerging.ProguardMethodMapTest$B -> classmerging.ProguardMethodMapTest$B:",
+                "    1:1:void classmerging.ProguardMethodMapTest$A.<init>():14:14 -> <init>",
+                "    1:1:void <init>():21 -> <init>",
+                "    1:1:void method():25:25 -> method",
+                "    2:2:void classmerging.ProguardMethodMapTest$A.method():17:17 -> method",
+                "    2:2:void method():26 -> method");
+    Set<String> preservedClassNames =
+        ImmutableSet.of(
+            "classmerging.ProguardMethodMapTest", "classmerging.ProguardMethodMapTest$B");
+    runTestOnInput(
+        main,
+        readProgramFiles(programFiles),
+        preservedClassNames::contains,
+        getProguardConfig(
+            EXAMPLE_KEEP,
+            "-forceinline class classmerging.ProguardMethodMapTest$A { public void method(); }"),
+        options -> {
+          configure(options);
+          options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+          options.proguardMapConsumer =
+              (proguardMap, handler) ->
+                  assertThat(proguardMap, containsString(expectedProguardMapWithClassMerging));
+        });
+  }
+
+  @Test
+  public void testSuperCallWasDetected() throws Throwable {
     String main = "classmerging.SuperCallRewritingTest";
     Path[] programFiles =
         new Path[] {
@@ -342,7 +581,7 @@
   // targets SubClassThatReferencesSuperMethod.referencedMethod. When running without class
   // merging, R8 should not rewrite the invoke-super instruction into invoke-direct.
   @Test
-  public void testSuperCallNotRewrittenToDirect() throws Exception {
+  public void testSuperCallNotRewrittenToDirect() throws Throwable {
     String main = "classmerging.SuperCallRewritingTest";
     Path[] programFiles =
         new Path[] {
@@ -422,7 +661,7 @@
   //   }
   @Test
   @IgnoreForRangeOfVmVersions(from = Version.V5_1_1, to = Version.V6_0_1)
-  public void testSuperCallToMergedClassIsRewritten() throws Exception {
+  public void testSuperCallToMergedClassIsRewritten() throws Throwable {
     String main = "classmerging.SuperCallToMergedClassIsRewrittenTest";
     Set<String> preservedClassNames =
         ImmutableSet.of(
@@ -545,7 +784,7 @@
   }
 
   @Test
-  public void testConflictingInterfaceSignatures() throws Exception {
+  public void testConflictingInterfaceSignatures() throws Throwable {
     String main = "classmerging.ConflictingInterfaceSignaturesTest";
     Path[] programFiles =
         new Path[] {
@@ -564,7 +803,7 @@
   // If an exception class A is merged into another exception class B, then all exception tables
   // should be updated, and class A should be removed entirely.
   @Test
-  public void testExceptionTables() throws Exception {
+  public void testExceptionTables() throws Throwable {
     String main = "classmerging.ExceptionTest";
     Path[] programFiles =
         new Path[] {
@@ -579,7 +818,7 @@
             "classmerging.ExceptionTest",
             "classmerging.ExceptionTest$ExceptionB",
             "classmerging.ExceptionTest$Exception2");
-    DexInspector inspector = runTest(main, programFiles, preservedClassNames::contains);
+    CodeInspector inspector = runTest(main, programFiles, preservedClassNames::contains);
 
     ClassSubject mainClass = inspector.clazz(main);
     assertThat(mainClass, isPresent());
@@ -600,7 +839,7 @@
   }
 
   @Test
-  public void testMergeDefaultMethodIntoClass() throws Exception {
+  public void testMergeDefaultMethodIntoClass() throws Throwable {
     String main = "classmerging.MergeDefaultMethodIntoClassTest";
     Path[] programFiles =
         new Path[] {
@@ -621,7 +860,7 @@
     // Sanity check that there is actually an invoke-interface instruction in the input. We need
     // to make sure that this invoke-interface instruction is translated to invoke-virtual after
     // the classes A and B are merged.
-    DexInspector inputInspector = new DexInspector(app);
+    CodeInspector inputInspector = new CodeInspector(app);
     ClassSubject clazz = inputInspector.clazz("classmerging.MergeDefaultMethodIntoClassTest");
     assertThat(clazz, isPresent());
     MethodSubject method = clazz.method("void", "main", ImmutableList.of("java.lang.String[]"));
@@ -634,7 +873,7 @@
   }
 
   @Test
-  public void testNativeMethod() throws Exception {
+  public void testNativeMethod() throws Throwable {
     String main = "classmerging.ClassWithNativeMethodTest";
     Path[] programFiles =
         new Path[] {
@@ -652,7 +891,7 @@
   }
 
   @Test
-  public void testNoIllegalClassAccess() throws Exception {
+  public void testNoIllegalClassAccess() throws Throwable {
     String main = "classmerging.SimpleInterfaceAccessTest";
     Path[] programFiles =
         new Path[] {
@@ -677,7 +916,7 @@
   }
 
   @Test
-  public void testNoIllegalClassAccessWithAccessModifications() throws Exception {
+  public void testNoIllegalClassAccessWithAccessModifications() throws Throwable {
     // If access modifications are allowed then SimpleInterface should be merged into
     // SimpleInterfaceImpl.
     String main = "classmerging.SimpleInterfaceAccessTest";
@@ -712,7 +951,7 @@
   // into an invoke-direct instruction after it gets merged into class C. We should add a test that
   // checks that this works with and without inlining of the method B.m().
   @Test
-  public void testRewritePinnedMethod() throws Exception {
+  public void testRewritePinnedMethod() throws Throwable {
     String main = "classmerging.RewritePinnedMethodTest";
     Path[] programFiles =
         new Path[] {
@@ -735,7 +974,7 @@
   }
 
   @Test
-  public void testTemplateMethodPattern() throws Exception {
+  public void testTemplateMethodPattern() throws Throwable {
     String main = "classmerging.TemplateMethodTest";
     Path[] programFiles =
         new Path[] {
@@ -749,37 +988,66 @@
     runTest(main, programFiles, preservedClassNames::contains);
   }
 
-  private DexInspector runTest(
-      String main, Path[] programFiles, Predicate<String> preservedClassNames) throws Exception {
+  private CodeInspector runTest(
+      String main, Path[] programFiles, Predicate<String> preservedClassNames) throws Throwable {
     return runTest(main, programFiles, preservedClassNames, getProguardConfig(EXAMPLE_KEEP));
   }
 
-  private DexInspector runTest(
+  private CodeInspector runTest(
       String main,
       Path[] programFiles,
       Predicate<String> preservedClassNames,
       String proguardConfig)
-      throws Exception {
+      throws Throwable {
     return runTestOnInput(
         main, readProgramFiles(programFiles), preservedClassNames, proguardConfig);
   }
 
-  private DexInspector runTestOnInput(
+  private CodeInspector runTestOnInput(
       String main, AndroidApp input, Predicate<String> preservedClassNames, String proguardConfig)
-      throws Exception {
+      throws Throwable {
     return runTestOnInput(main, input, preservedClassNames, proguardConfig, this::configure);
   }
 
-  private DexInspector runTestOnInput(
+  private CodeInspector runTestOnInput(
       String main,
       AndroidApp input,
       Predicate<String> preservedClassNames,
       String proguardConfig,
       Consumer<InternalOptions> optionsConsumer)
-      throws Exception {
-    AndroidApp output = compileWithR8(input, proguardConfig, optionsConsumer);
-    DexInspector inputInspector = new DexInspector(input);
-    DexInspector outputInspector = new DexInspector(output);
+      throws Throwable {
+    return runTestOnInput(
+        main,
+        input,
+        preservedClassNames,
+        proguardConfig,
+        optionsConsumer,
+        new VerticalClassMergerDebugTest(main));
+  }
+
+  private CodeInspector runTestOnInput(
+      String main,
+      AndroidApp input,
+      Predicate<String> preservedClassNames,
+      String proguardConfig,
+      Consumer<InternalOptions> optionsConsumer,
+      VerticalClassMergerDebugTest debugTestRunner)
+      throws Throwable {
+    Path proguardMapPath = File.createTempFile("mapping", ".txt", temp.getRoot()).toPath();
+    R8Command.Builder commandBuilder =
+        ToolHelper.prepareR8CommandBuilder(input)
+            .addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
+    ToolHelper.allowTestProguardOptions(commandBuilder);
+    AndroidApp output =
+        ToolHelper.runR8(
+            commandBuilder.build(),
+            options -> {
+              optionsConsumer.accept(options);
+              options.proguardMapConsumer =
+                  new FileConsumer(proguardMapPath, options.proguardMapConsumer);
+            });
+    CodeInspector inputInspector = new CodeInspector(input);
+    CodeInspector outputInspector = new CodeInspector(output);
     // Check that all classes in [preservedClassNames] are in fact preserved.
     for (FoundClassSubject classSubject : inputInspector.allClasses()) {
       String className = classSubject.getOriginalName();
@@ -791,6 +1059,11 @@
     }
     // Check that the R8-generated code produces the same result as D8-generated code.
     assertEquals(runOnArt(compileWithD8(input), main), runOnArt(output, main));
+    // Check that we never come across a method that has a name with "$classmerging$" in it during
+    // debugging.
+    if (debugTestRunner != null) {
+      debugTestRunner.test(output, proguardMapPath);
+    }
     return outputInspector;
   }
 
@@ -806,4 +1079,82 @@
     }
     return builder.toString();
   }
+
+  private class VerticalClassMergerDebugTest extends DebugTestBase {
+
+    private final String main;
+    private DebugTestRunner runner = null;
+
+    public VerticalClassMergerDebugTest(String main) {
+      this.main = main;
+    }
+
+    public void test(AndroidApp app, Path proguardMapPath) throws Throwable {
+      Path appPath =
+          File.createTempFile("app", ".zip", ClassMergingTest.this.temp.getRoot()).toPath();
+      app.writeToZip(appPath, OutputMode.DexIndexed);
+
+      DexDebugTestConfig config = new DexDebugTestConfig(appPath);
+      config.allowUnprocessedCommands();
+      config.setProguardMap(proguardMapPath);
+
+      this.runner =
+          getDebugTestRunner(
+              config, main, breakpoint(main, "main"), run(), stepIntoUntilNoLongerInApp());
+      this.runner.runBare();
+    }
+
+    private void checkState(DebuggeeState state) {
+      // If a class pkg.A is merged into pkg.B, and a method pkg.A.m() needs to be renamed, then
+      // it will be renamed to pkg.B.m$pkg$A(). Since all tests are in the package "classmerging",
+      // we check that no methods in the debugging state (i.e., after the Proguard map has been
+      // applied) contain "$classmerging$.
+      String qualifiedMethodSignature =
+          state.getClassSignature() + "->" + state.getMethodName() + state.getMethodSignature();
+      boolean holderIsCompanionClass = state.getClassName().endsWith(COMPANION_CLASS_NAME_SUFFIX);
+      if (!holderIsCompanionClass) {
+        assertThat(qualifiedMethodSignature, not(containsString("$classmerging$")));
+      }
+    }
+
+    // Keeps stepping in until it is no longer in a class from the classmerging package.
+    // Then starts stepping out until it is again in the classmerging package.
+    private Command stepIntoUntilNoLongerInApp() {
+      return stepUntil(
+          StepKind.INTO,
+          StepLevel.INSTRUCTION,
+          state -> {
+            if (state.getClassSignature().contains("classmerging")) {
+              checkState(state);
+
+              // Continue stepping into.
+              return false;
+            }
+
+            // Stop stepping into.
+            runner.enqueueCommandFirst(stepOutUntilInApp());
+            return true;
+          });
+    }
+
+    // Keeps stepping out until it is in a class from the classmerging package.
+    // Then starts stepping in until it is no longer in the classmerging package.
+    private Command stepOutUntilInApp() {
+      return stepUntil(
+          StepKind.OUT,
+          StepLevel.INSTRUCTION,
+          state -> {
+            if (state.getClassSignature().contains("classmerging")) {
+              checkState(state);
+
+              // Stop stepping out.
+              runner.enqueueCommandFirst(stepIntoUntilNoLongerInApp());
+              return true;
+            }
+
+            // Continue stepping out.
+            return false;
+          });
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/code/NativeMethodWithCodeTest.java b/src/test/java/com/android/tools/r8/code/NativeMethodWithCodeTest.java
index 83e711c..5b4f1d4 100644
--- a/src/test/java/com/android/tools/r8/code/NativeMethodWithCodeTest.java
+++ b/src/test/java/com/android/tools/r8/code/NativeMethodWithCodeTest.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
@@ -17,9 +17,9 @@
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -102,7 +102,7 @@
 
   private MethodSubject getNativeMethod(String mainClassName, AndroidApp processedApp)
       throws IOException, ExecutionException {
-    DexInspector inspector = new DexInspector(processedApp);
+    CodeInspector inspector = new CodeInspector(processedApp);
     ClassSubject mainSubject = inspector.clazz(mainClassName);
     return mainSubject.method("void", "n1", ImmutableList.of("java.lang.String"));
   }
diff --git a/src/test/java/com/android/tools/r8/compatproguard/AtomicFieldUpdaterTest.java b/src/test/java/com/android/tools/r8/compatproguard/AtomicFieldUpdaterTest.java
index 502fa95..2521e02 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/AtomicFieldUpdaterTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/AtomicFieldUpdaterTest.java
@@ -12,9 +12,9 @@
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.smali.SmaliBuilder;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
@@ -44,11 +44,11 @@
         keepMainProguardConfiguration(CLASS_NAME),
         "-dontshrink",
         "-dontoptimize");
-    DexInspector inspector = runCompatProguard(builder, pgConfigs);
+    CodeInspector inspector = runCompatProguard(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
-    MethodSubject method = clazz.method(DexInspector.MAIN);
+    MethodSubject method = clazz.method(CodeInspector.MAIN);
     assertTrue(method.isPresent());
 
     DexCode code = method.getMethod().getCode().asDexCode();
@@ -80,11 +80,11 @@
         keepMainProguardConfiguration(CLASS_NAME),
         "-dontshrink",
         "-dontoptimize");
-    DexInspector inspector = runCompatProguard(builder, pgConfigs);
+    CodeInspector inspector = runCompatProguard(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
-    MethodSubject method = clazz.method(DexInspector.MAIN);
+    MethodSubject method = clazz.method(CodeInspector.MAIN);
     assertTrue(method.isPresent());
 
     DexCode code = method.getMethod().getCode().asDexCode();
@@ -117,11 +117,11 @@
         keepMainProguardConfiguration(CLASS_NAME),
         "-dontshrink",
         "-dontoptimize");
-    DexInspector inspector = runCompatProguard(builder, pgConfigs);
+    CodeInspector inspector = runCompatProguard(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
-    MethodSubject method = clazz.method(DexInspector.MAIN);
+    MethodSubject method = clazz.method(CodeInspector.MAIN);
     assertTrue(method.isPresent());
 
     DexCode code = method.getMethod().getCode().asDexCode();
diff --git a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
index 178337a..ea450e4 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
@@ -10,12 +10,12 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliTestBase;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Path;
 import java.util.List;
 
 class CompatProguardSmaliTestBase extends SmaliTestBase {
-  DexInspector runCompatProguard(SmaliBuilder builder, List<String> proguardConfigurations)
+  CodeInspector runCompatProguard(SmaliBuilder builder, List<String> proguardConfigurations)
       throws Exception {
     Path dexOutputDir = temp.newFolder().toPath();
     R8Command.Builder commandBuilder =
@@ -23,6 +23,6 @@
             .setOutput(dexOutputDir, OutputMode.DexIndexed)
             .addProguardConfiguration(proguardConfigurations, Origin.unknown());
     ToolHelper.getAppBuilder(commandBuilder).addDexProgramData(builder.compile(), Origin.unknown());
-    return new DexInspector(ToolHelper.runR8(commandBuilder.build()));
+    return new CodeInspector(ToolHelper.runR8(commandBuilder.build()));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
index 5ef5830..6aa8805 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/ForNameTest.java
@@ -12,9 +12,9 @@
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.smali.SmaliBuilder;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
@@ -39,11 +39,11 @@
         keepMainProguardConfiguration(CLASS_NAME),
         "-dontshrink",
         "-dontoptimize");
-    DexInspector inspector = runCompatProguard(builder, pgConfigs);
+    CodeInspector inspector = runCompatProguard(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
-    MethodSubject method = clazz.method(DexInspector.MAIN);
+    MethodSubject method = clazz.method(CodeInspector.MAIN);
     assertTrue(method.isPresent());
 
     DexCode code = method.getMethod().getCode().asDexCode();
@@ -70,11 +70,11 @@
         "-dontshrink",
         "-dontoptimize",
         "-dontobfuscate");
-    DexInspector inspector = runCompatProguard(builder, pgConfigs);
+    CodeInspector inspector = runCompatProguard(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
-    MethodSubject method = clazz.method(DexInspector.MAIN);
+    MethodSubject method = clazz.method(CodeInspector.MAIN);
     assertTrue(method.isPresent());
 
     DexCode code = method.getMethod().getCode().asDexCode();
diff --git a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
index 665919b..5771019 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/GetMembersTest.java
@@ -15,9 +15,9 @@
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.smali.SmaliBuilder;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
@@ -45,11 +45,11 @@
         keepMainProguardConfiguration(CLASS_NAME),
         "-dontshrink",
         "-dontoptimize");
-    DexInspector inspector = runCompatProguard(builder, pgConfigs);
+    CodeInspector inspector = runCompatProguard(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
-    MethodSubject method = clazz.method(DexInspector.MAIN);
+    MethodSubject method = clazz.method(CodeInspector.MAIN);
     assertTrue(method.isPresent());
 
     DexCode code = method.getMethod().getCode().asDexCode();
@@ -85,11 +85,11 @@
         keepMainProguardConfiguration(CLASS_NAME),
         "-dontshrink",
         "-dontoptimize");
-    DexInspector inspector = runCompatProguard(builder, pgConfigs);
+    CodeInspector inspector = runCompatProguard(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
-    MethodSubject method = clazz.method(DexInspector.MAIN);
+    MethodSubject method = clazz.method(CodeInspector.MAIN);
     assertTrue(method.isPresent());
 
     DexCode code = method.getMethod().getCode().asDexCode();
diff --git a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java
index 5cd652d..8d79472 100644
--- a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java
+++ b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestDump.java
@@ -1,3 +1,7 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
 package com.android.tools.r8.debug;
 
 import org.objectweb.asm.AnnotationVisitor;
diff --git a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
index 7866e1b..bcc70c6 100644
--- a/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/ArrayDimensionGreaterThanSevenTestRunner.java
@@ -36,6 +36,7 @@
         R8Command.builder()
             .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
             .setMode(CompilationMode.DEBUG)
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setOutput(cfOut, OutputMode.ClassFile)
             .build(),
         optionsConsumer);
diff --git a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
index f010357..29b0e17 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
@@ -55,7 +55,9 @@
       config = new DexDebugTestConfig(outjar);
     } else {
       assert (runtimeKind == RuntimeKind.CF);
-      builder.setOutput(outjar, OutputMode.ClassFile);
+      builder
+          .setOutput(outjar, OutputMode.ClassFile)
+          .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
       config = new CfDebugTestConfig(outjar);
     }
 
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 5707812..e9ef5ed 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -137,6 +137,18 @@
   private void runInternal(
       DebugTestConfig config, String debuggeeClass, List<JUnit3Wrapper.Command> commands)
       throws Throwable {
+    getDebugTestRunner(config, debuggeeClass, commands).runBare();
+  }
+
+  protected DebugTestRunner getDebugTestRunner(
+      DebugTestConfig config, String debuggeeClass, JUnit3Wrapper.Command... commands)
+      throws Throwable {
+    return getDebugTestRunner(config, debuggeeClass, Arrays.asList(commands));
+  }
+
+  protected DebugTestRunner getDebugTestRunner(
+      DebugTestConfig config, String debuggeeClass, List<JUnit3Wrapper.Command> commands)
+      throws Throwable {
     // Skip test due to unsupported runtime.
     Assume.assumeTrue("Skipping test " + testName.getMethodName() + " because ART is not supported",
         ToolHelper.artSupported());
@@ -152,7 +164,7 @@
             ? null
             : ClassNameMapper.mapperFromFile(config.getProguardMap());
 
-    new JUnit3Wrapper(config, debuggeeClass, commands, classNameMapper).runBare();
+    return new JUnit3Wrapper(config, debuggeeClass, commands, classNameMapper);
   }
 
   /**
@@ -495,8 +507,14 @@
     return t -> inspector.accept(t.debuggeeState.getLocalValues().get(localName));
   }
 
+  protected interface DebugTestRunner {
+    void enqueueCommandFirst(JUnit3Wrapper.Command command);
+
+    void runBare() throws Throwable;
+  }
+
   @Ignore("Prevents Gradle from running the wrapper as a test.")
-  static class JUnit3Wrapper extends JDWPTestCase {
+  public static class JUnit3Wrapper extends JDWPTestCase implements DebugTestRunner {
 
     private final String debuggeeClassName;
 
@@ -630,7 +648,7 @@
           return null;
         }
         ClassNamingForNameMapper.MappedRangesOfName ranges =
-            classNaming.mappedRangesByName.get(obfuscatedMethodName);
+            classNaming.mappedRangesByRenamedName.get(obfuscatedMethodName);
         if (ranges == null) {
           return null;
         }
@@ -683,7 +701,7 @@
         if (range == null) {
           return obfuscatedLineNumber;
         }
-        return range.originalLineFromObfuscated(obfuscatedLineNumber);
+        return range.getOriginalLineNumber(obfuscatedLineNumber);
       }
 
       @Override
@@ -694,7 +712,7 @@
           return null;
         }
         ClassNamingForNameMapper.MappedRangesOfName ranges =
-            classNaming.mappedRangesByName.get(obfuscatedMethodName);
+            classNaming.mappedRangesByRenamedName.get(obfuscatedMethodName);
         if (ranges == null) {
           return null;
         }
@@ -706,8 +724,7 @@
         for (MappedRange range : mappedRanges) {
           lines.add(
               new SignatureAndLine(
-                  range.signature.toString(),
-                  range.originalLineFromObfuscated(obfuscatedLineNumber)));
+                  range.signature.toString(), range.getOriginalLineNumber(obfuscatedLineNumber)));
         }
         return lines;
       }
@@ -801,8 +818,10 @@
         // Continue stepping until mainLoopStep exits with false.
       }
 
-      assertTrue(
-          "All commands have NOT been processed for config: " + config, commandsQueue.isEmpty());
+      if (config.mustProcessAllCommands()) {
+        assertTrue(
+            "All commands have NOT been processed for config: " + config, commandsQueue.isEmpty());
+      }
 
       logWriter.println("Finish loop");
     }
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
index 556e17f..519b580 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
@@ -17,6 +17,7 @@
     DEX
   }
 
+  private boolean mustProcessAllCommands = true;
   private List<Path> paths = new ArrayList<>();
 
   private Path proguardMap = null;
@@ -47,6 +48,14 @@
     return this;
   }
 
+  public boolean mustProcessAllCommands() {
+    return mustProcessAllCommands;
+  }
+
+  public void allowUnprocessedCommands() {
+    mustProcessAllCommands = false;
+  }
+
   /** Proguard map that the debuggee has been translated according to, null if not present. */
   public Path getProguardMap() {
     return proguardMap;
diff --git a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
index 6de20c6..6dccf0b 100644
--- a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
@@ -51,6 +51,7 @@
     ToolHelper.runR8(
         R8Command.builder()
             .addProgramFiles(input)
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setMode(CompilationMode.DEBUG)
             .setOutput(output, OutputMode.ClassFile)
             .build(),
diff --git a/src/test/java/com/android/tools/r8/debug/IincDebugTestDump.java b/src/test/java/com/android/tools/r8/debug/IincDebugTestDump.java
index 8ff3d92..670a3bc 100644
--- a/src/test/java/com/android/tools/r8/debug/IincDebugTestDump.java
+++ b/src/test/java/com/android/tools/r8/debug/IincDebugTestDump.java
@@ -4,7 +4,10 @@
 
 package com.android.tools.r8.debug;
 
-import org.objectweb.asm.*;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
 
 public class IincDebugTestDump implements Opcodes {
 
diff --git a/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
index bc390b9..a275e6b 100644
--- a/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.stream.Stream;
 import org.junit.Assume;
 import org.junit.Test;
@@ -115,7 +114,7 @@
             .setProgramConsumer(consumer)
             .addProgramFiles(inputJar);
     if ((consumer instanceof ClassFileConsumer)) {
-      builder.addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME));
+      builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
     } else {
       builder.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()));
     }
diff --git a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
index 6e1c05f..5cbe03d 100644
--- a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
@@ -60,7 +60,9 @@
     DebugTestConfig config = null;
 
     if (runtimeKind == RuntimeKind.CF) {
-      builder.setOutput(outjar, OutputMode.ClassFile);
+      builder
+          .setOutput(outjar, OutputMode.ClassFile)
+          .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
       config = new CfDebugTestConfig(outjar);
     } else {
       assert (runtimeKind == RuntimeKind.DEX);
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java b/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java
index 36cdad0..09a46e5 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsLiveAtBlockEntryDebugTest.java
@@ -7,9 +7,9 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.jasmin.JasminBuilder;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.Collections;
@@ -53,8 +53,8 @@
   }
 
   private void runTest(DebugTestConfig config) throws Throwable {
-    DexInspector inspector =
-        new DexInspector(
+    CodeInspector inspector =
+        new CodeInspector(
             (config instanceof CfDebugTestConfig)
                 ? Collections.singletonList(config.getPaths().get(1).resolve(className + ".class"))
                 : config.getPaths());
diff --git a/src/test/java/com/android/tools/r8/debug/MinificationTest.java b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
index 2f995db..1b51cd8 100644
--- a/src/test/java/com/android/tools/r8/debug/MinificationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
@@ -15,9 +15,9 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.Collection;
@@ -80,7 +80,6 @@
       proguardConfigurations = builder.build();
     }
 
-    AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
     Path outputPath = temp.getRoot().toPath().resolve("classes.zip");
     Path proguardMap = writeProguardMap ? temp.getRoot().toPath().resolve("proguard.map") : null;
     OutputMode outputMode =
@@ -89,10 +88,12 @@
         R8Command.builder()
             .addProgramFiles(DEBUGGEE_JAR)
             .setOutput(outputPath, outputMode)
-            .setMode(CompilationMode.DEBUG)
-            .addLibraryFiles(ToolHelper.getAndroidJar(minSdk));
-    if (runtimeKind != RuntimeKind.CF) {
-      builder.setMinApiLevel(minSdk.getLevel());
+            .setMode(CompilationMode.DEBUG);
+    if (runtimeKind == RuntimeKind.DEX) {
+      AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
+      builder.setMinApiLevel(minSdk.getLevel()).addLibraryFiles(ToolHelper.getAndroidJar(minSdk));
+    } else if (runtimeKind == RuntimeKind.CF) {
+      builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
     }
     if (proguardMap != null) {
       builder.setProguardMapOutputPath(proguardMap);
@@ -187,7 +188,7 @@
       throws Throwable {
     Path proguardMap = config.getProguardMap();
     String mappingFile = proguardMap == null ? null : proguardMap.toString();
-    DexInspector inspector = new DexInspector(config.getPaths(), mappingFile);
+    CodeInspector inspector = new CodeInspector(config.getPaths(), mappingFile);
     ClassSubject clazz = inspector.clazz(className);
     assertTrue(clazz.isPresent());
     if (method != null) {
diff --git a/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java b/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java
index 3ea4017..813a873 100644
--- a/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
 import com.android.tools.r8.utils.DescriptorUtils;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.stream.Stream;
 import org.junit.Assume;
 import org.junit.Test;
@@ -47,7 +46,7 @@
     Path outputJar = temp.getRoot().toPath().resolve(outputName);
     ToolHelper.runR8(
         R8Command.builder()
-            .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setMode(CompilationMode.DEBUG)
             .addProgramFiles(inputJar)
             .setOutput(outputJar, OutputMode.ClassFile)
diff --git a/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java b/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
index e4a88f0..67b7c1a 100644
--- a/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
 import java.nio.file.Path;
@@ -21,14 +20,13 @@
 
   private static synchronized AndroidApp getCompiledResources() throws Throwable {
     if (compiledResources == null) {
-      AndroidApiLevel minApi = ToolHelper.getMinApiLevelForDexVm();
       AndroidAppConsumers sink = new AndroidAppConsumers();
       R8.run(
           R8Command.builder()
               .setMode(CompilationMode.DEBUG)
               .addProgramFiles(DebugTestBase.DEBUGGEE_JAR)
               .setProgramConsumer(sink.wrapClassFileConsumer(null))
-              .addLibraryFiles(ToolHelper.getAndroidJar(minApi))
+              .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
               .build());
       compiledResources = sink.build();
     }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
index 4ce3b5a..3ccf46d 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/DebugInfoInspector.java
@@ -19,8 +19,8 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.HashSet;
@@ -49,13 +49,13 @@
     }
   }
 
-  public DebugInfoInspector(DexInspector inspector, String clazz, MethodSignature method) {
+  public DebugInfoInspector(CodeInspector inspector, String clazz, MethodSignature method) {
     this(inspector.clazz(clazz).method(method).getMethod(), inspector.getFactory());
   }
 
   public DebugInfoInspector(AndroidApp app, String clazz, MethodSignature method)
       throws IOException, ExecutionException {
-    this(new DexInspector(app), clazz, method);
+    this(new CodeInspector(app), clazz, method);
   }
 
   public boolean hasLocalsInfo() {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
index c67cf32..ce84f51 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
@@ -40,33 +40,49 @@
 @RunWith(Parameterized.class)
 public class InliningWithoutPositionsTestRunner {
 
+  enum Backend {
+    CF,
+    DEX
+  }
+
   private static final String TEST_CLASS = "InliningWithoutPositionsTestSource";
   private static final String TEST_PACKAGE = "com.android.tools.r8.debuginfo";
 
   @ClassRule public static TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
+  private final Backend backend;
   private final boolean mainPos;
   private final boolean foo1Pos;
   private final boolean barPos;
   private final boolean foo2Pos;
   private final Location throwLocation;
 
-  @Parameters(name = "main/foo1/bar/foo2 positions: {0}/{1}/{2}/{3}, throwLocation: {4}")
+  @Parameters(name = "{0}: main/foo1/bar/foo2 positions: {1}/{2}/{3}/{4}, throwLocation: {5}")
   public static Collection<Object[]> data() {
     List<Object[]> testCases = new ArrayList<>();
+    for (Backend backend : Backend.values()) {
     for (int i = 0; i < 16; ++i) {
       for (Location throwLocation : Location.values()) {
         if (throwLocation != Location.MAIN) {
-          testCases.add(
-              new Object[] {(i & 1) != 0, (i & 2) != 0, (i & 4) != 0, (i & 8) != 0, throwLocation});
+            testCases.add(
+                new Object[] {
+                  backend, (i & 1) != 0, (i & 2) != 0, (i & 4) != 0, (i & 8) != 0, throwLocation
+                });
         }
       }
+      }
     }
     return testCases;
   }
 
   public InliningWithoutPositionsTestRunner(
-      boolean mainPos, boolean foo1Pos, boolean barPos, boolean foo2Pos, Location throwLocation) {
+      Backend backend,
+      boolean mainPos,
+      boolean foo1Pos,
+      boolean barPos,
+      boolean foo2Pos,
+      Location throwLocation) {
+    this.backend = backend;
     this.mainPos = mainPos;
     this.foo1Pos = foo1Pos;
     this.barPos = barPos;
@@ -79,33 +95,45 @@
     // See InliningWithoutPositionsTestSourceDump for the code compiled here.
     Path testClassDir = temp.newFolder(TEST_PACKAGE.split(".")).toPath();
     Path testClassPath = testClassDir.resolve(TEST_CLASS + ".class");
-    Path outputDexPath = temp.newFolder().toPath();
+    Path outputPath = temp.newFolder().toPath();
 
     Files.write(
         testClassPath,
         InliningWithoutPositionsTestSourceDump.dump(
             mainPos, foo1Pos, barPos, foo2Pos, throwLocation));
 
-    AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
     Path proguardMapPath = testClassDir.resolve("proguard.map");
 
-    ToolHelper.runR8(
+    R8Command.Builder builder =
         R8Command.builder()
             .addProgramFiles(testClassPath)
-            .setMinApiLevel(minSdk.getLevel())
-            .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
-            .setOutput(outputDexPath, OutputMode.DexIndexed)
             .setMode(CompilationMode.RELEASE)
-            .setProguardMapOutputPath(proguardMapPath)
-            .build(),
-        options -> options.inliningInstructionLimit = 20);
+            .setProguardMapOutputPath(proguardMapPath);
+    if (backend == Backend.DEX) {
+      AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
+      builder
+          .setMinApiLevel(minSdk.getLevel())
+          .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
+          .setOutput(outputPath, OutputMode.DexIndexed);
+    } else {
+      assert (backend == Backend.CF);
+      builder
+          .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+          .setOutput(outputPath, OutputMode.ClassFile);
+    }
 
-    ArtCommandBuilder artCommandBuilder = new ArtCommandBuilder();
-    artCommandBuilder.appendClasspath(outputDexPath.resolve("classes.dex").toString());
-    artCommandBuilder.setMainClass(TEST_PACKAGE + "." + TEST_CLASS);
+    ToolHelper.runR8(builder.build(), options -> options.inliningInstructionLimit = 40);
 
-    ProcessResult result = ToolHelper.runArtRaw(artCommandBuilder);
+    ProcessResult result;
+    if (backend == Backend.DEX) {
+      ArtCommandBuilder artCommandBuilder = new ArtCommandBuilder();
+      artCommandBuilder.appendClasspath(outputPath.resolve("classes.dex").toString());
+      artCommandBuilder.setMainClass(TEST_PACKAGE + "." + TEST_CLASS);
 
+      result = ToolHelper.runArtRaw(artCommandBuilder);
+    } else {
+      result = ToolHelper.runJava(outputPath, TEST_PACKAGE + "." + TEST_CLASS);
+    }
     assertNotEquals(result.exitCode, 0);
 
     // Verify stack trace.
@@ -157,7 +185,7 @@
     ClassNamingForNameMapper classNaming = mapper.getClassNaming(TEST_PACKAGE + "." + TEST_CLASS);
     assertNotNull(classNaming);
 
-    MappedRangesOfName rangesForMain = classNaming.mappedRangesByName.get("main");
+    MappedRangesOfName rangesForMain = classNaming.mappedRangesByRenamedName.get("main");
     assertNotNull(rangesForMain);
 
     List<MappedRange> frames = rangesForMain.allRangesForLine(expectedLineNumber);
diff --git a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestSourceDump.java b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestSourceDump.java
index 53d6df3..d0ddf00 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestSourceDump.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestSourceDump.java
@@ -4,8 +4,12 @@
 
 package com.android.tools.r8.debuginfo;
 
-import java.util.*;
-import org.objectweb.asm.*;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
 
 public class InliningWithoutPositionsTestSourceDump implements Opcodes {
 
@@ -61,8 +65,7 @@
       }
   */
   public static byte[] dump(
-      boolean mainPos, boolean foo1Pos, boolean barPos, boolean foo2Pos, Location throwLocation)
-      throws Exception {
+      boolean mainPos, boolean foo1Pos, boolean barPos, boolean foo2Pos, Location throwLocation) {
     ClassWriter cw = new ClassWriter(0);
     FieldVisitor fv;
     MethodVisitor mv;
diff --git a/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
index 103c07f..80279d5 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import org.junit.Test;
 
 public class KotlinDebugInfoTestRunner extends TestBase {
@@ -74,7 +73,7 @@
             .setProgramConsumer(consumer)
             .addProgramFiles(inputJar);
     if ((consumer instanceof ClassFileConsumer)) {
-      builder.addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME));
+      builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
     } else {
       builder.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()));
     }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/KotlinReflectionDump.java b/src/test/java/com/android/tools/r8/debuginfo/KotlinReflectionDump.java
index f8bddbb..ac2a8ff 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/KotlinReflectionDump.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/KotlinReflectionDump.java
@@ -3,7 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debuginfo;
 
-import org.objectweb.asm.*;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
 
 public class KotlinReflectionDump implements Opcodes {
 
diff --git a/src/test/java/com/android/tools/r8/debuginfo/KotlinRingBufferDump.java b/src/test/java/com/android/tools/r8/debuginfo/KotlinRingBufferDump.java
index 11b981a..4811648 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/KotlinRingBufferDump.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/KotlinRingBufferDump.java
@@ -3,7 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debuginfo;
 
-import org.objectweb.asm.*;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
 
 public class KotlinRingBufferDump implements Opcodes {
 
diff --git a/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestSourceDump.java b/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestSourceDump.java
index 5ed2a4e..7067295 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestSourceDump.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/PreamblePositionTestSourceDump.java
@@ -4,8 +4,12 @@
 
 package com.android.tools.r8.debuginfo;
 
-import java.util.*;
-import org.objectweb.asm.*;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
 
 /*
 Generated from the source code below, line numbers removed, except for the false branch,
diff --git a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
index d452699..3b1e30a 100644
--- a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
+++ b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
@@ -26,7 +26,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.Files;
 import java.nio.file.Path;
@@ -127,7 +127,7 @@
     AndroidApp application = AndroidApp.builder()
         .addDexProgramData(Files.toByteArray(originalDexFile.toFile()), Origin.unknown())
         .build();
-    DexInspector inspector = new DexInspector(application);
+    CodeInspector inspector = new CodeInspector(application);
     DexEncodedMethod method = getMethod(
         inspector,
         "android.databinding.DataBinderMapperImpl",
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index ead9175..3b12cc4 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -22,6 +22,7 @@
 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.GraphLense;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.naming.NamingLens;
@@ -135,7 +136,14 @@
     options.programConsumer = consumer;
     ApplicationWriter writer =
         new ApplicationWriter(
-            application, options, null, null, NamingLens.getIdentityLens(), null, null);
+            application,
+            options,
+            null,
+            null,
+            GraphLense.getIdentityLense(),
+            NamingLens.getIdentityLens(),
+            null,
+            null);
     ExecutorService executorService = ThreadUtils.getExecutorService(options);
     writer.write(executorService);
     List<Set<String>> generatedDescriptors = consumer.getDescriptors();
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
index a0b2515..5848396 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
@@ -20,9 +20,9 @@
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dexsplitter.DexSplitter.Options;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import java.io.FileNotFoundException;
@@ -32,6 +32,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.zip.ZipEntry;
@@ -50,6 +51,8 @@
   private static final String CLASS3_INNER_CLASS = CLASS_DIR + "/Class3$InnerClass.class";
   private static final String CLASS4_CLASS = CLASS_DIR + "/Class4.class";
   private static final String CLASS4_LAMBDA_INTERFACE = CLASS_DIR + "/Class4$LambdaInterface.class";
+  private static final String TEXT_FILE =
+      ToolHelper.EXAMPLES_ANDROID_N_DIR + "dexsplitsample/TextFile.txt";
 
 
   @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
@@ -222,8 +225,7 @@
 
   @Test
   public void splitFilesFromJar()
-      throws IOException, CompilationFailedException, FeatureMappingException, ResourceException,
-          ExecutionException {
+      throws IOException, CompilationFailedException, FeatureMappingException {
     splitFromJars(true, true);
     splitFromJars(false, true);
     splitFromJars(true, false);
@@ -304,7 +306,6 @@
     validateUnobfuscatedOutput(base, feature);
   }
 
-
   @Test
   public void splitFilesObfuscation()
       throws CompilationFailedException, IOException, ExecutionException {
@@ -363,9 +364,109 @@
 
     // Ensure that the Class1 is actually in the correct split. Note that Class2 would have been
     // shaken away.
-    DexInspector inspector = new DexInspector(base, proguardMap.toString());
+    CodeInspector inspector = new CodeInspector(base, proguardMap.toString());
     ClassSubject subject = inspector.clazz("dexsplitsample.Class1");
     assertTrue(subject.isPresent());
     assertTrue(subject.isRenamed());
   }
+
+  @Test
+  public void splitNonClassFiles()
+      throws CompilationFailedException, IOException, FeatureMappingException {
+    Path inputZip = temp.getRoot().toPath().resolve("input-zip-with-non-class-files.jar");
+    ZipOutputStream inputZipStream = new ZipOutputStream(Files.newOutputStream(inputZip));
+    String name = "dexsplitsample/TextFile.txt";
+    inputZipStream.putNextEntry(new ZipEntry(name));
+    byte[] fileBytes = Files.readAllBytes(Paths.get(TEXT_FILE));
+    inputZipStream.write(fileBytes);
+    inputZipStream.closeEntry();
+    name = "dexsplitsample/TextFile2.txt";
+    inputZipStream.putNextEntry(new ZipEntry(name));
+    inputZipStream.write(fileBytes);
+    inputZipStream.write(fileBytes);
+    inputZipStream.closeEntry();
+    inputZipStream.close();
+    Path output = temp.newFolder().toPath().resolve("output");
+    Files.createDirectory(output);
+    Path baseJar = temp.getRoot().toPath().resolve("base.jar");
+    Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
+    ZipOutputStream baseStream = new ZipOutputStream(Files.newOutputStream(baseJar));
+    name = "dexsplitsample/TextFile.txt";
+    baseStream.putNextEntry(new ZipEntry(name));
+    baseStream.write(fileBytes);
+    baseStream.closeEntry();
+    baseStream.close();
+    ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
+    name = "dexsplitsample/TextFile2.txt";
+    featureStream.putNextEntry(new ZipEntry(name));
+    featureStream.write(fileBytes);
+    featureStream.write(fileBytes);
+    featureStream.closeEntry();
+    featureStream.close();
+    Options options = new Options();
+    options.addInputArchive(inputZip.toString());
+    options.setOutput(output.toString());
+    options.addFeatureJar(baseJar.toString());
+    options.addFeatureJar(featureJar.toString());
+    options.setSplitNonClassResources(true);
+    DexSplitter.run(options);
+    Path baseDir = output.resolve("base");
+    Path featureDir = output.resolve("feature1");
+    byte[] contents = fileBytes;
+    byte[] contents2 = new byte[contents.length * 2];
+    System.arraycopy(contents, 0, contents2, 0, contents.length);
+    System.arraycopy(contents, 0, contents2, contents.length, contents.length);
+    Path baseTextFile = baseDir.resolve("dexsplitsample/TextFile.txt");
+    Path featureTextFile = featureDir.resolve("dexsplitsample/TextFile2.txt");
+    assert Files.exists(baseTextFile);
+    assert Files.exists(featureTextFile);
+    assert Arrays.equals(Files.readAllBytes(baseTextFile), contents);
+    assert Arrays.equals(Files.readAllBytes(featureTextFile), contents2);
+  }
+
+  @Test
+  public void splitDuplicateNonClassFiles()
+      throws IOException, CompilationFailedException, FeatureMappingException {
+    Path inputZip = temp.getRoot().toPath().resolve("input-zip-with-non-class-files.jar");
+    ZipOutputStream inputZipStream = new ZipOutputStream(Files.newOutputStream(inputZip));
+    String name = "dexsplitsample/TextFile.txt";
+    inputZipStream.putNextEntry(new ZipEntry(name));
+    byte[] fileBytes = Files.readAllBytes(Paths.get(TEXT_FILE));
+    inputZipStream.write(fileBytes);
+    inputZipStream.closeEntry();
+    inputZipStream.close();
+    Path output = temp.newFolder().toPath().resolve("output");
+    Files.createDirectory(output);
+    Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
+    Path feature2Jar = temp.getRoot().toPath().resolve("feature2.jar");
+    ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
+    name = "dexsplitsample/TextFile.txt";
+    featureStream.putNextEntry(new ZipEntry(name));
+    featureStream.write(fileBytes);
+    featureStream.closeEntry();
+    featureStream.close();
+    ZipOutputStream feature2Stream = new ZipOutputStream(Files.newOutputStream(feature2Jar));
+    name = "dexsplitsample/TextFile.txt";
+    feature2Stream.putNextEntry(new ZipEntry(name));
+    feature2Stream.write(fileBytes);
+    feature2Stream.closeEntry();
+    feature2Stream.close();
+    Options options = new Options();
+    options.addInputArchive(inputZip.toString());
+    options.setOutput(output.toString());
+    options.addFeatureJar(feature2Jar.toString());
+    options.addFeatureJar(featureJar.toString());
+    options.setSplitNonClassResources(true);
+    DexSplitter.run(options);
+    Path baseDir = output.resolve("base");
+    Path feature1Dir = output.resolve("feature1");
+    Path feature2Dir = output.resolve("feature2");
+    Path baseTextFile = baseDir.resolve("dexsplitsample/TextFile.txt");
+    Path feature1TextFile = feature1Dir.resolve("dexsplitsample/TextFile2.txt");
+    Path feature2TextFile = feature2Dir.resolve("dexsplitsample/TextFile2.txt");
+    assert !Files.exists(feature1TextFile);
+    assert !Files.exists(feature2TextFile);
+    assert Files.exists(baseTextFile);
+    assert Arrays.equals(Files.readAllBytes(baseTextFile), fileBytes);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index 6877617..39b5ff8 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -23,7 +23,7 @@
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.Collections;
@@ -77,7 +77,7 @@
 
     AndroidApp application = buildApplication(builder);
     AppInfo appInfo = getAppInfo(application);
-    DexInspector inspector = new DexInspector(appInfo.app);
+    CodeInspector inspector = new CodeInspector(appInfo.app);
     DexEncodedMethod method = getMethod(inspector, DEFAULT_CLASS_NAME, "int", "x",
         ImmutableList.of());
     assertNull(appInfo.lookupVirtualTarget(method.method.holder, method.method));
@@ -147,7 +147,7 @@
 
     AndroidApp application = buildApplication(builder);
     AppInfo appInfo = getAppInfo(application);
-    DexInspector inspector = new DexInspector(appInfo.app);
+    CodeInspector inspector = new CodeInspector(appInfo.app);
 
     DexMethod methodXOnTestSuper =
         getMethod(inspector, "TestSuper", "int", "x", ImmutableList.of()).method;
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
index 39c8d28..2ec38b4 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -28,12 +28,12 @@
 import com.android.tools.r8.utils.ArtErrorParser;
 import com.android.tools.r8.utils.ArtErrorParser.ArtErrorInfo;
 import com.android.tools.r8.utils.ArtErrorParser.ArtErrorParserException;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
-import com.android.tools.r8.utils.DexInspector.FoundFieldSubject;
-import com.android.tools.r8.utils.DexInspector.FoundMethodSubject;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundFieldSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.google.common.io.ByteStreams;
 import com.google.common.io.Closer;
 import java.io.File;
@@ -132,8 +132,8 @@
       if (referenceApk == null) {
         throw e;
       }
-      DexInspector theirs = new DexInspector(Paths.get(referenceApk));
-      DexInspector ours = new DexInspector(out);
+      CodeInspector theirs = new CodeInspector(Paths.get(referenceApk));
+      CodeInspector ours = new CodeInspector(out);
       List<ArtErrorInfo> errors;
       try {
         errors = ArtErrorParser.parse(e.getMessage());
@@ -194,8 +194,8 @@
   public void assertIdenticalApplicationsUpToCode(
       AndroidApp app1, AndroidApp app2, boolean allowNewClassesInApp2)
       throws IOException, ExecutionException {
-    DexInspector inspect1 = new DexInspector(app1);
-    DexInspector inspect2 = new DexInspector(app2);
+    CodeInspector inspect1 = new CodeInspector(app1);
+    CodeInspector inspect2 = new CodeInspector(app2);
 
     class Pair<T> {
       private T first;
@@ -213,7 +213,7 @@
     // Collect all classes from both inspectors, indexed by finalDescriptor.
     Map<String, Pair<FoundClassSubject>> allClasses = new HashMap<>();
 
-    BiConsumer<DexInspector, Boolean> collectClasses =
+    BiConsumer<CodeInspector, Boolean> collectClasses =
         (inspector, selectFirst) -> {
           inspector.forAllClasses(
               clazz -> {
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
index abc7a73..6ae151e 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
@@ -3,13 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.internal;
 
+import static junit.framework.TestCase.assertEquals;
+
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.beust.jcommander.internal.Lists;
@@ -20,52 +21,60 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
-import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 
 public class R8GMSCoreDeterministicTest extends GMSCoreCompilationTestBase {
 
-  public Set<DexEncodedMethod> shuffle(Set<DexEncodedMethod> methods) {
+  private static class CompilationResult {
+    AndroidApp app;
+    String proguardMap;
+  }
+
+  private static Set<DexEncodedMethod> shuffle(Set<DexEncodedMethod> methods) {
     List<DexEncodedMethod> toShuffle = Lists.newArrayList(methods);
     Collections.shuffle(toShuffle);
     return new LinkedHashSet<>(toShuffle);
   }
 
-  private AndroidApp doRun()
-      throws IOException, ProguardRuleParserException, ExecutionException,
-      CompilationFailedException {
+  private CompilationResult doRun() throws IOException, CompilationFailedException {
     R8Command command =
         R8Command.builder()
             .addProgramFiles(Paths.get(GMSCORE_V7_DIR, GMSCORE_APK))
             .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
             .setMinApiLevel(AndroidApiLevel.L.getLevel())
             .build();
-    return ToolHelper.runR8(
-        command,
-        options -> {
-          // For this test just do random shuffle.
-          options.testing.irOrdering = this::shuffle;
-          // Only use one thread to process to process in the order decided by the callback.
-          options.numberOfThreads = 1;
-          // Ignore the missing classes.
-          options.ignoreMissingClasses = true;
-        });
+    CompilationResult result = new CompilationResult();
+    result.app =
+        ToolHelper.runR8(
+            command,
+            options -> {
+              // For this test just do random shuffle.
+              options.testing.irOrdering = R8GMSCoreDeterministicTest::shuffle;
+              // Only use one thread to process to process in the order decided by the callback.
+              options.numberOfThreads = 1;
+              // Ignore the missing classes.
+              options.ignoreMissingClasses = true;
+              // Store the generated Proguard map.
+              options.proguardMapConsumer =
+                  (proguardMap, handler) -> result.proguardMap = proguardMap;
+            });
+    return result;
   }
 
   @Test
   public void deterministic() throws Exception {
-
     // Run two independent compilations.
-    AndroidApp app1 = doRun();
-    AndroidApp app2 = doRun();
+    CompilationResult result1 = doRun();
+    CompilationResult result2 = doRun();
 
     // Check that the generated bytecode runs through the dex2oat verifier with no errors.
     Path combinedInput = temp.getRoot().toPath().resolve("all.jar");
     Path oatFile = temp.getRoot().toPath().resolve("all.oat");
-    app1.writeToZip(combinedInput, OutputMode.DexIndexed);
+    result1.app.writeToZip(combinedInput, OutputMode.DexIndexed);
     ToolHelper.runDex2Oat(combinedInput, oatFile);
 
     // Verify that the result of the two compilations was the same.
-    assertIdenticalApplications(app1, app2);
+    assertIdenticalApplications(result1.app, result2.app);
+    assertEquals(result1.proguardMap, result2.proguardMap);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
index 6adf680..d881185 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.internal;
 
+import static org.junit.Assert.assertEquals;
+
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.utils.AndroidApp;
@@ -11,17 +13,33 @@
 
 public class R8GMSCoreV10DeployJarVerificationTest extends GMSCoreDeployJarVerificationTest {
 
+  private String proguardMap1 = null;
+  private String proguardMap2 = null;
+
   @Test
   public void buildFromDeployJar() throws Exception {
     // TODO(tamaskenez): set hasReference = true when we have the noshrink file for V10
-    AndroidApp app1 = buildFromDeployJar(
-        CompilerUnderTest.R8, CompilationMode.RELEASE,
-        GMSCoreCompilationTestBase.GMSCORE_V10_DIR, false);
-    AndroidApp app2 = buildFromDeployJar(
-        CompilerUnderTest.R8, CompilationMode.RELEASE,
-        GMSCoreCompilationTestBase.GMSCORE_V10_DIR, false);
+    AndroidApp app1 =
+        buildFromDeployJar(
+            CompilerUnderTest.R8,
+            CompilationMode.RELEASE,
+            GMSCoreCompilationTestBase.GMSCORE_V10_DIR,
+            false,
+            options ->
+                options.proguardMapConsumer =
+                    (proguardMap, handler) -> this.proguardMap1 = proguardMap);
+    AndroidApp app2 =
+        buildFromDeployJar(
+            CompilerUnderTest.R8,
+            CompilationMode.RELEASE,
+            GMSCoreCompilationTestBase.GMSCORE_V10_DIR,
+            false,
+            options ->
+                options.proguardMapConsumer =
+                    (proguardMap, handler) -> this.proguardMap2 = proguardMap);
 
     // Verify that the result of the two compilations was the same.
     assertIdenticalApplications(app1, app2);
+    assertEquals(proguardMap1, proguardMap2);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
index ae470c6..4374d0e 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.internal;
 
+import static org.junit.Assert.assertEquals;
+
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.utils.AndroidApp;
 import org.junit.Test;
@@ -10,15 +12,33 @@
 public class R8GMSCoreV10TreeShakeJarVerificationTest
     extends R8GMSCoreTreeShakeJarVerificationTest {
 
+  private String proguardMap1 = null;
+  private String proguardMap2 = null;
+
   @Test
   public void buildAndTreeShakeFromDeployJar() throws Exception {
     // TODO(tamaskenez): set hasReference = true when we have the noshrink file for V10
-    AndroidApp app1 = buildAndTreeShakeFromDeployJar(
-        CompilationMode.RELEASE, GMSCORE_V10_DIR, false, GMSCORE_V10_MAX_SIZE, null);
-    AndroidApp app2 = buildAndTreeShakeFromDeployJar(
-        CompilationMode.RELEASE, GMSCORE_V10_DIR, false, GMSCORE_V10_MAX_SIZE, null);
+    AndroidApp app1 =
+        buildAndTreeShakeFromDeployJar(
+            CompilationMode.RELEASE,
+            GMSCORE_V10_DIR,
+            false,
+            GMSCORE_V10_MAX_SIZE,
+            options ->
+                options.proguardMapConsumer =
+                    (proguardMap, handler) -> this.proguardMap1 = proguardMap);
+    AndroidApp app2 =
+        buildAndTreeShakeFromDeployJar(
+            CompilationMode.RELEASE,
+            GMSCORE_V10_DIR,
+            false,
+            GMSCORE_V10_MAX_SIZE,
+            options ->
+                options.proguardMapConsumer =
+                    (proguardMap, handler) -> this.proguardMap2 = proguardMap);
 
     // Verify that the result of the two compilations was the same.
     assertIdenticalApplications(app1, app2);
+    assertEquals(proguardMap1, proguardMap2);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java b/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
new file mode 100644
index 0000000..68daeb8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.invalid;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.jasmin.JasminTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import org.junit.Test;
+
+public class DuplicateDefinitionsTest extends JasminTestBase {
+
+  @Test
+  public void testDuplicateMethods() throws Exception {
+    JasminBuilder jasminBuilder = new JasminBuilder();
+    ClassBuilder classBuilder = jasminBuilder.addClass("C");
+    classBuilder.addMainMethod(".limit locals 1", ".limit stack 0", "return");
+    classBuilder.addMainMethod(".limit locals 1", ".limit stack 0", "return");
+    classBuilder.addVirtualMethod("method", "V", ".limit locals 1", ".limit stack 0", "return");
+    classBuilder.addVirtualMethod("method", "V", ".limit locals 1", ".limit stack 0", "return");
+
+    // Run D8 and intercept warnings.
+    PrintStream stderr = System.err;
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    System.setErr(new PrintStream(baos));
+
+    AndroidApp app = compileWithD8(jasminBuilder.build());
+
+    String output = new String(baos.toByteArray(), Charset.defaultCharset());
+    System.setOut(stderr);
+
+    // Check that warnings were emitted.
+    assertThat(
+        output,
+        containsString(
+            "Ignoring an implementation of the method `void C.main(java.lang.String[])` because "
+                + "it has multiple definitions"));
+    assertThat(
+        output,
+        containsString(
+            "Ignoring an implementation of the method `void C.method()` because "
+                + "it has multiple definitions"));
+
+    CodeInspector inspector = new CodeInspector(app);
+    ClassSubject clazz = inspector.clazz("C");
+    assertThat(clazz, isPresent());
+
+    // There are two direct methods, but only because one is <init>.
+    assertEquals(2, clazz.getDexClass().directMethods().length);
+    assertThat(clazz.method("void", "<init>", ImmutableList.of()), isPresent());
+
+    // There is only one virtual method.
+    assertEquals(1, clazz.getDexClass().virtualMethods().length);
+  }
+
+  @Test
+  public void testDuplicateFields() throws Exception {
+    JasminBuilder jasminBuilder = new JasminBuilder();
+    ClassBuilder classBuilder = jasminBuilder.addClass("C");
+    classBuilder.addField("public", "fld", "LC;", null);
+    classBuilder.addField("public", "fld", "LC;", null);
+    classBuilder.addStaticField("staticFld", "LC;", null);
+    classBuilder.addStaticField("staticFld", "LC;", null);
+
+    // Run D8 and intercept warnings.
+    PrintStream stderr = System.err;
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    System.setErr(new PrintStream(baos));
+
+    AndroidApp app = compileWithD8(jasminBuilder.build());
+
+    String output = new String(baos.toByteArray(), Charset.defaultCharset());
+    System.setOut(stderr);
+
+    // Check that warnings were emitted.
+    assertThat(output, containsString("Field `C C.fld` has multiple definitions"));
+    assertThat(output, containsString("Field `C C.staticFld` has multiple definitions"));
+
+    CodeInspector inspector = new CodeInspector(app);
+    ClassSubject clazz = inspector.clazz("C");
+    assertThat(clazz, isPresent());
+
+    // Redundant fields have been removed.
+    assertEquals(1, clazz.getDexClass().instanceFields().length);
+    assertEquals(1, clazz.getDexClass().staticFields().length);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
index d4ab6c6..b66e6e0 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -20,9 +20,9 @@
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
-import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
 import java.util.List;
 import java.util.ListIterator;
@@ -61,7 +61,7 @@
       String returnType,
       String methodName,
       List<String> parameters) {
-    DexInspector inspector = new DexInspector(application);
+    CodeInspector inspector = new CodeInspector(application);
     return getMethod(inspector, className, returnType, methodName, parameters);
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestDump.java b/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestDump.java
index 5a93117..0094ad1 100644
--- a/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestDump.java
+++ b/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestDump.java
@@ -3,7 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir;
 
-import org.objectweb.asm.*;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
 
 public class PhiDefinitionsTestDump implements Opcodes {
 
diff --git a/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java b/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
index 4f2bdeb..ba43a10 100644
--- a/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import java.io.IOException;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import org.junit.Test;
 
 public class PhiDefinitionsTestRunner extends TestBase {
@@ -111,7 +110,7 @@
             .setProgramConsumer(consumer)
             .addProgramFiles(inputJar);
     if (consumer instanceof ClassFileConsumer) {
-      builder.addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME));
+      builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
     } else {
       builder.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()));
     }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index 16785f6..a329513 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.ArrayGet;
 import com.android.tools.r8.ir.code.IRCode;
@@ -25,18 +26,18 @@
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.NonNull;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.NonNullTracker;
 import com.android.tools.r8.ir.optimize.nonnull.FieldAccessTest;
 import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterArrayAccess;
 import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterFieldAccess;
 import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterInvoke;
-import com.android.tools.r8.ir.optimize.NonNullTracker;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableMap;
 import java.util.Map;
 import java.util.function.BiConsumer;
@@ -56,9 +57,10 @@
         new ApplicationReader(app, TEST_OPTIONS, new Timing("NullabilityTest.appReader"))
             .read().toDirect();
     AppInfo appInfo = new AppInfo(dexApplication);
-    DexInspector dexInspector = new DexInspector(appInfo.app);
-    DexEncodedMethod foo = dexInspector.clazz(mainClass.getName()).method(signature).getMethod();
-    IRCode irCode = foo.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    CodeInspector codeInspector = new CodeInspector(appInfo.app);
+    DexEncodedMethod foo = codeInspector.clazz(mainClass.getName()).method(signature).getMethod();
+    IRCode irCode =
+        foo.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
     NonNullTracker nonNullTracker = new NonNullTracker();
     nonNullTracker.addNonNull(irCode);
     TypeAnalysis analysis = new TypeAnalysis(appInfo, foo, irCode);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
index aefe665..9c1214c 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.ArrayLength;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.ConstString;
@@ -30,10 +31,10 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Smali;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import java.nio.charset.StandardCharsets;
@@ -116,13 +117,14 @@
 
   // Simple one path with a lot of arithmetic operations.
   private static void arithmetic(AppInfo appInfo) {
-    DexInspector inspector = new DexInspector(appInfo.app);
+    CodeInspector inspector = new CodeInspector(appInfo.app);
     DexEncodedMethod subtract =
         inspector.clazz("Test")
             .method(
                 new MethodSignature("subtractConstants8bitRegisters", "int", ImmutableList.of()))
             .getMethod();
-    IRCode irCode = subtract.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    IRCode irCode =
+        subtract.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
     TypeAnalysis analysis = new TypeAnalysis(appInfo, subtract, irCode);
     analysis.forEach((v, l) -> {
       assertEither(l, PRIMITIVE, NULL, TOP);
@@ -131,12 +133,13 @@
 
   // A couple branches, along with some recursive calls.
   private static void fibonacci(AppInfo appInfo) {
-    DexInspector inspector = new DexInspector(appInfo.app);
+    CodeInspector inspector = new CodeInspector(appInfo.app);
     DexEncodedMethod fib =
         inspector.clazz("Test")
             .method(new MethodSignature("fibonacci", "int", ImmutableList.of("int")))
             .getMethod();
-    IRCode irCode = fib.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    IRCode irCode =
+        fib.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
     TypeAnalysis analysis = new TypeAnalysis(appInfo, fib, irCode);
     analysis.forEach((v, l) -> {
       assertEither(l, PRIMITIVE, NULL);
@@ -145,12 +148,13 @@
 
   // fill-array-data
   private static void fillArrayData(AppInfo appInfo) {
-    DexInspector inspector = new DexInspector(appInfo.app);
+    CodeInspector inspector = new CodeInspector(appInfo.app);
     DexEncodedMethod test1 =
         inspector.clazz("Test")
             .method(new MethodSignature("test1", "int[]", ImmutableList.of()))
             .getMethod();
-    IRCode irCode = test1.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    IRCode irCode =
+        test1.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
     TypeAnalysis analysis = new TypeAnalysis(appInfo, test1, irCode);
     Value array = null;
     InstructionIterator iterator = irCode.instructionIterator();
@@ -176,12 +180,13 @@
 
   // filled-new-array
   private static void filledNewArray(AppInfo appInfo) {
-    DexInspector inspector = new DexInspector(appInfo.app);
+    CodeInspector inspector = new CodeInspector(appInfo.app);
     DexEncodedMethod test4 =
         inspector.clazz("Test")
             .method(new MethodSignature("test4", "int[]", ImmutableList.of()))
             .getMethod();
-    IRCode irCode = test4.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    IRCode irCode =
+        test4.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
     TypeAnalysis analysis = new TypeAnalysis(appInfo, test4, irCode);
     Value array = null;
     InstructionIterator iterator = irCode.instructionIterator();
@@ -207,12 +212,13 @@
 
   // Make sure the analysis does not hang.
   private static void infiniteLoop(AppInfo appInfo) {
-    DexInspector inspector = new DexInspector(appInfo.app);
+    CodeInspector inspector = new CodeInspector(appInfo.app);
     DexEncodedMethod loop2 =
         inspector.clazz("Test")
             .method(new MethodSignature("loop2", "void", ImmutableList.of()))
             .getMethod();
-    IRCode irCode = loop2.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    IRCode irCode =
+        loop2.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
     TypeAnalysis analysis = new TypeAnalysis(appInfo, loop2, irCode);
     analysis.forEach((v, l) -> {
       if (l.isClassTypeLatticeElement()) {
@@ -226,12 +232,13 @@
 
   // move-exception
   private static void tryCatch(AppInfo appInfo) {
-    DexInspector inspector = new DexInspector(appInfo.app);
+    CodeInspector inspector = new CodeInspector(appInfo.app);
     DexEncodedMethod test2 =
         inspector.clazz("Test")
             .method(new MethodSignature("test2_throw", "int", ImmutableList.of()))
             .getMethod();
-    IRCode irCode = test2.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    IRCode irCode =
+        test2.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
     TypeAnalysis analysis = new TypeAnalysis(appInfo, test2, irCode);
     analysis.forEach((v, l) -> {
       if (l.isClassTypeLatticeElement()) {
@@ -244,7 +251,7 @@
 
   // One very complicated example.
   private static void typeConfusion(AppInfo appInfo) {
-    DexInspector inspector = new DexInspector(appInfo.app);
+    CodeInspector inspector = new CodeInspector(appInfo.app);
     DexEncodedMethod method =
         inspector.clazz("TestObject")
             .method(
@@ -257,14 +264,15 @@
         ConstString.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, false),
         CheckCast.class, new ClassTypeLatticeElement(test, true),
         NewInstance.class, new ClassTypeLatticeElement(test, false));
-    IRCode irCode = method.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    IRCode irCode =
+        method.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
     TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
     analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
   }
 
   // One more complicated example.
   private static void typeConfusion5(AppInfo appInfo) {
-    DexInspector inspector = new DexInspector(appInfo.app);
+    CodeInspector inspector = new CodeInspector(appInfo.app);
     DexEncodedMethod method =
         inspector.clazz("TestObject")
             .method(
@@ -275,7 +283,8 @@
       ConstString.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, false),
       InstanceOf.class, PRIMITIVE,
       StaticGet.class, new ClassTypeLatticeElement(test, true));
-    IRCode irCode = method.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    IRCode irCode =
+        method.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
     TypeAnalysis analysis = new TypeAnalysis(appInfo, method, irCode);
     analysis.forEach((v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
   }
diff --git a/src/test/java/com/android/tools/r8/ir/callgraph/CycleEliminationTest.java b/src/test/java/com/android/tools/r8/ir/callgraph/CycleEliminationTest.java
new file mode 100644
index 0000000..56699e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/callgraph/CycleEliminationTest.java
@@ -0,0 +1,214 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.callgraph;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.conversion.CallGraph.CycleEliminator;
+import com.android.tools.r8.ir.conversion.CallGraph.Node;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.BooleanSupplier;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class CycleEliminationTest extends TestBase {
+
+  private static class Configuration {
+
+    final Collection<Node> nodes;
+    final Set<Node> forceInline;
+    final BooleanSupplier test;
+
+    Configuration(Collection<Node> nodes, Set<Node> forceInline, BooleanSupplier test) {
+      this.nodes = nodes;
+      this.forceInline = forceInline;
+      this.test = test;
+    }
+  }
+
+  private DexItemFactory dexItemFactory = new DexItemFactory();
+
+  @Rule public final ExpectedException exception = ExpectedException.none();
+
+  @Test
+  public void testSimpleCycle() {
+    Node method = createNode("n1");
+    Node forceInlinedMethod = createForceInlinedNode("n2");
+
+    Iterable<Collection<Node>> orderings =
+        ImmutableList.of(
+            ImmutableList.of(method, forceInlinedMethod),
+            ImmutableList.of(forceInlinedMethod, method));
+
+    for (Collection<Node> nodes : orderings) {
+      // Create a cycle between the two nodes.
+      method.addCallee(forceInlinedMethod);
+      forceInlinedMethod.addCallee(method);
+
+      // Check that the cycle eliminator finds the cycle.
+      CycleEliminator cycleEliminator = new CycleEliminator(nodes, new InternalOptions());
+      assertEquals(1, cycleEliminator.breakCycles());
+
+      // The edge from method to forceInlinedMethod should be removed to ensure that force inlining
+      // will work.
+      assertTrue(forceInlinedMethod.isLeaf());
+
+      // Check that the cycle eliminator agrees that there are no more cycles left.
+      assertEquals(0, cycleEliminator.breakCycles());
+    }
+  }
+
+  @Test
+  public void testSimpleCycleWithCyclicForceInlining() {
+    Node method = createForceInlinedNode("n1");
+    Node forceInlinedMethod = createForceInlinedNode("n2");
+
+    // Create a cycle between the two nodes.
+    method.addCallee(forceInlinedMethod);
+    forceInlinedMethod.addCallee(method);
+
+    CycleEliminator cycleEliminator =
+        new CycleEliminator(ImmutableList.of(method, forceInlinedMethod), new InternalOptions());
+
+    exception.expect(CompilationError.class);
+    exception.expectMessage(CycleEliminator.CYCLIC_FORCE_INLINING_MESSAGE);
+
+    // Should throw because force inlining will fail.
+    cycleEliminator.breakCycles();
+  }
+
+  @Test
+  public void testGraphWithNestedCycles() {
+    Node n1 = createNode("n1");
+    Node n2 = createNode("n2");
+    Node n3 = createNode("n3");
+
+    BooleanSupplier canInlineN1 =
+        () -> {
+          // The node n1 should be force inlined into n2 and n3, so these edges must be kept.
+          assertTrue(n2.hasCallee(n1));
+          assertTrue(n3.hasCallee(n1));
+          // Furthermore, the edge from n1 to n2 must be removed.
+          assertFalse(n1.hasCallee(n2));
+          return true;
+        };
+
+    BooleanSupplier canInlineN3 =
+        () -> {
+          // The node n3 should be force inlined into n2, so this edge must be kept.
+          assertTrue(n2.hasCallee(n3));
+          // Furthermore, one of the edges n1 -> n2 and n3 -> n1 must be removed.
+          assertFalse(n1.hasCallee(n2) && n3.hasCallee(n1));
+          return true;
+        };
+
+    BooleanSupplier canInlineN1N3 =
+        () -> {
+          // The edge n1 -> n2 must be removed.
+          assertFalse(n1.hasCallee(n2));
+          // Check that both can be force inlined.
+          return canInlineN1.getAsBoolean() && canInlineN3.getAsBoolean();
+        };
+
+    List<Collection<Node>> orderings =
+        ImmutableList.of(
+            ImmutableList.of(n1, n2, n3),
+            ImmutableList.of(n1, n3, n2),
+            ImmutableList.of(n2, n1, n3),
+            ImmutableList.of(n2, n3, n1),
+            ImmutableList.of(n3, n1, n2),
+            ImmutableList.of(n3, n2, n1));
+
+    List<Configuration> configurations = new ArrayList<>();
+    // All orderings, where no methods are marked as force inline.
+    orderings
+        .stream()
+        .map(ordering -> new Configuration(ordering, ImmutableSet.of(), null))
+        .forEach(configurations::add);
+    // All orderings, where n1 is marked as force inline
+    // (the configuration where n2 is marked as force inline is symmetric).
+    orderings
+        .stream()
+        .map(ordering -> new Configuration(ordering, ImmutableSet.of(n1), canInlineN1))
+        .forEach(configurations::add);
+    // All orderings, where n3 is marked as force inline.
+    orderings
+        .stream()
+        .map(ordering -> new Configuration(ordering, ImmutableSet.of(n3), canInlineN3))
+        .forEach(configurations::add);
+    // All orderings, where n1 and n3 are marked as force inline.
+    orderings
+        .stream()
+        .map(ordering -> new Configuration(ordering, ImmutableSet.of(n1, n3), canInlineN1N3))
+        .forEach(configurations::add);
+
+    for (Configuration configuration : configurations) {
+      // Create a cycle between the three nodes.
+      n1.addCallee(n2);
+      n2.addCallee(n3);
+      n3.addCallee(n1);
+
+      // Create a cycle in the graph between node n1 and n2.
+      n2.addCallee(n1);
+
+      for (Node node : configuration.nodes) {
+        if (configuration.forceInline.contains(node)) {
+          node.method.markForceInline();
+        } else {
+          node.method.unsetForceInline();
+        }
+      }
+
+      // Check that the cycle eliminator finds the cycles.
+      CycleEliminator cycleEliminator =
+          new CycleEliminator(configuration.nodes, new InternalOptions());
+      int numberOfCycles = cycleEliminator.breakCycles();
+      if (numberOfCycles == 1) {
+        // If only one cycle was removed, then it must be the edge from n1 -> n2 that was removed.
+        assertTrue(n1.isLeaf());
+      } else {
+        // Check that the cycle eliminator found both cycles.
+        assertEquals(2, numberOfCycles);
+      }
+
+      // Check that the cycle eliminator agrees that there are no more cycles left.
+      assertEquals(0, cycleEliminator.breakCycles());
+
+      // Check that force inlining is guaranteed to succeed.
+      if (configuration.test != null) {
+        assertTrue(configuration.test.getAsBoolean());
+      }
+    }
+  }
+
+  private Node createNode(String methodName) {
+    DexMethod signature =
+        dexItemFactory.createMethod(
+            dexItemFactory.objectType,
+            dexItemFactory.createProto(dexItemFactory.voidType),
+            methodName);
+    return new Node(new DexEncodedMethod(signature, null, null, null, null));
+  }
+
+  private Node createForceInlinedNode(String methodName) {
+    Node node = createNode(methodName);
+    node.method.markForceInline();
+    return node;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
index 1f4d2ba..f1f2842 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
@@ -4,14 +4,16 @@
 
 package com.android.tools.r8.ir.desugar.annotations;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.AsmTestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.util.Collections;
 import org.junit.Assert;
 import org.junit.Test;
@@ -153,47 +155,47 @@
   }
 
   private void checkPresenceOfSyntheticMethods(AndroidApp output) throws Exception {
-    DexInspector inspector = new DexInspector(output);
+    CodeInspector inspector = new CodeInspector(output);
 
     // Get classes A, B, and C.
-    DexInspector.ClassSubject clazzA = inspector.clazz(A.class.getCanonicalName());
+    ClassSubject clazzA = inspector.clazz(A.class.getCanonicalName());
     assertThat(clazzA, isPresent());
 
-    DexInspector.ClassSubject clazzB = inspector.clazz(B.class.getCanonicalName());
+    ClassSubject clazzB = inspector.clazz(B.class.getCanonicalName());
     assertThat(clazzB, isPresent());
 
-    DexInspector.ClassSubject clazzC = inspector.clazz(C.class.getCanonicalName());
+    ClassSubject clazzC = inspector.clazz(C.class.getCanonicalName());
     assertThat(clazzC, isPresent());
 
     // Check that the original methods are there, and that they are not synthetic.
-    DexInspector.MethodSubject methodA =
+    MethodSubject methodA =
         clazzB.method(A.class.getCanonicalName(), "method", Collections.emptyList());
     assertThat(methodA, isPresent());
     Assert.assertTrue(!methodA.getMethod().isSyntheticMethod());
 
-    DexInspector.MethodSubject methodB =
+    MethodSubject methodB =
         clazzB.method(A.class.getCanonicalName(), "method", Collections.emptyList());
     assertThat(methodB, isPresent());
     Assert.assertTrue(!methodB.getMethod().isSyntheticMethod());
 
-    DexInspector.MethodSubject methodC =
+    MethodSubject methodC =
         clazzC.method(A.class.getCanonicalName(), "method", Collections.emptyList());
     assertThat(methodC, isPresent());
     Assert.assertTrue(!methodC.getMethod().isSyntheticMethod());
 
     // Check that a synthetic method has been added to class B.
-    DexInspector.MethodSubject methodB2 =
+    MethodSubject methodB2 =
         clazzB.method(B.class.getCanonicalName(), "method", Collections.emptyList());
     assertThat(methodB2, isPresent());
     Assert.assertTrue(methodB2.getMethod().isSyntheticMethod());
 
     // Check that two synthetic methods have been added to class C.
-    DexInspector.MethodSubject methodC2 =
+    MethodSubject methodC2 =
         clazzC.method(B.class.getCanonicalName(), "method", Collections.emptyList());
     assertThat(methodC2, isPresent());
     Assert.assertTrue(methodC2.getMethod().isSyntheticMethod());
 
-    DexInspector.MethodSubject methodC3 =
+    MethodSubject methodC3 =
         clazzC.method(C.class.getCanonicalName(), "method", Collections.emptyList());
     assertThat(methodC3, isPresent());
     Assert.assertTrue(methodC3.getMethod().isSyntheticMethod());
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.java
index bc06250..30cf603 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.java
@@ -4,7 +4,9 @@
 
 package com.android.tools.r8.ir.desugar.annotations.version2;
 
-import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.*;
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.CRTS_SIMPLE_NAME;
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.CRT_NAME;
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.PACKAGE_NAME;
 
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.ClassWriter;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/B87341268.java b/src/test/java/com/android/tools/r8/ir/optimize/B87341268.java
index 3f7bcfb..ac3952d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/B87341268.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/B87341268.java
@@ -3,20 +3,20 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 
 public class B87341268 extends TestBase {
   @Test
   public void test() throws Exception {
     AndroidApp app = compileWithD8(readClasses(TestClass.class));
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     ClassSubject clazz = inspector.clazz(TestClass.class);
     assertThat(clazz, isPresent());
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
new file mode 100644
index 0000000..33994b1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstraintWithTargetTest.java
@@ -0,0 +1,144 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class ConstraintWithTargetTest {
+  private static DexItemFactory factory;
+  private static AppInfoWithSubtyping appInfo;
+
+  @BeforeClass
+  public static void makeAppInfo() throws Exception {
+    InternalOptions options = new InternalOptions();
+    DexApplication application =
+        new ApplicationReader(
+                AndroidApp.builder()
+                    .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+                    .build(),
+                options,
+                new Timing(ConstraintWithTargetTest.class.getName()))
+            .read()
+            .toDirect();
+    factory = options.itemFactory;
+    appInfo = new AppInfoWithSubtyping(application);
+  }
+
+  private ConstraintWithTarget never() {
+    return ConstraintWithTarget.NEVER;
+  }
+
+  private ConstraintWithTarget always() {
+    return ConstraintWithTarget.ALWAYS;
+  }
+
+  private ConstraintWithTarget element(Constraint constraint, DexType type) {
+    return new ConstraintWithTarget(constraint, type);
+  }
+
+  private ConstraintWithTarget meet(ConstraintWithTarget e1, ConstraintWithTarget e2) {
+    return ConstraintWithTarget.meet(e1, e2, appInfo);
+  }
+
+  @Test
+  public void meetNeverIsNever() {
+    assertEquals(never(),
+        meet(never(), always()));
+    assertEquals(never(),
+        meet(always(), never()));
+    assertEquals(never(),
+        meet(never(), element(Constraint.SAMECLASS, factory.objectType)));
+  }
+
+  @Test
+  public void meetAlwaysIsUnit() {
+    ConstraintWithTarget o = element(Constraint.SUBCLASS, factory.objectType);
+    assertEquals(o,
+        meet(always(), o));
+    assertEquals(o,
+        meet(o, always()));
+  }
+
+  @Test
+  public void withSameTarget() {
+    DexType s = factory.createType("Ljava/lang/String;");
+    ConstraintWithTarget c0 = element(Constraint.SAMECLASS, s);
+    ConstraintWithTarget c1 = element(Constraint.PACKAGE, s);
+    ConstraintWithTarget c2 = element(Constraint.SUBCLASS, s);
+    assertEquals(c0,
+        meet(c1, c0));
+    assertEquals(c0,
+        meet(c0, c2));
+    assertEquals(c1,
+        meet(c2, c1));
+  }
+
+  @Test
+  public void withDifferentTarget() {
+    DexType s = factory.createType("Ljava/lang/String;");
+    DexType b = factory.createType("Ljava/lang/StringBuilder;");
+    ConstraintWithTarget c1 = element(Constraint.SAMECLASS, s);
+    ConstraintWithTarget c2 = element(Constraint.SAMECLASS, b);
+    assertEquals(never(),
+        meet(c1, c2));
+
+    ConstraintWithTarget c0 = element(Constraint.PACKAGE, factory.objectType);
+    assertEquals(c1,
+        meet(c0, c1));
+    assertEquals(c2,
+        meet(c0, c2));
+
+    c0 = element(Constraint.SUBCLASS, factory.objectType);
+    assertEquals(c1,
+        meet(c0, c1));
+    assertEquals(c2,
+        meet(c0, c2));
+
+    c1 = element(Constraint.PACKAGE, s);
+    c2 = element(Constraint.PACKAGE, b);
+    assertEquals(c1,
+        meet(c0, c1));
+    assertEquals(c2,
+        meet(c0, c2));
+    assertEquals(c1,
+        meet(c1, c2));
+    assertEquals(c2,
+        meet(c2, c1));
+
+    DexType t = factory.createType("Ljava/lang/reflect/Type;");
+    DexType c = factory.createType("Ljava/lang/Class;");
+    c1 = element(Constraint.SUBCLASS, t);
+    c2 = element(Constraint.SUBCLASS, c);
+    assertEquals(c2,
+        meet(c1, c2));
+    assertEquals(c2,
+        meet(c2, c1));
+  }
+
+
+  @Test
+  public void b111080693() {
+    ConstraintWithTarget c1 =
+        element(Constraint.SUBCLASS, factory.createType("Ljava/lang/Class;"));
+    ConstraintWithTarget c2 =
+        element(Constraint.PACKAGE, factory.createType("Ljava/lang/reflect/Type;"));
+    assertEquals(never(),
+        meet(c1, c2));
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
index a5c7093..0eac6bb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
@@ -18,9 +18,9 @@
 import com.android.tools.r8.code.SputObject;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -43,7 +43,7 @@
   @Test
   public void testWriteOnlyField_putObject_gone() throws Exception {
     Path processedApp = runR8(EXAMPLE_KEEP);
-    DexInspector inspector = new DexInspector(processedApp);
+    CodeInspector inspector = new CodeInspector(processedApp);
     ClassSubject clazz = inspector.clazz(WRITE_ONLY_FIELD + ".WriteOnlyCls");
     clazz.forAllMethods(
         methodSubject -> {
@@ -71,7 +71,7 @@
   @Test
   public void testWriteOnlyField_dontoptimize() throws Exception {
     Path processedApp = runR8(DONT_OPTIMIZE);
-    DexInspector inspector = new DexInspector(processedApp);
+    CodeInspector inspector = new CodeInspector(processedApp);
     ClassSubject clazz = inspector.clazz(WRITE_ONLY_FIELD + ".WriteOnlyCls");
     clazz.forAllMethods(
         methodSubject -> {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index 7c90976..b1647cc 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
@@ -26,9 +27,9 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.function.Consumer;
 import org.junit.Test;
 
@@ -46,9 +47,10 @@
         new ApplicationReader(app, TEST_OPTIONS, new Timing("NonNullMarkerTest.appReader"))
             .read().toDirect();
     AppInfo appInfo = new AppInfo(dexApplication);
-    DexInspector dexInspector = new DexInspector(appInfo.app);
-    DexEncodedMethod foo = dexInspector.clazz(testClass.getName()).method(signature).getMethod();
-    IRCode irCode = foo.buildIR(appInfo, TEST_OPTIONS, Origin.unknown());
+    CodeInspector codeInspector = new CodeInspector(appInfo.app);
+    DexEncodedMethod foo = codeInspector.clazz(testClass.getName()).method(signature).getMethod();
+    IRCode irCode =
+        foo.buildIR(appInfo, GraphLense.getIdentityLense(), TEST_OPTIONS, Origin.unknown());
     checkCountOfNonNull(irCode, 0);
 
     NonNullTracker nonNullTracker = new NonNullTracker();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 5dc7f23..dd7756b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -27,11 +27,12 @@
 import com.android.tools.r8.code.Throw;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -55,17 +56,26 @@
   private static final String DEFAULT_DEX_FILENAME = "classes.dex";
   private static final String DEFAULT_MAP_FILENAME = "proguard.map";
 
-  @Parameters(name = "{0}")
+  @Parameters(name = "{0}, minification={1}, allowaccessmodification={2}")
   public static Collection<Object[]> data() {
-    return Arrays.asList(new Object[][]{{"Inlining"}});
+    return Arrays.asList(new Object[][]{
+        {"Inlining", false, false},
+        {"Inlining", false, true},
+        {"Inlining", true, false},
+        {"Inlining", true, true}
+    });
   }
 
   private final String name;
   private final String keepRulesFile;
+  private final boolean minification;
+  private final boolean allowAccessModification;
 
-  public R8InliningTest(String name) {
+  public R8InliningTest(String name, boolean minification, boolean allowAccessModification) {
     this.name = name.toLowerCase();
     this.keepRulesFile = ToolHelper.EXAMPLES_DIR + this.name + "/keep-rules.txt";
+    this.minification = minification;
+    this.allowAccessModification = allowAccessModification;
   }
 
   private Path getInputFile() {
@@ -81,7 +91,7 @@
   }
 
   private String getGeneratedProguardMap() throws IOException {
-    Path mapFile = Paths.get(temp.getRoot().getCanonicalPath(), DEFAULT_MAP_FILENAME);
+    Path mapFile = temp.getRoot().toPath().resolve(DEFAULT_MAP_FILENAME);
     if (Files.exists(mapFile)) {
       return mapFile.toAbsolutePath().toString();
     }
@@ -94,19 +104,23 @@
   @Before
   public void generateR8Version() throws Exception {
     Path out = temp.getRoot().toPath();
-    R8Command command =
+    Path mapFile = out.resolve(DEFAULT_MAP_FILENAME);
+    R8Command.Builder commandBuilder =
         R8Command.builder()
             .addProgramFiles(getInputFile())
             .setMinApiLevel(AndroidApiLevel.M.getLevel())
             .setOutput(out, OutputMode.DexIndexed)
-            .addProguardConfigurationFiles(Paths.get(keepRulesFile))
-            .build();
-    // TODO(62048823): Enable minification.
-    ToolHelper.runR8(command, o -> {
-      o.enableMinification = false;
+            .setProguardMapOutputPath(mapFile)
+            .addProguardConfigurationFiles(Paths.get(keepRulesFile));
+    if (allowAccessModification) {
+      commandBuilder.addProguardConfiguration(
+          ImmutableList.of("-allowaccessmodification"), Origin.unknown());
+    }
+    ToolHelper.runR8(commandBuilder.build(), o -> {
+      o.enableMinification = minification;
     });
-    String artOutput = ToolHelper.runArtNoVerificationErrors(out + "/classes.dex",
-        "inlining.Inlining");
+    String artOutput =
+        ToolHelper.runArtNoVerificationErrors(out + "/classes.dex", "inlining.Inlining");
 
     // Compare result with Java to make sure we have the same behavior.
     ProcessResult javaResult = ToolHelper.runJava(getInputFile(), "inlining.Inlining");
@@ -131,14 +145,14 @@
 
   private void dump(Path path, String title) throws Throwable {
     System.out.println(title + ":");
-    DexInspector inspector = new DexInspector(path.toAbsolutePath());
+    CodeInspector inspector = new CodeInspector(path.toAbsolutePath());
     inspector.clazz("inlining.Inlining").forAllMethods(m -> dump(m.getMethod()));
     System.out.println(title + " size: " + Files.size(path));
   }
 
   @Test
   public void checkNoInvokes() throws Throwable {
-    DexInspector inspector = new DexInspector(getGeneratedDexFile().toAbsolutePath(),
+    CodeInspector inspector = new CodeInspector(getGeneratedDexFile().toAbsolutePath(),
         getGeneratedProguardMap());
     ClassSubject clazz = inspector.clazz("inlining.Inlining");
 
@@ -175,21 +189,25 @@
 
   @Test
   public void invokeOnNullableReceiver() throws Exception {
-    DexInspector inspector =
-        new DexInspector(getGeneratedDexFile().toAbsolutePath(), getGeneratedProguardMap());
+    CodeInspector inspector =
+        new CodeInspector(getGeneratedDexFile().toAbsolutePath(), getGeneratedProguardMap());
     ClassSubject clazz = inspector.clazz("inlining.Nullability");
     MethodSubject m = clazz.method("int", "inlinable", ImmutableList.of("inlining.A"));
-    assertTrue(m.isPresent());
-    DexCode code = m.getMethod().getCode().asDexCode();
-    checkInstructions(
-        code,
-        ImmutableList.of(
-            Iget.class,
-            // TODO(b/70572176): below two could be replaced with Iget via inlining
-            InvokeVirtual.class,
-            MoveResult.class,
-            AddInt2Addr.class,
-            Return.class));
+    DexCode code;
+    if (allowAccessModification) {
+      assertFalse(m.isPresent());
+    } else {
+      assertTrue(m.isPresent());
+      code = m.getMethod().getCode().asDexCode();
+      checkInstructions(
+          code,
+          ImmutableList.of(
+              Iget.class,
+              InvokeVirtual.class,
+              MoveResult.class,
+              AddInt2Addr.class,
+              Return.class));
+    }
 
     m = clazz.method("int", "notInlinable", ImmutableList.of("inlining.A"));
     assertTrue(m.isPresent());
@@ -239,8 +257,8 @@
 
   @Test
   public void invokeOnNonNullReceiver() throws Exception {
-    DexInspector inspector =
-        new DexInspector(getGeneratedDexFile().toAbsolutePath(), getGeneratedProguardMap());
+    CodeInspector inspector =
+        new CodeInspector(getGeneratedDexFile().toAbsolutePath(), getGeneratedProguardMap());
     ClassSubject clazz = inspector.clazz("inlining.Nullability");
     MethodSubject m = clazz.method("int", "conditionalOperator", ImmutableList.of("inlining.A"));
     assertTrue(m.isPresent());
@@ -249,10 +267,7 @@
         code,
         ImmutableList.of(
             IfEqz.class,
-            // TODO(b/70794661): below two could be replaced with Iget via inlining if access
-            // modification is allowed.
-            InvokeVirtual.class,
-            MoveResult.class,
+            Iget.class,
             Goto.class,
             Const4.class,
             Return.class));
@@ -273,9 +288,7 @@
     builder.add(Const4.class);
     builder.add(IfEqz.class);
     builder.add(IfEqz.class);
-    // TODO(b/70794661): below two could be replaced with Iget via inlining
-    builder.add(InvokeVirtual.class);
-    builder.add(MoveResult.class);
+    builder.add(Iget.class);
     builder.add(MulInt2Addr.class);
     builder.add(Return.class);
     builder.add(PackedSwitchPayload.class);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
index 525e21b..ad0ae87 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterInvoke;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
 import java.util.List;
@@ -32,10 +32,10 @@
     AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(testClass));
     AndroidApp r8Result = compileWithR8(app,
         "-keep class " + testClass.getCanonicalName() + " { *; }");
-    DexInspector dexInspector = new DexInspector(r8Result);
+    CodeInspector codeInspector = new CodeInspector(r8Result);
     for (MethodSignature signature : signatures) {
       DexEncodedMethod method =
-          dexInspector.clazz(testClass.getName()).method(signature).getMethod();
+          codeInspector.clazz(testClass.getName()).method(signature).getMethod();
       long count = Arrays.stream(method.getCode().asDexCode().instructions)
           .filter(SimplifyIfNotNullTest::isIf).count();
       assertEquals(0, count);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
index 3239191..793da3c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerTest.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.ir.optimize.classinliner.code.C;
 import com.android.tools.r8.ir.optimize.classinliner.code.CodeTestClass;
 import com.android.tools.r8.ir.optimize.classinliner.invalidroot.InvalidRootsTestClass;
+import com.android.tools.r8.ir.optimize.classinliner.lambdas.LambdasTestClass;
 import com.android.tools.r8.ir.optimize.classinliner.trivial.ClassWithFinal;
 import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceAB;
 import com.android.tools.r8.ir.optimize.classinliner.trivial.CycleReferenceBA;
@@ -44,9 +45,9 @@
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.Sets;
 import java.nio.file.Path;
 import java.util.Collections;
@@ -80,7 +81,7 @@
     String artOutput = runOnArt(app, TrivialTestClass.class);
     assertEquals(javaOutput, artOutput);
 
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     ClassSubject clazz = inspector.clazz(TrivialTestClass.class);
 
     assertEquals(
@@ -152,7 +153,7 @@
     String artOutput = runOnArt(app, BuildersTestClass.class);
     assertEquals(javaOutput, artOutput);
 
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     ClassSubject clazz = inspector.clazz(BuildersTestClass.class);
 
     assertEquals(
@@ -237,7 +238,7 @@
     String artOutput = runOnArt(app, CodeTestClass.class);
     assertEquals(javaOutput, artOutput);
 
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     ClassSubject clazz = inspector.clazz(C.class);
 
     assertEquals(
@@ -273,7 +274,7 @@
     String artOutput = runOnArt(app, InvalidRootsTestClass.class);
     assertEquals(javaOutput, artOutput);
 
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     ClassSubject clazz = inspector.clazz(InvalidRootsTestClass.class);
 
     assertEquals(
@@ -301,6 +302,37 @@
     assertFalse(inspector.clazz(InvalidRootsTestClass.B.class).isPresent());
   }
 
+  @Test
+  public void testDesugaredLambdas() throws Exception {
+    byte[][] classes = {
+        ToolHelper.getClassAsBytes(LambdasTestClass.class),
+        ToolHelper.getClassAsBytes(LambdasTestClass.Iface.class),
+        ToolHelper.getClassAsBytes(LambdasTestClass.IfaceUtil.class),
+    };
+    AndroidApp app = runR8(buildAndroidApp(classes), LambdasTestClass.class);
+
+    String javaOutput = runOnJava(LambdasTestClass.class);
+    String artOutput = runOnArt(app, LambdasTestClass.class);
+    assertEquals(javaOutput, artOutput);
+
+    CodeInspector inspector = new CodeInspector(app);
+    ClassSubject clazz = inspector.clazz(LambdasTestClass.class);
+
+    assertEquals(
+        Sets.newHashSet(
+            "java.lang.StringBuilder"),
+        collectTypes(clazz, "testStatelessLambda", "void"));
+
+    assertEquals(
+        Sets.newHashSet(
+            "java.lang.StringBuilder"),
+        collectTypes(clazz, "testStatefulLambda", "void", "java.lang.String", "java.lang.String"));
+
+    assertEquals(0,
+        inspector.allClasses().stream()
+            .filter(ClassSubject::isSynthesizedJavaLambdaClass).count());
+  }
+
   private Set<String> collectTypes(
       ClassSubject clazz, String methodName, String retValue, String... params) {
     return Stream.concat(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
new file mode 100644
index 0000000..130f895
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/lambdas/LambdasTestClass.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner.lambdas;
+
+public class LambdasTestClass {
+  private static int ID = 0;
+
+  private static int nextInt() {
+    return ID++;
+  }
+
+  private static String next() {
+    return Integer.toString(nextInt());
+  }
+
+  public interface Iface {
+    String foo();
+  }
+
+  public static class IfaceUtil {
+    public static void act(Iface iface) {
+      System.out.println("" + next() + "> " + iface.foo());
+    }
+  }
+
+  public static void main(String[] args) {
+    LambdasTestClass test = new LambdasTestClass();
+    test.testStatelessLambda();
+    test.testStatefulLambda(next(), next());
+  }
+
+  public static String exact() {
+    return next();
+  }
+
+  public static String almost(String... s) {
+    return next();
+  }
+
+  private synchronized void testStatelessLambda() {
+    IfaceUtil.act(() -> next());
+    IfaceUtil.act(LambdasTestClass::next);
+    IfaceUtil.act(LambdasTestClass::exact);
+    IfaceUtil.act(LambdasTestClass::almost);
+  }
+
+  private synchronized void testStatefulLambda(String a, String b) {
+    IfaceUtil.act(() -> a);
+    IfaceUtil.act(() -> a + b);
+    IfaceUtil.act((a + b)::toLowerCase);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
index 71dae86..c5d6261 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/devirtualize/InvokeInterfaceToInvokeVirtualTest.java
@@ -24,8 +24,8 @@
 import com.android.tools.r8.ir.optimize.devirtualize.invokeinterface.Main;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import org.junit.Test;
@@ -67,9 +67,9 @@
     Path out = temp.getRoot().toPath();
     AndroidApp processedApp = runR8(originalApp, Main.class, out);
 
-    DexInspector dexInspector = new DexInspector(processedApp);
-    ClassSubject clazz = dexInspector.clazz(main);
-    DexEncodedMethod m = clazz.method(DexInspector.MAIN).getMethod();
+    CodeInspector codeInspector = new CodeInspector(processedApp);
+    ClassSubject clazz = codeInspector.clazz(main);
+    DexEncodedMethod m = clazz.method(CodeInspector.MAIN).getMethod();
     DexCode code = m.getCode().asDexCode();
     long numOfInvokeInterface = filterInstructionKind(code, InvokeInterface.class).count();
     // List#add, List#get
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerTest.java
index adb87e6..5cdaac8 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlinerTest.java
@@ -21,10 +21,10 @@
 import com.android.tools.r8.ir.optimize.inliner.interfaces.InterfaceTargetsTestClass.IfaceNoImpl;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.nio.file.Path;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -57,7 +57,7 @@
     String artOutput = runOnArt(app, InterfaceTargetsTestClass.class);
     assertEquals(javaOutput, artOutput);
 
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     ClassSubject clazz = inspector.clazz(InterfaceTargetsTestClass.class);
 
     assertFalse(getMethodSubject(clazz,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/b111893131/B111893131.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b111893131/B111893131.java
new file mode 100644
index 0000000..860a913
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/b111893131/B111893131.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.outliner.b111893131;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+class TestClass {
+  public interface Act {
+    // Need both builder and arg to create code snippets for outline candidates.
+    String get(StringBuilder builder, String arg);
+  }
+
+  public static void main(String[] args) {
+    System.out.println(test(new TestClass("OK").toAct(), new StringBuilder(), "1"));
+  }
+
+  // Need to pass Act and call #get to create private instance lambda$
+  private static String test(Act act, StringBuilder builder, String arg) {
+    // Outline candidate
+    builder.append(arg).append(arg).append(arg);
+    act.get(builder, "#");
+    return builder.toString();
+  }
+
+  private final String foo;
+
+  TestClass(String foo) {
+    this.foo = foo;
+  }
+
+  private Act toAct() {
+    return (builder, arg) -> {
+      // Outline candidate
+      builder.append(arg).append(arg).append(arg);
+      return foo;
+    };
+  }
+}
+
+@RunWith(VmTestRunner.class)
+public class B111893131 extends TestBase {
+
+  @Test
+  public void test() throws Exception {
+    String javaResult = runOnJava(TestClass.class);
+
+    R8Command.Builder builder = R8Command.builder();
+    builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
+    builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.Act.class));
+    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+    builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
+    String config = keepMainProguardConfiguration(TestClass.class);
+    builder.addProguardConfiguration(ImmutableList.of(config), Origin.unknown());
+    AndroidApp app = ToolHelper.runR8(builder.build(), options -> {
+      // To trigger outliner, set # of expected outline candidate as threshold.
+      options.outline.threshold = 2;
+      options.enableInlining = false;
+      options.enableMinification = false;
+    });
+    ProcessResult result = runOnArtRaw(app, TestClass.class);
+    assertEquals(0, result.exitCode);
+    assertEquals(javaResult, result.stdout);
+
+    CodeInspector inspector = new CodeInspector(app);
+    ClassSubject classSubject = inspector.clazz(TestClass.class);
+    assertThat(classSubject, isPresent());
+    DexClass clazz = classSubject.getDexClass();
+    clazz.forEachMethod(encodedMethod -> {
+      Code code = encodedMethod.getCode();
+      assertTrue(code.isDexCode());
+      DexCode dexCode = code.asDexCode();
+      // TODO(b/111893131): all outline candidate should be replaced with a call to outlined code.
+      verifyAbsenceOfStringBuilderAppend(dexCode.instructions);
+    });
+  }
+
+  private void verifyAbsenceOfStringBuilderAppend(Instruction[] instructions) {
+    for (Instruction instr : instructions) {
+      if (instr instanceof InvokeVirtual) {
+        InvokeVirtual invokeVirtual = (InvokeVirtual) instr;
+        DexMethod invokedMethod = invokeVirtual.getMethod();
+        if (invokedMethod.getHolder().getName().endsWith("StringBuilder")) {
+          assertNotEquals("append", invokedMethod.name.toString());
+        }
+      }
+    }
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
new file mode 100644
index 0000000..9be118c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerTest.java
@@ -0,0 +1,272 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.InvokeDirect;
+import com.android.tools.r8.code.InvokeStatic;
+import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.code.SgetObject;
+import com.android.tools.r8.code.SputObject;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateConflictField;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateConflictMethod;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateOk;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateOkSideEffects;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostConflictField;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostConflictMethod;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostOk;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostOkSideEffects;
+import com.android.tools.r8.ir.optimize.staticizer.movetohost.MoveToHostTestClass;
+import com.android.tools.r8.ir.optimize.staticizer.trivial.Simple;
+import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithGetter;
+import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithParams;
+import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithSideEffects;
+import com.android.tools.r8.ir.optimize.staticizer.trivial.TrivialTestClass;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Streams;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class ClassStaticizerTest extends TestBase {
+  @Test
+  public void testTrivial() throws Exception {
+    byte[][] classes = {
+        ToolHelper.getClassAsBytes(TrivialTestClass.class),
+        ToolHelper.getClassAsBytes(Simple.class),
+        ToolHelper.getClassAsBytes(SimpleWithSideEffects.class),
+        ToolHelper.getClassAsBytes(SimpleWithParams.class),
+        ToolHelper.getClassAsBytes(SimpleWithGetter.class),
+    };
+    AndroidApp app = runR8(buildAndroidApp(classes), TrivialTestClass.class);
+
+    String javaOutput = runOnJava(TrivialTestClass.class);
+    String artOutput = runOnArt(app, TrivialTestClass.class);
+    assertEquals(javaOutput, artOutput);
+
+    CodeInspector inspector = new CodeInspector(app);
+    ClassSubject clazz = inspector.clazz(TrivialTestClass.class);
+
+    assertEquals(
+        Lists.newArrayList(
+            "STATIC: String trivial.Simple.bar(String)",
+            "STATIC: String trivial.Simple.foo()",
+            "STATIC: String trivial.TrivialTestClass.next()"),
+        references(clazz, "testSimple", "void"));
+
+    assertTrue(instanceMethods(inspector.clazz(Simple.class)).isEmpty());
+
+    assertEquals(
+        Lists.newArrayList(
+            "STATIC: String trivial.SimpleWithParams.bar(String)",
+            "STATIC: String trivial.SimpleWithParams.foo()",
+            "STATIC: String trivial.TrivialTestClass.next()"),
+        references(clazz, "testSimpleWithParams", "void"));
+
+    assertTrue(instanceMethods(inspector.clazz(SimpleWithParams.class)).isEmpty());
+
+    assertEquals(
+        Lists.newArrayList(
+            "STATIC: String trivial.SimpleWithSideEffects.bar(String)",
+            "STATIC: String trivial.SimpleWithSideEffects.foo()",
+            "STATIC: String trivial.TrivialTestClass.next()",
+            "trivial.SimpleWithSideEffects trivial.SimpleWithSideEffects.INSTANCE",
+            "trivial.SimpleWithSideEffects trivial.SimpleWithSideEffects.INSTANCE"),
+        references(clazz, "testSimpleWithSideEffects", "void"));
+
+    assertTrue(instanceMethods(inspector.clazz(SimpleWithSideEffects.class)).isEmpty());
+
+    // TODO(b/111832046): add support for singleton instance getters.
+    assertEquals(
+        Lists.newArrayList(
+            "STATIC: String trivial.TrivialTestClass.next()",
+            "VIRTUAL: String trivial.SimpleWithGetter.bar(String)",
+            "VIRTUAL: String trivial.SimpleWithGetter.foo()",
+            "trivial.SimpleWithGetter trivial.SimpleWithGetter.INSTANCE",
+            "trivial.SimpleWithGetter trivial.SimpleWithGetter.INSTANCE"),
+        references(clazz, "testSimpleWithGetter", "void"));
+
+    assertFalse(instanceMethods(inspector.clazz(SimpleWithGetter.class)).isEmpty());
+  }
+
+  @Test
+  public void testMoveToHost() throws Exception {
+    byte[][] classes = {
+        ToolHelper.getClassAsBytes(MoveToHostTestClass.class),
+        ToolHelper.getClassAsBytes(HostOk.class),
+        ToolHelper.getClassAsBytes(CandidateOk.class),
+        ToolHelper.getClassAsBytes(HostOkSideEffects.class),
+        ToolHelper.getClassAsBytes(CandidateOkSideEffects.class),
+        ToolHelper.getClassAsBytes(HostConflictMethod.class),
+        ToolHelper.getClassAsBytes(CandidateConflictMethod.class),
+        ToolHelper.getClassAsBytes(HostConflictField.class),
+        ToolHelper.getClassAsBytes(CandidateConflictField.class),
+    };
+    AndroidApp app = runR8(buildAndroidApp(classes), MoveToHostTestClass.class);
+
+    String javaOutput = runOnJava(MoveToHostTestClass.class);
+    String artOutput = runOnArt(app, MoveToHostTestClass.class);
+    assertEquals(javaOutput, artOutput);
+
+    CodeInspector inspector = new CodeInspector(app);
+    ClassSubject clazz = inspector.clazz(MoveToHostTestClass.class);
+
+    assertEquals(
+        Lists.newArrayList(
+            "STATIC: String movetohost.HostOk.bar(String)",
+            "STATIC: String movetohost.HostOk.foo()",
+            "STATIC: String movetohost.MoveToHostTestClass.next()",
+            "STATIC: String movetohost.MoveToHostTestClass.next()",
+            "STATIC: void movetohost.HostOk.blah(String)"),
+        references(clazz, "testOk", "void"));
+
+    assertFalse(inspector.clazz(CandidateOk.class).isPresent());
+
+    assertEquals(
+        Lists.newArrayList(
+            "STATIC: String movetohost.HostOkSideEffects.bar(String)",
+            "STATIC: String movetohost.HostOkSideEffects.foo()",
+            "STATIC: String movetohost.MoveToHostTestClass.next()",
+            "movetohost.HostOkSideEffects movetohost.HostOkSideEffects.INSTANCE",
+            "movetohost.HostOkSideEffects movetohost.HostOkSideEffects.INSTANCE"),
+        references(clazz, "testOkSideEffects", "void"));
+
+    assertFalse(inspector.clazz(CandidateOkSideEffects.class).isPresent());
+
+    assertEquals(
+        Lists.newArrayList(
+            "DIRECT: void movetohost.HostConflictMethod.<init>()",
+            "STATIC: String movetohost.CandidateConflictMethod.bar(String)",
+            "STATIC: String movetohost.CandidateConflictMethod.foo()",
+            "STATIC: String movetohost.MoveToHostTestClass.next()",
+            "STATIC: String movetohost.MoveToHostTestClass.next()",
+            "VIRTUAL: String movetohost.HostConflictMethod.bar(String)"),
+        references(clazz, "testConflictMethod", "void"));
+
+    assertTrue(inspector.clazz(CandidateConflictMethod.class).isPresent());
+
+    assertEquals(
+        Lists.newArrayList(
+            "DIRECT: void movetohost.HostConflictField.<init>()",
+            "STATIC: String movetohost.CandidateConflictField.bar(String)",
+            "STATIC: String movetohost.CandidateConflictField.foo()",
+            "STATIC: String movetohost.MoveToHostTestClass.next()",
+            "String movetohost.CandidateConflictField.field"),
+        references(clazz, "testConflictField", "void"));
+
+    assertTrue(inspector.clazz(CandidateConflictMethod.class).isPresent());
+  }
+
+  private List<String> instanceMethods(ClassSubject clazz) {
+    assertNotNull(clazz);
+    assertTrue(clazz.isPresent());
+    return Streams.stream(clazz.getDexClass().methods())
+        .filter(method -> !method.isStaticMethod())
+        .map(method -> method.method.toSourceString())
+        .sorted()
+        .collect(Collectors.toList());
+  }
+
+  private List<String> references(
+      ClassSubject clazz, String methodName, String retValue, String... params) {
+    assertNotNull(clazz);
+    assertTrue(clazz.isPresent());
+
+    MethodSignature signature = new MethodSignature(methodName, retValue, params);
+    DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
+    return Streams.concat(
+        filterInstructionKind(code, SgetObject.class)
+            .map(Instruction::getField)
+            .filter(fld -> isTypeOfInterest(fld.clazz))
+            .map(DexField::toSourceString),
+        filterInstructionKind(code, SputObject.class)
+            .map(Instruction::getField)
+            .filter(fld -> isTypeOfInterest(fld.clazz))
+            .map(DexField::toSourceString),
+        filterInstructionKind(code, InvokeStatic.class)
+            .map(insn -> (InvokeStatic) insn)
+            .map(InvokeStatic::getMethod)
+            .filter(method -> isTypeOfInterest(method.holder))
+            .map(method -> "STATIC: " + method.toSourceString()),
+        filterInstructionKind(code, InvokeVirtual.class)
+            .map(insn -> (InvokeVirtual) insn)
+            .map(InvokeVirtual::getMethod)
+            .filter(method -> isTypeOfInterest(method.holder))
+            .map(method -> "VIRTUAL: " + method.toSourceString()),
+        filterInstructionKind(code, InvokeDirect.class)
+            .map(insn -> (InvokeDirect) insn)
+            .map(InvokeDirect::getMethod)
+            .filter(method -> isTypeOfInterest(method.holder))
+            .map(method -> "DIRECT: " + method.toSourceString()))
+        .map(txt -> txt.replace("java.lang.", ""))
+        .map(txt -> txt.replace("com.android.tools.r8.ir.optimize.staticizer.", ""))
+        .sorted()
+        .collect(Collectors.toList());
+  }
+
+  private boolean isTypeOfInterest(DexType type) {
+    return type.toSourceString().startsWith("com.android.tools.r8.ir.optimize.staticizer");
+  }
+
+  private AndroidApp runR8(AndroidApp app, Class mainClass) throws Exception {
+    AndroidApp compiled =
+        compileWithR8(app, getProguardConfig(mainClass.getCanonicalName()), this::configure);
+
+    // Materialize file for execution.
+    Path generatedDexFile = temp.getRoot().toPath().resolve("classes.jar");
+    compiled.writeToZip(generatedDexFile, OutputMode.DexIndexed);
+
+    // Run with ART.
+    String artOutput = ToolHelper.runArtNoVerificationErrors(
+        generatedDexFile.toString(), mainClass.getCanonicalName());
+
+    // Compare with Java.
+    ProcessResult javaResult = ToolHelper.runJava(
+        ToolHelper.getClassPathForTests(), mainClass.getCanonicalName());
+
+    if (javaResult.exitCode != 0) {
+      System.out.println(javaResult.stdout);
+      System.err.println(javaResult.stderr);
+      fail("JVM failed for: " + mainClass);
+    }
+    assertEquals("JVM and ART output differ", javaResult.stdout, artOutput);
+
+    return compiled;
+  }
+
+  private String getProguardConfig(String main) {
+    return keepMainProguardConfiguration(main)
+        + System.lineSeparator()
+        + "-dontobfuscate"
+        + System.lineSeparator()
+        + "-allowaccessmodification";
+  }
+
+  private void configure(InternalOptions options) {
+    options.enableClassInlining = false;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java
new file mode 100644
index 0000000..f2108b0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictField.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class CandidateConflictField {
+  public static String field;
+
+  public String foo() {
+    synchronized ("") {
+      return bar("CandidateConflictMethod::foo()");
+    }
+  }
+
+  public String bar(String other) {
+    synchronized ("") {
+      return "CandidateConflictMethod::bar(" + other + ")";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictMethod.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictMethod.java
new file mode 100644
index 0000000..40d0576
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateConflictMethod.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class CandidateConflictMethod {
+  public String foo() {
+    synchronized ("") {
+      return bar("CandidateConflictMethod::foo()");
+    }
+  }
+
+  public String bar(String other) {
+    synchronized ("") {
+      return "CandidateConflictMethod::bar(" + other + ")";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOk.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOk.java
new file mode 100644
index 0000000..daf5647
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOk.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class CandidateOk {
+  public String foo() {
+    synchronized ("") {
+      return bar("CandidateOk::foo()");
+    }
+  }
+
+  public String bar(String other) {
+    synchronized ("") {
+      return "CandidateOk::bar(" + other + ")";
+    }
+  }
+
+  public void blah(String other) {
+    synchronized ("") {
+      System.out.println("CandidateOk::blah(" + other + ")");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOkSideEffects.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOkSideEffects.java
new file mode 100644
index 0000000..2077056
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/CandidateOkSideEffects.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class CandidateOkSideEffects {
+  public String foo() {
+    synchronized ("") {
+      return bar("CandidateOkSideEffects::foo()");
+    }
+  }
+
+  public String bar(String other) {
+    synchronized ("") {
+      return "CandidateOkSideEffects::bar(" + other + ")";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictField.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictField.java
new file mode 100644
index 0000000..7166b81
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictField.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class HostConflictField {
+  static CandidateConflictField INSTANCE = new CandidateConflictField();
+
+  public String field;
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictMethod.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictMethod.java
new file mode 100644
index 0000000..ad86b35
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostConflictMethod.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class HostConflictMethod {
+  static CandidateConflictMethod INSTANCE = new CandidateConflictMethod();
+
+  public String bar(String other) {
+    synchronized ("") {
+      return "HostConflictMethod::bar(" + other + ")";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOk.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOk.java
new file mode 100644
index 0000000..8ecd7ac
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOk.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class HostOk {
+  static CandidateOk INSTANCE = new CandidateOk();
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOkSideEffects.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOkSideEffects.java
new file mode 100644
index 0000000..1f6ec7c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/HostOkSideEffects.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class HostOkSideEffects {
+  static CandidateOkSideEffects INSTANCE = new CandidateOkSideEffects();
+
+  static {
+    System.out.println("Inside HostOkSideEffects::<clinit>()");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostTestClass.java
new file mode 100644
index 0000000..3f31839
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/movetohost/MoveToHostTestClass.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.movetohost;
+
+public class MoveToHostTestClass {
+  private static int ID = 0;
+
+  private static String next() {
+    return Integer.toString(ID++);
+  }
+
+  public static void main(String[] args) {
+    MoveToHostTestClass test = new MoveToHostTestClass();
+    test.testOk();
+    test.testOkSideEffects();
+    test.testConflictMethod();
+    test.testConflictField();
+  }
+
+  private synchronized void testOk() {
+    System.out.println(HostOk.INSTANCE.foo());
+    System.out.println(HostOk.INSTANCE.bar(next()));
+    HostOk.INSTANCE.blah(next());
+  }
+
+  private synchronized void testOkSideEffects() {
+    System.out.println(HostOkSideEffects.INSTANCE.foo());
+    System.out.println(HostOkSideEffects.INSTANCE.bar(next()));
+  }
+
+  private synchronized void testConflictMethod() {
+    System.out.println(new HostConflictMethod().bar(next()));
+    System.out.println(HostConflictMethod.INSTANCE.foo());
+    System.out.println(HostConflictMethod.INSTANCE.bar(next()));
+  }
+
+  private synchronized void testConflictField() {
+    System.out.println(new HostConflictField().field);
+    System.out.println(CandidateConflictField.field);
+    System.out.println(HostConflictField.INSTANCE.foo());
+    System.out.println(HostConflictField.INSTANCE.bar(next()));
+  }
+}
+
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/Simple.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/Simple.java
new file mode 100644
index 0000000..8df079c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/Simple.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.trivial;
+
+public class Simple {
+  static Simple INSTANCE = new Simple();
+
+  String foo() {
+    synchronized ("") {
+      return bar("Simple::foo()");
+    }
+  }
+
+  String bar(String other) {
+    synchronized ("") {
+      return "Simple::bar(" + other + ")";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
new file mode 100644
index 0000000..9ff20c3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithGetter.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.trivial;
+
+public class SimpleWithGetter {
+  private static SimpleWithGetter INSTANCE = new SimpleWithGetter();
+
+  static SimpleWithGetter getInstance() {
+    return INSTANCE;
+  }
+
+  String foo() {
+    synchronized ("") {
+      return bar("Simple::foo()");
+    }
+  }
+
+  String bar(String other) {
+    synchronized ("") {
+      return "Simple::bar(" + other + ")";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java
new file mode 100644
index 0000000..a71f1f5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithParams.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.trivial;
+
+public class SimpleWithParams {
+  static SimpleWithParams INSTANCE = new SimpleWithParams(123);
+
+  SimpleWithParams(int i) {
+  }
+
+  String foo() {
+    synchronized ("") {
+      return bar("SimpleWithParams::foo()");
+    }
+  }
+
+  String bar(String other) {
+    synchronized ("") {
+      return "SimpleWithParams::bar(" + other + ")";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithSideEffects.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithSideEffects.java
new file mode 100644
index 0000000..5ba97dc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/SimpleWithSideEffects.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.trivial;
+
+public class SimpleWithSideEffects {
+  static SimpleWithSideEffects INSTANCE = new SimpleWithSideEffects();
+
+  static {
+    System.out.println("SimpleWithSideEffects::<clinit>()");
+  }
+
+  String foo() {
+    synchronized ("") {
+      return bar("SimpleWithSideEffects::foo()");
+    }
+  }
+
+  String bar(String other) {
+    synchronized ("") {
+      return "SimpleWithSideEffects::bar(" + other + ")";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/TrivialTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/TrivialTestClass.java
new file mode 100644
index 0000000..2dad273
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/staticizer/trivial/TrivialTestClass.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.staticizer.trivial;
+
+public class TrivialTestClass {
+  private static int ID = 0;
+
+  private static String next() {
+    return Integer.toString(ID++);
+  }
+
+  public static void main(String[] args) {
+    TrivialTestClass test = new TrivialTestClass();
+    test.testSimple();
+    test.testSimpleWithSideEffects();
+    test.testSimpleWithParams();
+    test.testSimpleWithGetter();
+  }
+
+  private synchronized void testSimple() {
+    System.out.println(Simple.INSTANCE.foo());
+    System.out.println(Simple.INSTANCE.bar(next()));
+  }
+
+  private synchronized void testSimpleWithSideEffects() {
+    System.out.println(SimpleWithSideEffects.INSTANCE.foo());
+    System.out.println(SimpleWithSideEffects.INSTANCE.bar(next()));
+  }
+
+  private synchronized void testSimpleWithParams() {
+    System.out.println(SimpleWithParams.INSTANCE.foo());
+    System.out.println(SimpleWithParams.INSTANCE.bar(next()));
+  }
+
+  private synchronized void testSimpleWithGetter() {
+    System.out.println(SimpleWithGetter.getInstance().foo());
+    System.out.println(SimpleWithGetter.getInstance().bar(next()));
+  }
+}
+
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/B77240639.java b/src/test/java/com/android/tools/r8/ir/regalloc/B77240639.java
index a143b65..2c66eb1 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/B77240639.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/B77240639.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.regalloc;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertThat;
@@ -11,8 +11,8 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.List;
 import java.util.Map;
 import org.junit.Test;
@@ -21,7 +21,7 @@
   @Test
   public void test1() throws Exception {
     AndroidApp app = compileWithD8(readClasses(TestClass.class));
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     ClassSubject clazz = inspector.clazz(TestClass.class);
     assertThat(clazz, isPresent());
   }
@@ -29,7 +29,7 @@
   @Test
   public void test2() throws Exception {
     AndroidApp app = compileWithD8(readClasses(OtherTestClass.class));
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     ClassSubject clazz = inspector.clazz(OtherTestClass.class);
     assertThat(clazz, isPresent());
     ToolHelper.ProcessResult d8Result = runOnArtRaw(app, OtherTestClass.class.getCanonicalName());
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java b/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java
index 08058e5..4065493 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/B79405526.java
@@ -4,20 +4,21 @@
 
 package com.android.tools.r8.ir.regalloc;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 
 public class B79405526 extends TestBase {
   @Test
   public void test() throws Exception {
     AndroidApp app = compileWithD8(readClasses(TestClass.class));
-    DexInspector inspector = new DexInspector(app);
-    DexInspector.ClassSubject clazz = inspector.clazz(TestClass.class);
+    CodeInspector inspector = new CodeInspector(app);
+    ClassSubject clazz = inspector.clazz(TestClass.class);
     assertThat(clazz, isPresent());
     // Throws if a method in TestClass does not verify.
     runOnArt(app, TestClass.class.getName());
diff --git a/src/test/java/com/android/tools/r8/jar/UnicodeSetRegression/UnicodeSetRegressionTest.java b/src/test/java/com/android/tools/r8/jar/UnicodeSetRegression/UnicodeSetRegressionTest.java
index 16fed9e..6f8d39c 100644
--- a/src/test/java/com/android/tools/r8/jar/UnicodeSetRegression/UnicodeSetRegressionTest.java
+++ b/src/test/java/com/android/tools/r8/jar/UnicodeSetRegression/UnicodeSetRegressionTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.utils.ArtErrorParser;
 import com.android.tools.r8.utils.ArtErrorParser.ArtErrorInfo;
 import com.android.tools.r8.utils.ArtErrorParser.ArtErrorParserException;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -60,8 +60,8 @@
     } catch (AssertionError e) {
       AndroidApp fromDexApp =
           ToolHelper.runR8(dexFromDX(), options -> options.ignoreMissingClasses = true);
-      DexInspector fromDex = new DexInspector(fromDexApp);
-      DexInspector fromJar = new DexInspector(result);
+      CodeInspector fromDex = new CodeInspector(fromDexApp);
+      CodeInspector fromJar = new CodeInspector(result);
       List<ArtErrorInfo> errors;
       try {
         errors = ArtErrorParser.parse(e.getMessage());
diff --git a/src/test/java/com/android/tools/r8/jasmin/AnnotationCompanionClassTest.java b/src/test/java/com/android/tools/r8/jasmin/AnnotationCompanionClassTest.java
index 033183c..7e90163 100644
--- a/src/test/java/com/android/tools/r8/jasmin/AnnotationCompanionClassTest.java
+++ b/src/test/java/com/android/tools/r8/jasmin/AnnotationCompanionClassTest.java
@@ -7,7 +7,7 @@
 
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
@@ -33,9 +33,9 @@
     JasminBuilder builder = buildClass();
     AndroidApp androidApp = compileWithD8(builder);
 
-    DexInspector dexInspector = new DexInspector(androidApp);
+    CodeInspector codeInspector = new CodeInspector(androidApp);
     assertFalse(
-        dexInspector
+        codeInspector
             .clazz("LMyAnnotation" + InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX + ";")
             .isAnnotation());
   }
diff --git a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
index e572bfa..a6505c8 100644
--- a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
@@ -12,9 +12,9 @@
 import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
@@ -101,7 +101,7 @@
     AndroidApp jasminApp = builder.build();
     AndroidApp d8App = ToolHelper.runD8(jasminApp);
 
-    DexInspector inspector = new DexInspector(d8App);
+    CodeInspector inspector = new CodeInspector(d8App);
     ClassSubject classSubject = inspector.clazz("Test");
     MethodSubject methodSubject = classSubject.method(foo);
     DexCode code = methodSubject.getMethod().getCode().asDexCode();
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java b/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java
index 27d8d8b..40928ee 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JumboStringTests.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
@@ -91,7 +91,7 @@
     AndroidApp d8App = ToolHelper.runD8(jasminApp);
     assertEquals(expected, runOnArt(d8App, clazz.name));
 
-    DexInspector inspector = new DexInspector(d8App);
+    CodeInspector inspector = new CodeInspector(d8App);
     for (Entry<String, MethodSignature> entry : classes.entrySet()) {
       DebugInfoInspector info = new DebugInfoInspector(inspector, entry.getKey(), entry.getValue());
       info.checkStartLine(1);
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
index ec18fac..fd68772 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import org.junit.Test;
 
 public class JumpSubroutineTests extends JasminTestBase {
@@ -40,7 +39,7 @@
         R8Command.builder()
             .addProgramFiles(inputJar)
             .setOutput(outputJar, OutputMode.ClassFile)
-            .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .build(),
         options -> options.enableCfFrontend = true);
     ProcessResult processResult = ToolHelper.runJava(outputJar, main);
diff --git a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
index 7b74453..4919227 100644
--- a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
+++ b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
@@ -14,9 +14,9 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.AnnotationSubject;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -132,9 +132,9 @@
 
     compileWithR8(INPUT_PATH, outputPath, SHRINK_NO_KEEP_CONFIG);
 
-    DexInspector dexInspector =
-        new DexInspector(outputPath.resolve("classes.dex"), getGeneratedProguardMap());
-    ClassSubject classSubject = dexInspector.clazz("HelloKt");
+    CodeInspector codeInspector =
+        new CodeInspector(outputPath.resolve("classes.dex"), getGeneratedProguardMap());
+    ClassSubject classSubject = codeInspector.clazz("HelloKt");
     AnnotationSubject annotationSubject =
         classSubject.annotation("dalvik.annotation.SourceDebugExtension");
     Assert.assertFalse(annotationSubject.isPresent());
@@ -147,8 +147,8 @@
         new ReadSourceDebugExtensionAttribute(Opcodes.ASM6, null);
     classReader.accept(sourceDebugExtensionReader, 0);
 
-    DexInspector dexInspector = new DexInspector(androidApp);
-    ClassSubject classSubject = dexInspector.clazz("HelloKt");
+    CodeInspector codeInspector = new CodeInspector(androidApp);
+    ClassSubject classSubject = codeInspector.clazz("HelloKt");
 
     AnnotationSubject annotationSubject =
         classSubject.annotation("dalvik.annotation.SourceDebugExtension");
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index fd78cb7..4405565 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -21,12 +21,12 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -112,7 +112,7 @@
     }));
   }
 
-  protected ClassSubject checkClassIsKept(DexInspector inspector, String className) {
+  protected ClassSubject checkClassIsKept(CodeInspector inspector, String className) {
     checkClassExistsInInput(className);
     ClassSubject classSubject = inspector.clazz(className);
     assertNotNull(classSubject);
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
index dc2774a..67afdd7 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -17,8 +17,8 @@
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
@@ -55,7 +55,7 @@
         clazz.interfaces.size() == 1;
   }
 
-  private static Predicate<DexType> createLambdaCheck(DexInspector inspector) {
+  private static Predicate<DexType> createLambdaCheck(CodeInspector inspector) {
     Set<DexType> lambdaClasses = inspector.allClasses().stream()
         .filter(clazz -> isLambda(clazz.getDexClass()))
         .map(clazz -> clazz.getDexClass().type)
@@ -67,7 +67,7 @@
   public void testJStyleLambdas() throws Exception {
     final String mainClassName = "class_inliner_lambda_j_style.MainKt";
     runTest("class_inliner_lambda_j_style", mainClassName, false, (app) -> {
-      DexInspector inspector = new DexInspector(app);
+      CodeInspector inspector = new CodeInspector(app);
       assertTrue(
           inspector.clazz("class_inliner_lambda_j_style.MainKt$testStateful$1").isPresent());
       assertTrue(
@@ -77,7 +77,7 @@
     });
 
     runTest("class_inliner_lambda_j_style", mainClassName, true, (app) -> {
-      DexInspector inspector = new DexInspector(app);
+      CodeInspector inspector = new CodeInspector(app);
       Predicate<DexType> lambdaCheck = createLambdaCheck(inspector);
       ClassSubject clazz = inspector.clazz(mainClassName);
 
@@ -112,7 +112,7 @@
   public void testKStyleLambdas() throws Exception {
     final String mainClassName = "class_inliner_lambda_k_style.MainKt";
     runTest("class_inliner_lambda_k_style", mainClassName, false, (app) -> {
-      DexInspector inspector = new DexInspector(app);
+      CodeInspector inspector = new CodeInspector(app);
       assertTrue(inspector.clazz(
           "class_inliner_lambda_k_style.MainKt$testKotlinSequencesStateless$1").isPresent());
       assertTrue(inspector.clazz(
@@ -135,7 +135,7 @@
     });
 
     runTest("class_inliner_lambda_k_style", mainClassName, true, (app) -> {
-      DexInspector inspector = new DexInspector(app);
+      CodeInspector inspector = new CodeInspector(app);
       Predicate<DexType> lambdaCheck = createLambdaCheck(inspector);
       ClassSubject clazz = inspector.clazz(mainClassName);
 
@@ -186,7 +186,7 @@
   public void testDataClass() throws Exception {
     final String mainClassName = "class_inliner_data_class.MainKt";
     runTest("class_inliner_data_class", mainClassName, true, (app) -> {
-      DexInspector inspector = new DexInspector(app);
+      CodeInspector inspector = new CodeInspector(app);
       ClassSubject clazz = inspector.clazz(mainClassName);
       assertTrue(collectAccessedTypes(
           type -> !type.toSourceString().startsWith("java."),
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
new file mode 100644
index 0000000..c19b80e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassStaticizerTest.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameters;
+
+public class KotlinClassStaticizerTest extends AbstractR8KotlinTestBase {
+  @Parameters(name = "allowAccessModification: {0} target: {1}")
+  public static Collection<Object[]> data() {
+    ImmutableList.Builder<Object[]> builder = new ImmutableList.Builder<>();
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      builder.add(new Object[]{Boolean.TRUE, targetVersion});
+    }
+    return builder.build();
+  }
+
+  @Test
+  public void testCompanionAndRegularObjects() throws Exception {
+    final String mainClassName = "class_staticizer.MainKt";
+
+    // Without class staticizer.
+    runTest("class_staticizer", mainClassName, false, (app) -> {
+      CodeInspector inspector = new CodeInspector(app);
+      assertTrue(inspector.clazz("class_staticizer.Regular$Companion").isPresent());
+      assertTrue(inspector.clazz("class_staticizer.Derived$Companion").isPresent());
+
+      ClassSubject utilClass = inspector.clazz("class_staticizer.Util");
+      assertTrue(utilClass.isPresent());
+      AtomicInteger nonStaticMethodCount = new AtomicInteger();
+      utilClass.forAllMethods(method -> {
+        if (!method.isStatic()) {
+          nonStaticMethodCount.incrementAndGet();
+        }
+      });
+      assertEquals(4, nonStaticMethodCount.get());
+    });
+
+    // With class staticizer.
+    runTest("class_staticizer", mainClassName, true, (app) -> {
+      CodeInspector inspector = new CodeInspector(app);
+      assertFalse(inspector.clazz("class_staticizer.Regular$Companion").isPresent());
+      assertFalse(inspector.clazz("class_staticizer.Derived$Companion").isPresent());
+
+      ClassSubject utilClass = inspector.clazz("class_staticizer.Util");
+      assertTrue(utilClass.isPresent());
+      utilClass.forAllMethods(method -> assertTrue(method.isStatic()));
+    });
+  }
+
+  protected void runTest(String folder, String mainClass,
+      boolean enabled, AndroidAppInspector inspector) throws Exception {
+    runTest(
+        folder, mainClass, null,
+        options -> {
+          options.enableClassInlining = false;
+          options.enableClassStaticizer = enabled;
+        }, inspector);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
index 062095c..e43e94c 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
@@ -13,8 +13,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.Lists;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -139,21 +139,21 @@
   }
 
   static class Verifier {
-    final DexInspector dexInspector;
+    final CodeInspector codeInspector;
     final List<DexClass> lambdas = new ArrayList<>();
     final List<DexClass> groups = new ArrayList<>();
 
     Verifier(AndroidApp app) throws IOException, ExecutionException {
-      this(new DexInspector(app));
+      this(new CodeInspector(app));
     }
 
-    Verifier(DexInspector dexInspector) {
-      this.dexInspector = dexInspector;
+    Verifier(CodeInspector codeInspector) {
+      this.codeInspector = codeInspector;
       initGroupsAndLambdas();
     }
 
     private void initGroupsAndLambdas() {
-      dexInspector.forAllClasses(clazz -> {
+      codeInspector.forAllClasses(clazz -> {
         DexClass dexClass = clazz.getDexClass();
         if (isLambdaOrGroup(dexClass)) {
           if (isLambdaGroupClass(dexClass)) {
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java
index 2beb777..87b9487 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinxMetadataExtensionsServiceTest.java
@@ -19,8 +19,8 @@
 import com.android.tools.r8.kotlin.KotlinLambdaMergingTest.Lambda;
 import com.android.tools.r8.kotlin.KotlinLambdaMergingTest.Verifier;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -60,7 +60,7 @@
         "No MetadataExtensions instances found in the classpath")));
     assertTrue(Files.exists(output));
 
-    DexInspector inspector = new DexInspector(output);
+    CodeInspector inspector = new CodeInspector(output);
     Verifier verifier = new Verifier(inspector);
     String pkg = "lambdas_kstyle_trivial";
     verifier.assertLambdaGroups(
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index 7425082..a3aa5e1 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -16,11 +16,13 @@
 import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
 import java.nio.file.Path;
 import java.util.Collections;
+import java.util.function.Consumer;
 import org.junit.Assert;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -59,14 +61,17 @@
           .addProperty("property", JAVA_LANG_STRING, Visibility.PRIVATE)
           .addProperty("indirectPropertyGetter", JAVA_LANG_STRING, Visibility.PRIVATE);
 
+  private Consumer<InternalOptions> disableClassStaticizer =
+      opts -> opts.enableClassStaticizer = false;
+
   @Test
   public void testCompanionProperty_primitivePropertyIsAlwaysInlined() throws Exception {
     final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
         "companionProperties_usePrimitiveProp");
-    runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
+    runTest(PROPERTIES_PACKAGE_NAME, mainClass, disableClassStaticizer, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
       String propertyName = "primitiveProp";
       FieldSubject fieldSubject = checkFieldIsKept(outerClass, "int", propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -93,9 +98,9 @@
     final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
         "companionProperties_usePrivateProp");
-    runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
+    runTest(PROPERTIES_PACKAGE_NAME, mainClass, disableClassStaticizer, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
       String propertyName = "privateProp";
       FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -123,9 +128,9 @@
     final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
         "companionProperties_useInternalProp");
-    runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
+    runTest(PROPERTIES_PACKAGE_NAME, mainClass, disableClassStaticizer, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
       String propertyName = "internalProp";
       FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -152,9 +157,9 @@
     final TestKotlinCompanionClass testedClass = COMPANION_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionPropertiesKt",
         "companionProperties_usePublicProp");
-    runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
+    runTest(PROPERTIES_PACKAGE_NAME, mainClass, disableClassStaticizer, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
       String propertyName = "publicProp";
       FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -181,9 +186,9 @@
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_usePrivateLateInitProp");
-    runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
+    runTest(PROPERTIES_PACKAGE_NAME, mainClass, disableClassStaticizer, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
       String propertyName = "privateLateInitProp";
       FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -210,8 +215,8 @@
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_useInternalLateInitProp");
     runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
       String propertyName = "internalLateInitProp";
       FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -234,8 +239,8 @@
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_usePublicLateInitProp");
     runTest(PROPERTIES_PACKAGE_NAME, mainClass, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
       String propertyName = "publicLateInitProp";
       FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -257,10 +262,10 @@
     TestKotlinCompanionClass testedClass = ACCESSOR_COMPANION_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("accessors.AccessorKt",
         "accessor_accessPropertyFromCompanionClass");
-    runTest("accessors", mainClass, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
-      ClassSubject companionClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+    runTest("accessors", mainClass, disableClassStaticizer, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
+      ClassSubject companionClass = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "property";
       FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -288,9 +293,9 @@
     String mainClass = addMainToClasspath("accessors.AccessorKt",
         "accessor_accessPropertyFromOuterClass");
     runTest("accessors", mainClass, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
-      ClassSubject companionClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
+      ClassSubject companionClass = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "property";
       FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -318,8 +323,8 @@
     String mainClass = addMainToClasspath(testedClass.className + "Kt",
         "noUseOfPropertyAccessorFromInnerClass");
     runTest("accessors", mainClass, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject classSubject = checkClassIsKept(dexInspector, testedClass.getClassName());
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject classSubject = checkClassIsKept(codeInspector, testedClass.getClassName());
 
       for (String propertyName : testedClass.properties.keySet()) {
         MemberNaming.MethodSignature getterAccessor =
@@ -340,8 +345,8 @@
     String mainClass = addMainToClasspath(testedClass.className + "Kt",
         "usePrivatePropertyAccessorFromInnerClass");
     runTest("accessors", mainClass, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject classSubject = checkClassIsKept(dexInspector, testedClass.getClassName());
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject classSubject = checkClassIsKept(codeInspector, testedClass.getClassName());
 
       String propertyName = "privateProp";
       FieldSubject fieldSubject = checkFieldIsKept(classSubject, JAVA_LANG_STRING,
@@ -371,8 +376,8 @@
     String mainClass = addMainToClasspath(testedClass.className + "Kt",
         "usePrivateLateInitPropertyAccessorFromInnerClass");
     runTest("accessors", mainClass, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject classSubject = checkClassIsKept(dexInspector, testedClass.getClassName());
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject classSubject = checkClassIsKept(codeInspector, testedClass.getClassName());
 
       String propertyName = "privateLateInitProp";
       FieldSubject fieldSubject = checkFieldIsKept(classSubject, JAVA_LANG_STRING,
@@ -402,8 +407,8 @@
     String mainClass = addMainToClasspath(testedClass.className + "Kt",
         "noUseOfPropertyAccessorFromLambda");
     runTest("accessors", mainClass, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject classSubject = checkClassIsKept(dexInspector, testedClass.getClassName());
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject classSubject = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "property";
 
       MemberNaming.MethodSignature getterAccessor =
@@ -423,8 +428,8 @@
     String mainClass = addMainToClasspath(testedClass.className + "Kt",
         "usePropertyAccessorFromLambda");
     runTest("accessors", mainClass, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject classSubject = checkClassIsKept(dexInspector, testedClass.getClassName());
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject classSubject = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "property";
       FieldSubject fieldSubject = checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName);
       assertFalse(fieldSubject.getField().accessFlags.isStatic());
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
index 73d1e50..58eae89 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -7,10 +7,10 @@
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.util.Collections;
 import java.util.function.Consumer;
 import org.junit.Test;
@@ -44,8 +44,8 @@
         new MethodSignature("testDataClassGetters", "void", Collections.emptyList());
     final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
     runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject dataClass = checkClassIsKept(dexInspector, TEST_DATA_CLASS.getClassName());
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject dataClass = checkClassIsKept(codeInspector, TEST_DATA_CLASS.getClassName());
 
       // Getters should be removed after inlining, which is possible only if access is relaxed.
       final boolean areGetterPresent = !allowAccessModification;
@@ -60,7 +60,7 @@
       checkMethodIsRemoved(dataClass, COPY_METHOD);
       checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD);
 
-      ClassSubject classSubject = checkClassIsKept(dexInspector, mainClassName);
+      ClassSubject classSubject = checkClassIsKept(codeInspector, mainClassName);
       MethodSubject testMethod = checkMethodIsKept(classSubject, testMethodSignature);
       DexCode dexCode = getDexCode(testMethod);
       if (allowAccessModification) {
@@ -79,8 +79,8 @@
         new MethodSignature("testAllDataClassComponentFunctions", "void", Collections.emptyList());
     final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
     runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject dataClass = checkClassIsKept(dexInspector, TEST_DATA_CLASS.getClassName());
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject dataClass = checkClassIsKept(codeInspector, TEST_DATA_CLASS.getClassName());
 
       // ComponentN functions should be removed after inlining, which is possible only if access
       // is relaxed.
@@ -96,7 +96,7 @@
       checkMethodIsRemoved(dataClass, COPY_METHOD);
       checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD);
 
-      ClassSubject classSubject = checkClassIsKept(dexInspector, mainClassName);
+      ClassSubject classSubject = checkClassIsKept(codeInspector, mainClassName);
       MethodSubject testMethod = checkMethodIsKept(classSubject, testMethodSignature);
       DexCode dexCode = getDexCode(testMethod);
       if (allowAccessModification) {
@@ -114,8 +114,8 @@
         new MethodSignature("testSomeDataClassComponentFunctions", "void", Collections.emptyList());
     final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
     runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject dataClass = checkClassIsKept(dexInspector, TEST_DATA_CLASS.getClassName());
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject dataClass = checkClassIsKept(codeInspector, TEST_DATA_CLASS.getClassName());
 
       boolean component2IsPresent = !allowAccessModification;
       checkMethodisKeptOrRemoved(dataClass, COMPONENT2_METHOD, component2IsPresent);
@@ -131,7 +131,7 @@
       checkMethodIsRemoved(dataClass, COPY_METHOD);
       checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD);
 
-      ClassSubject classSubject = checkClassIsKept(dexInspector, mainClassName);
+      ClassSubject classSubject = checkClassIsKept(codeInspector, mainClassName);
       MethodSubject testMethod = checkMethodIsKept(classSubject, testMethodSignature);
       DexCode dexCode = getDexCode(testMethod);
       if (allowAccessModification) {
@@ -149,8 +149,8 @@
         new MethodSignature("testDataClassCopy", "void", Collections.emptyList());
     final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
     runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject dataClass = checkClassIsKept(dexInspector, TEST_DATA_CLASS.getClassName());
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject dataClass = checkClassIsKept(codeInspector, TEST_DATA_CLASS.getClassName());
 
       checkMethodIsRemoved(dataClass, COPY_METHOD);
       checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD);
@@ -164,8 +164,8 @@
         new MethodSignature("testDataClassCopyWithDefault", "void", Collections.emptyList());
     final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
     runTest("dataclass", mainClassName, extraRules, disableClassInliner, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject dataClass = checkClassIsKept(dexInspector, TEST_DATA_CLASS.getClassName());
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject dataClass = checkClassIsKept(codeInspector, TEST_DATA_CLASS.getClassName());
 
       checkMethodIsRemoved(dataClass, COPY_DEFAULT_METHOD);
     });
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
index 021e045..c90df04 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
@@ -5,8 +5,8 @@
 package com.android.tools.r8.kotlin;
 
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import java.util.Collections;
@@ -27,9 +27,9 @@
             "java.lang.String", Lists.newArrayList("java.lang.String", "java.lang.String")));
 
     runTest("intrinsics", "intrinsics.IntrinsicsKt", extraRules, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
+      CodeInspector codeInspector = new CodeInspector(app);
       ClassSubject intrinsicsClass = checkClassIsKept(
-          dexInspector, KOTLIN_INTRINSICS_CLASS.getClassName());
+          codeInspector, KOTLIN_INTRINSICS_CLASS.getClassName());
 
       checkMethodsPresence(intrinsicsClass,
           ImmutableMap.<MethodSignature, Boolean>builder()
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index dc9b642..08fcebe 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -10,10 +10,10 @@
 import com.android.tools.r8.kotlin.TestKotlinClass.Visibility;
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
 import java.util.Map.Entry;
 import java.util.function.Consumer;
 import org.junit.Test;
@@ -83,18 +83,19 @@
           .addProperty("internalLateInitProp", JAVA_LANG_STRING, Visibility.INTERNAL)
           .addProperty("publicLateInitProp", JAVA_LANG_STRING, Visibility.PUBLIC);
 
-  private Consumer<InternalOptions> disableClassInliningAndMerging = o -> {
+  private Consumer<InternalOptions> disableAggressiveClassOptimizations = o -> {
     o.enableClassInlining = false;
     o.enableClassMerging = false;
+    o.enableClassStaticizer = false;
   };
 
   @Test
   public void testMutableProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_noUseOfProperties");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject classSubject = checkClassIsKept(dexInspector,
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject classSubject = checkClassIsKept(codeInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
       for (Entry<String, KotlinProperty> property : MUTABLE_PROPERTY_CLASS.properties.entrySet()) {
         MethodSignature getter = MUTABLE_PROPERTY_CLASS.getGetterForProperty(property.getKey());
@@ -115,9 +116,9 @@
   public void testMutableProperty_privateIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_usePrivateProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject classSubject = checkClassIsKept(dexInspector,
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject classSubject = checkClassIsKept(codeInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
       String propertyName = "privateProp";
       FieldSubject fieldSubject = checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName);
@@ -137,9 +138,9 @@
   public void testMutableProperty_protectedIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_useProtectedProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject classSubject = checkClassIsKept(dexInspector,
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject classSubject = checkClassIsKept(codeInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
       String propertyName = "protectedProp";
       FieldSubject fieldSubject = checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName);
@@ -160,9 +161,9 @@
   public void testMutableProperty_internalIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_useInternalProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject classSubject = checkClassIsKept(dexInspector,
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject classSubject = checkClassIsKept(codeInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
       String propertyName = "internalProp";
       FieldSubject fieldSubject = checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName);
@@ -183,9 +184,9 @@
   public void testMutableProperty_publicIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_usePublicProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject classSubject = checkClassIsKept(dexInspector,
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject classSubject = checkClassIsKept(codeInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
       String propertyName = "publicProp";
       FieldSubject fieldSubject = checkFieldIsKept(classSubject, JAVA_LANG_STRING, propertyName);
@@ -206,9 +207,9 @@
   public void testMutableProperty_primitivePropertyIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/MutablePropertyKt",
         "mutableProperty_usePrimitiveProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject classSubject = checkClassIsKept(dexInspector,
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject classSubject = checkClassIsKept(codeInspector,
           MUTABLE_PROPERTY_CLASS.getClassName());
       String propertyName = "primitiveProp";
       FieldSubject fieldSubject = checkFieldIsKept(classSubject, "int", propertyName);
@@ -231,11 +232,12 @@
   public void testLateInitProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
     String mainClass = addMainToClasspath("properties/LateInitPropertyKt",
         "lateInitProperty_noUseOfProperties");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject classSubject = checkClassIsKept(dexInspector,
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject classSubject = checkClassIsKept(codeInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
-      for (Entry<String, KotlinProperty> property : LATE_INIT_PROPERTY_CLASS.properties.entrySet()) {
+      for (Entry<String, KotlinProperty> property : LATE_INIT_PROPERTY_CLASS.properties
+          .entrySet()) {
         MethodSignature getter = LATE_INIT_PROPERTY_CLASS.getGetterForProperty(property.getKey());
         MethodSignature setter = LATE_INIT_PROPERTY_CLASS.getSetterForProperty(property.getKey());
         if (property.getValue().getVisibility() == Visibility.PRIVATE) {
@@ -255,9 +257,9 @@
   public void testLateInitProperty_privateIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/LateInitPropertyKt", "lateInitProperty_usePrivateLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject classSubject = checkClassIsKept(dexInspector,
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject classSubject = checkClassIsKept(codeInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
       String propertyName = "privateLateInitProp";
       FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
@@ -278,9 +280,9 @@
   public void testLateInitProperty_protectedIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath("properties/LateInitPropertyKt",
         "lateInitProperty_useProtectedLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject classSubject = checkClassIsKept(dexInspector,
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject classSubject = checkClassIsKept(codeInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
       String propertyName = "protectedLateInitProp";
       FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
@@ -299,9 +301,9 @@
   public void testLateInitProperty_internalIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/LateInitPropertyKt", "lateInitProperty_useInternalLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject classSubject = checkClassIsKept(dexInspector,
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject classSubject = checkClassIsKept(codeInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
       String propertyName = "internalLateInitProp";
       FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
@@ -318,9 +320,9 @@
   public void testLateInitProperty_publicIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/LateInitPropertyKt", "lateInitProperty_usePublicLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject classSubject = checkClassIsKept(dexInspector,
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject classSubject = checkClassIsKept(codeInspector,
           LATE_INIT_PROPERTY_CLASS.getClassName());
       String propertyName = "publicLateInitProp";
       FieldSubject fieldSubject = classSubject.field(JAVA_LANG_STRING, propertyName);
@@ -337,9 +339,9 @@
   public void testUserDefinedProperty_getterAndSetterAreRemoveIfNotUsed() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/UserDefinedPropertyKt", "userDefinedProperty_noUseOfProperties");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject classSubject = checkClassIsKept(dexInspector,
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject classSubject = checkClassIsKept(codeInspector,
           USER_DEFINED_PROPERTY_CLASS.getClassName());
       for (String propertyName : USER_DEFINED_PROPERTY_CLASS.properties.keySet()) {
         checkMethodIsRemoved(classSubject,
@@ -354,9 +356,9 @@
   public void testUserDefinedProperty_publicIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties/UserDefinedPropertyKt", "userDefinedProperty_useProperties");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject classSubject = checkClassIsKept(dexInspector,
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject classSubject = checkClassIsKept(codeInspector,
           USER_DEFINED_PROPERTY_CLASS.getClassName());
       String propertyName = "durationInSeconds";
       // The 'wrapper' property is not assigned to a backing field, it only relies on the wrapped
@@ -380,11 +382,11 @@
   public void testCompanionProperty_primitivePropertyCannotBeInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_usePrimitiveProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassIsKept(dexInspector,
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject outerClass = checkClassIsKept(codeInspector,
           "properties.CompanionProperties");
-      ClassSubject companionClass = checkClassIsKept(dexInspector,
+      ClassSubject companionClass = checkClassIsKept(codeInspector,
           COMPANION_PROPERTY_CLASS.getClassName());
       String propertyName = "primitiveProp";
       FieldSubject fieldSubject = checkFieldIsKept(outerClass, "int", propertyName);
@@ -411,11 +413,11 @@
   public void testCompanionProperty_privatePropertyIsAlwaysInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_usePrivateProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassIsKept(dexInspector,
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject outerClass = checkClassIsKept(codeInspector,
           "properties.CompanionProperties");
-      ClassSubject companionClass = checkClassIsKept(dexInspector,
+      ClassSubject companionClass = checkClassIsKept(codeInspector,
           COMPANION_PROPERTY_CLASS.getClassName());
       String propertyName = "privateProp";
       FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
@@ -445,11 +447,11 @@
   public void testCompanionProperty_internalPropertyCannotBeInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_useInternalProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassIsKept(dexInspector,
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject outerClass = checkClassIsKept(codeInspector,
           "properties.CompanionProperties");
-      ClassSubject companionClass = checkClassIsKept(dexInspector,
+      ClassSubject companionClass = checkClassIsKept(codeInspector,
           COMPANION_PROPERTY_CLASS.getClassName());
       String propertyName = "internalProp";
       FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
@@ -476,11 +478,11 @@
   public void testCompanionProperty_publicPropertyCannotBeInlined() throws Exception {
     String mainClass = addMainToClasspath(
         "properties.CompanionPropertiesKt", "companionProperties_usePublicProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassIsKept(dexInspector,
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject outerClass = checkClassIsKept(codeInspector,
           "properties.CompanionProperties");
-      ClassSubject companionClass = checkClassIsKept(dexInspector,
+      ClassSubject companionClass = checkClassIsKept(codeInspector,
           COMPANION_PROPERTY_CLASS.getClassName());
       String propertyName = "publicProp";
       FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
@@ -508,10 +510,10 @@
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_usePrivateLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
-      ClassSubject companionClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
+      ClassSubject companionClass = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "privateLateInitProp";
       FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -539,10 +541,10 @@
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_useInternalLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
-      ClassSubject companionClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
+      ClassSubject companionClass = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "internalLateInitProp";
       FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -563,10 +565,10 @@
     final TestKotlinCompanionClass testedClass = COMPANION_LATE_INIT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath("properties.CompanionLateInitPropertiesKt",
         "companionLateInitProperties_usePublicLateInitProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject outerClass = checkClassIsKept(dexInspector, testedClass.getOuterClassName());
-      ClassSubject companionClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject outerClass = checkClassIsKept(codeInspector, testedClass.getOuterClassName());
+      ClassSubject companionClass = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "publicLateInitProp";
       FieldSubject fieldSubject = checkFieldIsKept(outerClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -587,9 +589,9 @@
     final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.ObjectPropertiesKt", "objectProperties_usePrimitiveProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "primitiveProp";
       FieldSubject fieldSubject = checkFieldIsKept(objectClass, "int", propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -614,9 +616,9 @@
     final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.ObjectPropertiesKt", "objectProperties_usePrivateProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "privateProp";
       FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -641,9 +643,9 @@
     final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.ObjectPropertiesKt", "objectProperties_useInternalProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "internalProp";
       FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -668,9 +670,9 @@
     final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.ObjectPropertiesKt", "objectProperties_usePublicProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "publicProp";
       FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -695,9 +697,9 @@
     final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.ObjectPropertiesKt", "objectProperties_useLateInitPrivateProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "privateLateInitProp";
       FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -722,9 +724,9 @@
     final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.ObjectPropertiesKt", "objectProperties_useLateInitInternalProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "internalLateInitProp";
       FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -745,9 +747,9 @@
     final TestKotlinClass testedClass = OBJECT_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.ObjectPropertiesKt", "objectProperties_useLateInitPublicProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "publicLateInitProp";
       FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -768,9 +770,9 @@
     final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.FilePropertiesKt", "fileProperties_usePrimitiveProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "primitiveProp";
       FieldSubject fieldSubject = checkFieldIsKept(objectClass, "int", propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -795,9 +797,9 @@
     final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.FilePropertiesKt", "fileProperties_usePrivateProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "privateProp";
       FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -821,9 +823,9 @@
     final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.FilePropertiesKt", "fileProperties_useInternalProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "internalProp";
       FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -847,9 +849,9 @@
     final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.FilePropertiesKt", "fileProperties_usePublicProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "publicProp";
       FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -874,9 +876,9 @@
     final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.FilePropertiesKt", "fileProperties_useLateInitPrivateProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject fileClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject fileClass = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "privateLateInitProp";
       FieldSubject fieldSubject = checkFieldIsKept(fileClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -900,9 +902,9 @@
     final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.FilePropertiesKt", "fileProperties_useLateInitInternalProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "internalLateInitProp";
       FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
@@ -924,9 +926,9 @@
     final TestKotlinClass testedClass = FILE_PROPERTY_CLASS;
     String mainClass = addMainToClasspath(
         "properties.FilePropertiesKt", "fileProperties_useLateInitPublicProp");
-    runTest(PACKAGE_NAME, mainClass, disableClassInliningAndMerging, (app) -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject objectClass = checkClassIsKept(dexInspector, testedClass.getClassName());
+    runTest(PACKAGE_NAME, mainClass, disableAggressiveClassOptimizations, (app) -> {
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject objectClass = checkClassIsKept(codeInspector, testedClass.getClassName());
       String propertyName = "publicLateInitProp";
       FieldSubject fieldSubject = checkFieldIsKept(objectClass, JAVA_LANG_STRING, propertyName);
       assertTrue(fieldSubject.getField().accessFlags.isStatic());
diff --git a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
index 5a30cfd..12fdaf9 100644
--- a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -10,9 +10,9 @@
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
 import org.junit.Test;
@@ -35,8 +35,8 @@
     final String mainClassName = ex1.getClassName();
     final String extraRules = keepAllMembers(mainClassName);
     runTest(FOLDER, mainClassName, extraRules, app -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject clazz = checkClassIsKept(dexInspector, ex1.getClassName());
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject clazz = checkClassIsKept(codeInspector, ex1.getClassName());
 
       MethodSubject testMethod = checkMethodIsKept(clazz, testMethodSignature);
       DexCode dexCode = getDexCode(testMethod);
@@ -61,8 +61,8 @@
     final String mainClassName = ex2.getClassName();
     final String extraRules = keepAllMembers(mainClassName);
     runTest(FOLDER, mainClassName, extraRules, app -> {
-      DexInspector dexInspector = new DexInspector(app);
-      ClassSubject clazz = checkClassIsKept(dexInspector, ex2.getClassName());
+      CodeInspector codeInspector = new CodeInspector(app);
+      ClassSubject clazz = checkClassIsKept(codeInspector, ex2.getClassName());
 
       MethodSubject testMethod = checkMethodIsKept(clazz, testMethodSignature);
       DexCode dexCode = getDexCode(testMethod);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 101bf06..d56c0e7 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -38,6 +38,7 @@
 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.GraphLense;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.code.CatchHandlers;
@@ -59,8 +60,6 @@
 import com.android.tools.r8.utils.AndroidAppConsumers;
 import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
@@ -68,6 +67,8 @@
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import java.io.IOException;
@@ -484,7 +485,7 @@
         .forEach(
             p -> {
               try {
-                DexInspector i = new DexInspector(AndroidApp.builder().addProgramFiles(p).build());
+                CodeInspector i = new CodeInspector(AndroidApp.builder().addProgramFiles(p).build());
                 assertFalse("Found " + clazz + " in file " + p, i.clazz(clazz).isPresent());
               } catch (IOException | ExecutionException e) {
                 e.printStackTrace();
@@ -519,7 +520,7 @@
       throws IOException, ExecutionException, ProguardRuleParserException,
       CompilationFailedException {
     AndroidApp originalApp = AndroidApp.builder().addProgramFiles(app).build();
-    DexInspector originalInspector = new DexInspector(originalApp);
+    CodeInspector originalInspector = new CodeInspector(originalApp);
     for (String clazz : mainDex) {
       assertTrue("Class " + clazz + " does not exist in input",
           originalInspector.clazz(clazz).isPresent());
@@ -583,8 +584,8 @@
       assertTrue("Output run only produced one dex file.",
           1 < Files.list(outDir).filter(FileUtils::isDexFile).count());
     }
-    DexInspector inspector =
-        new DexInspector(
+    CodeInspector inspector =
+        new CodeInspector(
             AndroidApp.builder().addProgramFiles(outDir.resolve("classes.dex")).build());
     for (String clazz : mainDex) {
       if (!inspector.clazz(clazz).isPresent()) {
@@ -650,7 +651,8 @@
                 DexAnnotationSet.empty(),
                 ParameterAnnotationsList.empty(),
                 code);
-        IRCode ir = code.buildIR(method, null, options, Origin.unknown());
+        IRCode ir =
+            code.buildIR(method, null, GraphLense.getIdentityLense(), options, Origin.unknown());
         RegisterAllocator allocator = new LinearScanRegisterAllocator(ir, options);
         method.setCode(ir, allocator, options);
         directMethods[i] = method;
@@ -676,7 +678,14 @@
     DirectMappedDexApplication application = builder.build().toDirect();
     ApplicationWriter writer =
         new ApplicationWriter(
-            application, options, null, null, NamingLens.getIdentityLens(), null, null);
+            application,
+            options,
+            null,
+            null,
+            GraphLense.getIdentityLense(),
+            NamingLens.getIdentityLens(),
+            null,
+            null);
     ExecutorService executor = ThreadUtils.getExecutorService(options);
     AndroidAppConsumers compatSink = new AndroidAppConsumers(options);
     try {
@@ -771,7 +780,7 @@
     }
 
     @Override
-    public Position getDebugPositionAtOffset(int offset) {
+    public Position getCanonicalDebugPositionAtOffset(int offset) {
       throw new Unreachable();
     }
 
diff --git a/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java b/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java
index 0f1359e..c1741c9 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java
@@ -23,7 +23,7 @@
 import com.android.tools.r8.VmTestRunner;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -84,7 +84,7 @@
         .setMainDexListConsumer(
             (string, handler) -> mainDexList.content = string)
         .build();
-    DexInspector inspector = new DexInspector(ToolHelper.runR8(command));
+    CodeInspector inspector = new CodeInspector(ToolHelper.runR8(command));
     assertTrue(inspector.clazz("instrumentationtest.InstrumentationTest").isPresent());
     assertTrue(mainDexList.content.contains("junit/framework/TestCase.class"));
     // TODO(72794301): Two copies of this message is a bit over the top.
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/CompositionalLenseTest.java b/src/test/java/com/android/tools/r8/memberrebinding/CompositionalLenseTest.java
index aba14cf..6c6e69d 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/CompositionalLenseTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/CompositionalLenseTest.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.memberrebinding;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
@@ -11,15 +11,15 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.List;
@@ -74,10 +74,10 @@
       options.enableInlining = false;
       options.enableClassMerging = false;
     });
-    DexInspector dexInspector = new DexInspector(processedApp);
-    ClassSubject classSubject = dexInspector.clazz(TestMain.class);
+    CodeInspector codeInspector = new CodeInspector(processedApp);
+    ClassSubject classSubject = codeInspector.clazz(TestMain.class);
     assertThat(classSubject, isPresent());
-    MethodSubject methodSubject = classSubject.method(DexInspector.MAIN);
+    MethodSubject methodSubject = classSubject.method(CodeInspector.MAIN);
     assertThat(methodSubject, isPresent());
     DexCode dexCode = methodSubject.getMethod().getCode().asDexCode();
     assertTrue(dexCode.instructions[2] instanceof InvokeVirtual);
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
index 46a439d..80379e0 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
@@ -11,12 +11,12 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.FieldAccessInstructionSubject;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
-import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldAccessInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -55,8 +55,8 @@
   private final Frontend kind;
   private final Path originalDex;
   private final Path programFile;
-  private final Consumer<DexInspector> inspection;
-  private final Consumer<DexInspector> originalInspection;
+  private final Consumer<CodeInspector> inspection;
+  private final Consumer<CodeInspector> originalInspection;
   private final int minApiLevel;
 
   @Rule
@@ -101,9 +101,9 @@
     return !invoke.holder().is("java.io.PrintStream");
   }
 
-  private static void inspectOriginalMain(DexInspector inspector) {
+  private static void inspectOriginalMain(CodeInspector inspector) {
     MethodSubject main = inspector.clazz("memberrebinding.Memberrebinding")
-        .method(DexInspector.MAIN);
+        .method(CodeInspector.MAIN);
     Iterator<InvokeInstructionSubject> iterator =
         main.iterateInstructions(MemberRebindingTest::coolInvokes);
     assertTrue(iterator.next().holder().is("memberrebinding.ClassAtBottomOfChain"));
@@ -129,9 +129,9 @@
     assertFalse(iterator.hasNext());
   }
 
-  private static void inspectMain(DexInspector inspector) {
+  private static void inspectMain(CodeInspector inspector) {
     MethodSubject main = inspector.clazz("memberrebinding.Memberrebinding")
-        .method(DexInspector.MAIN);
+        .method(CodeInspector.MAIN);
     Iterator<InvokeInstructionSubject> iterator =
         main.iterateInstructions(MemberRebindingTest::coolInvokes);
     assertTrue(iterator.next().holder().is("memberrebinding.ClassAtBottomOfChain"));
@@ -160,9 +160,9 @@
     assertFalse(iterator.hasNext());
   }
 
-  private static void inspectOriginalMain2(DexInspector inspector) {
+  private static void inspectOriginalMain2(CodeInspector inspector) {
     MethodSubject main = inspector.clazz("memberrebinding2.Memberrebinding")
-        .method(DexInspector.MAIN);
+        .method(CodeInspector.MAIN);
     Iterator<FieldAccessInstructionSubject> iterator =
         main.iterateInstructions(InstructionSubject::isFieldAccess);
     // Run through instance put, static put, instance get and instance get.
@@ -176,9 +176,9 @@
     assertFalse(iterator.hasNext());
   }
 
-  private static void inspectMain2(DexInspector inspector) {
+  private static void inspectMain2(CodeInspector inspector) {
     MethodSubject main = inspector.clazz("memberrebinding2.Memberrebinding")
-        .method(DexInspector.MAIN);
+        .method(CodeInspector.MAIN);
     Iterator<FieldAccessInstructionSubject> iterator =
         main.iterateInstructions(InstructionSubject::isFieldAccess);
     // Run through instance put, static put, instance get and instance get.
@@ -195,7 +195,7 @@
   public static MethodSignature TEST =
       new MethodSignature("test", "void", new String[]{});
 
-  private static void inspectOriginal3(DexInspector inspector) {
+  private static void inspectOriginal3(CodeInspector inspector) {
     MethodSubject main = inspector.clazz("memberrebinding3.Memberrebinding").method(TEST);
     Iterator<InvokeInstructionSubject> iterator =
         main.iterateInstructions(InstructionSubject::isInvoke);
@@ -205,7 +205,7 @@
     assertFalse(iterator.hasNext());
   }
 
-  private static void inspect3(DexInspector inspector) {
+  private static void inspect3(CodeInspector inspector) {
     MethodSubject main = inspector.clazz("memberrebinding3.Memberrebinding").method(TEST);
     Iterator<InvokeInstructionSubject> iterator =
         main.iterateInstructions(InstructionSubject::isInvoke);
@@ -215,7 +215,7 @@
     assertFalse(iterator.hasNext());
   }
 
-  private static void inspectOriginal4(DexInspector inspector) {
+  private static void inspectOriginal4(CodeInspector inspector) {
     MethodSubject main = inspector.clazz("memberrebinding4.Memberrebinding").method(TEST);
     Iterator<InvokeInstructionSubject> iterator =
         main.iterateInstructions(InstructionSubject::isInvoke);
@@ -224,7 +224,7 @@
     assertFalse(iterator.hasNext());
   }
 
-  private static void inspect4(DexInspector inspector) {
+  private static void inspect4(CodeInspector inspector) {
     MethodSubject main = inspector.clazz("memberrebinding4.Memberrebinding").method(TEST);
     Iterator<InvokeInstructionSubject> iterator =
         main.iterateInstructions(InstructionSubject::isInvoke);
@@ -243,14 +243,14 @@
     final String name;
     final Frontend kind;
     final AndroidVersion version;
-    final Consumer<DexInspector> originalInspection;
-    final Consumer<DexInspector> processedInspection;
+    final Consumer<CodeInspector> originalInspection;
+    final Consumer<CodeInspector> processedInspection;
 
     private TestConfiguration(String name,
         Frontend kind,
         AndroidVersion version,
-        Consumer<DexInspector> originalInspection,
-        Consumer<DexInspector> processedInspection) {
+        Consumer<CodeInspector> originalInspection,
+        Consumer<CodeInspector> processedInspection) {
       this.name = name;
       this.kind = kind;
       this.version = version;
@@ -261,8 +261,8 @@
     public static void add(ImmutableList.Builder<TestConfiguration> builder,
         String name,
         AndroidVersion version,
-        Consumer<DexInspector> originalInspection,
-        Consumer<DexInspector> processedInspection) {
+        Consumer<CodeInspector> originalInspection,
+        Consumer<CodeInspector> processedInspection) {
       if (version == AndroidVersion.PRE_N) {
         builder.add(new TestConfiguration(name, Frontend.DEX, version, originalInspection,
             processedInspection));
@@ -330,11 +330,11 @@
     Path processed = Paths.get(out, "classes.dex");
 
     if (kind == Frontend.DEX) {
-      DexInspector inspector = new DexInspector(originalDex);
+      CodeInspector inspector = new CodeInspector(originalDex);
       originalInspection.accept(inspector);
     }
 
-    DexInspector inspector = new DexInspector(processed);
+    CodeInspector inspector = new CodeInspector(processed);
     inspection.accept(inspector);
 
     // We don't run Art, as the test R8RunExamplesTest already does that.
diff --git a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
index 3429c2a..d7d0538 100644
--- a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
+++ b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8Command;
@@ -13,22 +14,46 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.CfInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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 com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 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 MoveStringConstantsTest extends TestBase {
-  private void runTest(Consumer<DexInspector> inspection) throws Exception {
+
+  private Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  private void runTest(Consumer<CodeInspector> inspection) throws Exception {
     R8Command.Builder builder = R8Command.builder();
     builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
     builder.addProgramFiles(ToolHelper.getClassFileForTestClass(Utils.class));
-    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+    builder.addLibraryFiles(
+        backend == Backend.DEX
+            ? ToolHelper.getDefaultAndroidJar()
+            : ToolHelper.getJava8RuntimeJar());
+    assert (backend == Backend.CF || backend == Backend.DEX);
+    builder.setProgramConsumer(
+        backend == Backend.DEX
+            ? DexIndexedConsumer.emptyConsumer()
+            : ClassFileConsumer.emptyConsumer());
     builder.setMode(CompilationMode.RELEASE);
     builder.addProguardConfiguration(
         ImmutableList.of(
@@ -37,14 +62,35 @@
             "-allowaccessmodification"
         ),
         Origin.unknown());
-    AndroidApp app = ToolHelper.runR8(builder.build());
-    inspection.accept(new DexInspector(app));
+    AndroidApp app =
+        ToolHelper.runR8(
+            builder.build(),
+            options -> {
+              // This test relies on that TestClass.java/Utils.check will be inlined.
+              // Its size must fit into the inlining instruction limit. For CF, the default
+              // setting (5) is just too small.
+              options.inliningInstructionLimit = 10;
+            });
+    inspection.accept(
+        new CodeInspector(
+            app,
+            options -> {
+              options.enableCfFrontend = true;
+            }));
 
-    // Run on Art to check generated code against verifier.
-    runOnArt(app, TestClass.class);
+    if (backend == Backend.DEX) {
+      // Run on Art to check generated code against verifier.
+      runOnArt(app, TestClass.class);
+    } else {
+      runOnJava(app, TestClass.class);
+    }
   }
 
-  private void validate(DexInspector inspector) {
+  public MoveStringConstantsTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  private void validate(CodeInspector inspector) {
     ClassSubject clazz = inspector.clazz(TestClass.class);
     assertTrue(clazz.isPresent());
 
@@ -53,37 +99,41 @@
         clazz.method("void", "foo", ImmutableList.of(
             "java.lang.String", "java.lang.String", "java.lang.String", "java.lang.String"));
     assertTrue(methodThrowToBeInlined.isPresent());
-    validateSequence(methodThrowToBeInlined.iterateInstructions(),
+    assert (backend == Backend.DEX || backend == Backend.CF);
+    Predicate<InstructionSubject> nullCheck =
+        backend == Backend.DEX
+            ? InstructionSubject::isIfEqz
+            : insn -> ((CfInstructionSubject) insn).isIfNull();
+    validateSequence(
+        methodThrowToBeInlined.iterateInstructions(),
         // 'if' with "foo#1" is flipped.
-        InstructionSubject::isIfEqz,
+        nullCheck,
 
         // 'if' with "foo#2" is removed along with the constant.
 
         // 'if' with "foo#3" is removed so now we have unconditional call.
-        insn -> insn.isConstString("StringConstants::foo#3"),
+        insn -> insn.isConstString("StringConstants::foo#3", JumboStringMode.DISALLOW),
         InstructionSubject::isInvokeStatic,
         InstructionSubject::isThrow,
 
         // 'if's with "foo#4" and "foo#5" are flipped, but their throwing branches
         // are not moved to the end of the code (area for improvement?).
-        insn -> insn.isConstString("StringConstants::foo#4"),
-        InstructionSubject::isIfEqz, // Flipped if
+        insn -> insn.isConstString("StringConstants::foo#4", JumboStringMode.DISALLOW),
+        nullCheck, // Flipped if
         InstructionSubject::isGoto, // Jump around throwing branch.
         InstructionSubject::isInvokeStatic, // Throwing branch.
         InstructionSubject::isThrow,
-
-        insn -> insn.isConstString("StringConstants::foo#5"),
-        InstructionSubject::isIfEqz, // Flipped if
+        insn -> insn.isConstString("StringConstants::foo#5", JumboStringMode.DISALLOW),
+        nullCheck, // Flipped if
         InstructionSubject::isReturnVoid, // Final return statement.
         InstructionSubject::isInvokeStatic, // Throwing branch.
         InstructionSubject::isThrow,
 
         // After 'if' with "foo#1" flipped, always throwing branch
         // moved here along with the constant.
-        insn -> insn.isConstString("StringConstants::foo#1"),
+        insn -> insn.isConstString("StringConstants::foo#1", JumboStringMode.DISALLOW),
         InstructionSubject::isInvokeStatic,
-        InstructionSubject::isThrow
-    );
+        InstructionSubject::isThrow);
   }
 
   @SafeVarargs
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
new file mode 100644
index 0000000..2f59f6d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileContentsTest.java
@@ -0,0 +1,406 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DataDirectoryResource;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceConsumer;
+import com.android.tools.r8.DataResourceProvider.Visitor;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ArchiveResourceProvider;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class AdaptResourceFileContentsTest extends ProguardCompatibilityTestBase {
+
+  private Backend backend;
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public AdaptResourceFileContentsTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  protected static class CustomDataResourceConsumer implements DataResourceConsumer {
+
+    private final Map<String, ImmutableList<String>> resources = new HashMap<>();
+
+    @Override
+    public void accept(DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) {
+      throw new Unreachable();
+    }
+
+    @Override
+    public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) {
+      assertFalse(resources.containsKey(file.getName()));
+      try {
+        byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+        String contents = new String(bytes, Charset.defaultCharset());
+        resources.put(file.getName(), ImmutableList.copyOf(contents.split(System.lineSeparator())));
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    @Override
+    public void finished(DiagnosticsHandler handler) {}
+
+    public ImmutableList<String> get(String name) {
+      return resources.get(name);
+    }
+
+    public int size() {
+      return resources.size();
+    }
+  }
+
+  private static final ImmutableList<String> originalAllChangedResource =
+      ImmutableList.of(
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A<java.lang.String>",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A<"
+              + "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A>",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+          // Test property values are rewritten.
+          "property=com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A",
+          // Test XML content is rewritten.
+          "<tag>com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A</tag>",
+          "<tag attr=\"com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A\"></tag>",
+          // Test single-quote literals are rewritten.
+          "'com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A'");
+
+  private static final ImmutableList<String> originalAllPresentResource =
+      ImmutableList.of(
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B");
+
+  private static final ImmutableList<String> originalAllUnchangedResource =
+      ImmutableList.of(
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass",
+          // Test there is no renaming for the method on A.
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A.method",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$A.method()",
+          // Test there is no renaming for the methods on B.
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B.method",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B.method()",
+          // Test various prefixes.
+          "42com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+          "WithIdentifierPrefixcom.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+          "-com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+          "WithDashPrefix-com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+          "$com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+          "WithDollarPrefix$com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+          ".com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+          "WithDotPrefix.com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B",
+          // Test various suffixes.
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B42",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$BWithIdentifierSuffix",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B-",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B-WithDashSuffix",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B$",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B$WithDollarSuffix",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B.",
+          "com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B.WithDotSuffix");
+
+  private static String getProguardConfig(
+      boolean enableAdaptResourceFileContents, String adaptResourceFileContentsPathFilter) {
+    String adaptResourceFileContentsRule;
+    if (enableAdaptResourceFileContents) {
+      adaptResourceFileContentsRule = "-adaptresourcefilecontents";
+      if (adaptResourceFileContentsPathFilter != null) {
+        adaptResourceFileContentsRule += " " + adaptResourceFileContentsPathFilter;
+      }
+    } else {
+      adaptResourceFileContentsRule = "";
+    }
+    return String.join(
+        System.lineSeparator(),
+        adaptResourceFileContentsRule,
+        "-keep class " + AdaptResourceFileContentsTestClass.class.getName() + " {",
+        "  public static void main(...);",
+        "}");
+  }
+
+  private static String getProguardConfigWithNeverInline(
+      boolean enableAdaptResourceFileContents, String adaptResourceFileContentsPathFilter) {
+    return String.join(
+        System.lineSeparator(),
+        getProguardConfig(enableAdaptResourceFileContents, adaptResourceFileContentsPathFilter),
+        "-neverinline class com.android.tools.r8.naming.AdaptResourceFileContentsTestClass$B {",
+        "  public void method();",
+        "}");
+  }
+
+  @Test
+  public void testEnabled() throws Exception {
+    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    AndroidApp out =
+        compileWithR8(getProguardConfigWithNeverInline(true, null), dataResourceConsumer);
+
+    // Check that the data resources have changed as expected.
+    checkAllAreChanged(
+        dataResourceConsumer.get("resource-all-changed.md"), originalAllChangedResource);
+    checkAllAreChanged(
+        dataResourceConsumer.get("resource-all-changed.txt"), originalAllChangedResource);
+
+    // Check that the new names are consistent with the actual application code.
+    checkAllArePresent(
+        dataResourceConsumer.get("resource-all-present.txt"), new CodeInspector(out));
+
+    // Check that the data resources have not changed unexpectedly.
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-changed.class"), originalAllChangedResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-changed.cLaSs"), originalAllChangedResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-unchanged.txt"), originalAllUnchangedResource);
+  }
+
+  @Test
+  public void testProguardBehavior() throws Exception {
+    Path proguardedJar =
+        File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+    runProguard6Raw(
+        proguardedJar,
+        ImmutableList.of(
+            AdaptResourceFileContentsTestClass.class,
+            AdaptResourceFileContentsTestClass.A.class,
+            AdaptResourceFileContentsTestClass.B.class),
+        getProguardConfig(true, null),
+        null,
+        getDataResources()
+            .stream()
+            .filter(x -> !x.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION))
+            .collect(Collectors.toList()));
+
+    // Visit each of the resources in the jar and check that their contents are as expected.
+    Set<String> filenames = new HashSet<>();
+    ArchiveResourceProvider.fromArchive(proguardedJar, true)
+        .accept(
+            new Visitor() {
+              @Override
+              public void visit(DataDirectoryResource directory) {}
+
+              @Override
+              public void visit(DataEntryResource file) {
+                try {
+                  byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
+                  List<String> lines =
+                      Arrays.asList(
+                          new String(bytes, Charset.defaultCharset())
+                              .split(System.lineSeparator()));
+                  if (file.getName().endsWith("resource-all-changed.md")) {
+                    checkAllAreChanged(lines, originalAllChangedResource);
+                  } else if (file.getName().endsWith("resource-all-changed.txt")) {
+                    checkAllAreChanged(lines, originalAllChangedResource);
+                  } else if (file.getName().endsWith("resource-all-present.txt")) {
+                    checkAllArePresent(lines, new CodeInspector(readJar(proguardedJar)));
+                  } else if (file.getName().endsWith("resource-all-unchanged.txt")) {
+                    checkAllAreUnchanged(lines, originalAllUnchangedResource);
+                  }
+                } catch (Exception e) {
+                  throw new RuntimeException(e);
+                }
+
+                // Record that the jar contains a resource with this name.
+                filenames.add(file.getName());
+              }
+            });
+
+    // Check that the jar contains the four expected resources, and nothing else.
+    assertEquals(4, filenames.size());
+    assertTrue(filenames.stream().anyMatch(x -> x.endsWith("resource-all-changed.md")));
+    assertTrue(filenames.stream().anyMatch(x -> x.endsWith("resource-all-changed.txt")));
+    assertTrue(filenames.stream().anyMatch(x -> x.endsWith("resource-all-present.txt")));
+    assertTrue(filenames.stream().anyMatch(x -> x.endsWith("resource-all-unchanged.txt")));
+  }
+
+  @Test
+  public void testEnabledWithFilter() throws Exception {
+    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    compileWithR8(getProguardConfigWithNeverInline(true, "*.md"), dataResourceConsumer);
+
+    // Check that the file matching the filter has changed as expected.
+    checkAllAreChanged(
+        dataResourceConsumer.get("resource-all-changed.md"), originalAllChangedResource);
+
+    // Check that all the other data resources are unchanged.
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-changed.class"), originalAllChangedResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-changed.cLaSs"), originalAllChangedResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-changed.txt"), originalAllChangedResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-present.txt"), originalAllPresentResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-unchanged.txt"), originalAllUnchangedResource);
+  }
+
+  @Test
+  public void testDisabled() throws Exception {
+    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    compileWithR8(getProguardConfigWithNeverInline(false, null), dataResourceConsumer);
+
+    // Check that all data resources are unchanged.
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-changed.class"), originalAllChangedResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-changed.cLaSs"), originalAllChangedResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-changed.md"), originalAllChangedResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-changed.txt"), originalAllChangedResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-present.txt"), originalAllPresentResource);
+    checkAllAreUnchanged(
+        dataResourceConsumer.get("resource-all-unchanged.txt"), originalAllUnchangedResource);
+  }
+
+  private static void checkAllAreChanged(List<String> adaptedLines, List<String> originalLines) {
+    assertEquals(adaptedLines.size(), originalLines.size());
+    for (int i = 0; i < originalLines.size(); i++) {
+      assertNotEquals(originalLines.get(i), adaptedLines.get(i));
+    }
+  }
+
+  private static void checkAllArePresent(List<String> lines, CodeInspector inspector) {
+    for (String line : lines) {
+      assertThat(inspector.clazz(line), isPresent());
+    }
+  }
+
+  private static void checkAllAreUnchanged(List<String> adaptedLines, List<String> originalLines) {
+    assertEquals(adaptedLines.size(), originalLines.size());
+    for (int i = 0; i < originalLines.size(); i++) {
+      assertEquals(originalLines.get(i), adaptedLines.get(i));
+    }
+  }
+
+  private AndroidApp compileWithR8(String proguardConfig, DataResourceConsumer dataResourceConsumer)
+      throws CompilationFailedException, IOException {
+    assert backend == Backend.DEX || backend == Backend.CF;
+    R8Command command =
+        ToolHelper.allowTestProguardOptions(
+                ToolHelper.prepareR8CommandBuilder(
+                        getAndroidApp(),
+                        backend == Backend.DEX
+                            ? DexIndexedConsumer.emptyConsumer()
+                            : ClassFileConsumer.emptyConsumer())
+                    .addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown()))
+            .addLibraryFiles(
+                backend == Backend.DEX
+                    ? ToolHelper.getDefaultAndroidJar()
+                    : ToolHelper.getJava8RuntimeJar())
+            .build();
+    return ToolHelper.runR8(
+        command,
+        options -> {
+          // TODO(christofferqa): Class inliner should respect -neverinline.
+          options.enableClassInlining = false;
+          options.enableClassMerging = true;
+          options.dataResourceConsumer = dataResourceConsumer;
+        });
+  }
+
+  private AndroidApp getAndroidApp() throws IOException {
+    AndroidApp.Builder builder = AndroidApp.builder();
+    builder.addProgramFiles(
+        ToolHelper.getClassFileForTestClass(AdaptResourceFileContentsTestClass.class),
+        ToolHelper.getClassFileForTestClass(AdaptResourceFileContentsTestClass.A.class),
+        ToolHelper.getClassFileForTestClass(AdaptResourceFileContentsTestClass.B.class));
+    getDataResources().forEach(builder::addDataResource);
+    return builder.build();
+  }
+
+  private List<DataEntryResource> getDataResources() {
+    return ImmutableList.of(
+        DataEntryResource.fromBytes(
+            String.join(System.lineSeparator(), originalAllChangedResource).getBytes(),
+            "resource-all-changed.class",
+            Origin.unknown()),
+        DataEntryResource.fromBytes(
+            String.join(System.lineSeparator(), originalAllChangedResource).getBytes(),
+            "resource-all-changed.cLaSs",
+            Origin.unknown()),
+        DataEntryResource.fromBytes(
+            String.join(System.lineSeparator(), originalAllChangedResource).getBytes(),
+            "resource-all-changed.md",
+            Origin.unknown()),
+        DataEntryResource.fromBytes(
+            String.join(System.lineSeparator(), originalAllChangedResource).getBytes(),
+            "resource-all-changed.txt",
+            Origin.unknown()),
+        DataEntryResource.fromBytes(
+            String.join(System.lineSeparator(), originalAllPresentResource).getBytes(),
+            "resource-all-present.txt",
+            Origin.unknown()),
+        DataEntryResource.fromBytes(
+            String.join(System.lineSeparator(), originalAllUnchangedResource).getBytes(),
+            "resource-all-unchanged.txt",
+            Origin.unknown()));
+  }
+}
+
+class AdaptResourceFileContentsTestClass {
+
+  public static void main(String[] args) {
+    B obj = new B();
+    obj.method();
+  }
+
+  static class A {
+
+    public void method() {
+      System.out.println("In A.method()");
+    }
+  }
+
+  static class B extends A {
+
+    public void method() {
+      System.out.println("In B.method()");
+      super.method();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
new file mode 100644
index 0000000..51201df
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/AdaptResourceFileNamesTest.java
@@ -0,0 +1,534 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DataDirectoryResource;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DataResourceConsumer;
+import com.android.tools.r8.DataResourceProvider.Visitor;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringConsumer;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.naming.AdaptResourceFileContentsTest.CustomDataResourceConsumer;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ArchiveResourceProvider;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.KeepingDiagnosticHandler;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class AdaptResourceFileNamesTest extends ProguardCompatibilityTestBase {
+
+  private Backend backend;
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public AdaptResourceFileNamesTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  private static final Path CF_DIR =
+      Paths.get(ToolHelper.EXAMPLES_CF_DIR).resolve("adaptresourcefilenames");
+  private static final Path TEST_JAR =
+      Paths.get(ToolHelper.EXAMPLES_BUILD_DIR)
+          .resolve("adaptresourcefilenames" + FileUtils.JAR_EXTENSION);
+
+  private KeepingDiagnosticHandler diagnosticsHandler;
+  private ClassNameMapper mapper = null;
+
+  @Before
+  public void reset() {
+    diagnosticsHandler = new KeepingDiagnosticHandler();
+    mapper = null;
+  }
+
+  private static String getProguardConfig(
+      boolean enableAdaptResourceFileNames, String adaptResourceFileNamesPathFilter) {
+    String adaptResourceFilenamesRule;
+    if (enableAdaptResourceFileNames) {
+      adaptResourceFilenamesRule = "-adaptresourcefilenames";
+      if (adaptResourceFileNamesPathFilter != null) {
+        adaptResourceFilenamesRule += " " + adaptResourceFileNamesPathFilter;
+      }
+    } else {
+      adaptResourceFilenamesRule = "";
+    }
+    return String.join(
+        System.lineSeparator(),
+        adaptResourceFilenamesRule,
+        "-keep class " + adaptresourcefilenames.TestClass.class.getName() + " {",
+        "  public static void main(...);",
+        "}");
+  }
+
+  private static String getProguardConfigWithNeverInline(
+      boolean enableAdaptResourceFileNames, String adaptResourceFileNamesPathFilter) {
+    return String.join(
+        System.lineSeparator(),
+        getProguardConfig(enableAdaptResourceFileNames, adaptResourceFileNamesPathFilter),
+        "-neverinline class " + adaptresourcefilenames.A.class.getName() + " {",
+        "  public void method();",
+        "}",
+        "-neverinline class " + adaptresourcefilenames.B.Inner.class.getName() + " {",
+        "  public void method();",
+        "}");
+  }
+
+  @Test
+  public void testEnabled() throws Exception {
+    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    compileWithR8(
+        getProguardConfigWithNeverInline(true, null), dataResourceConsumer, this::checkR8Renamings);
+    // Check that the generated resources have the expected names.
+    for (DataEntryResource dataResource : getOriginalDataResources()) {
+      assertNotNull(
+          "Resource not renamed as expected: " + dataResource.getName(),
+          dataResourceConsumer.get(getExpectedRenamingFor(dataResource.getName(), mapper)));
+    }
+  }
+
+  @Test
+  public void testEnabledWithFilter() throws Exception {
+    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    compileWithR8(
+        getProguardConfigWithNeverInline(true, "**.md"),
+        dataResourceConsumer,
+        this::checkR8Renamings);
+    // Check that the generated resources have the expected names.
+    Map<String, String> expectedRenamings =
+        ImmutableMap.of("adaptresourcefilenames/B.md", "adaptresourcefilenames/b.md");
+    for (DataEntryResource dataResource : getOriginalDataResources()) {
+      assertNotNull(
+          "Resource not renamed as expected: " + dataResource.getName(),
+          dataResourceConsumer.get(
+              expectedRenamings.getOrDefault(dataResource.getName(), dataResource.getName())));
+    }
+  }
+
+  @Test
+  public void testDisabled() throws Exception {
+    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    compileWithR8(getProguardConfigWithNeverInline(false, null), dataResourceConsumer);
+    // Check that none of the resources were renamed.
+    for (DataEntryResource dataResource : getOriginalDataResources()) {
+      assertNotNull(
+          "Resource not renamed as expected: " + dataResource.getName(),
+          dataResourceConsumer.get(dataResource.getName()));
+    }
+  }
+
+  @Test
+  public void testCollisionBehavior() throws Exception {
+    CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer();
+    compileWithR8(
+        getProguardConfigWithNeverInline(true, null),
+        dataResourceConsumer,
+        this::checkR8Renamings,
+        ImmutableList.<DataEntryResource>builder()
+            .addAll(getOriginalDataResources())
+            .add(
+                DataEntryResource.fromBytes(
+                    new byte[0], "adaptresourcefilenames/b.txt", Origin.unknown()))
+            .build());
+    assertEquals(1, diagnosticsHandler.warnings.size());
+    assertThat(
+        diagnosticsHandler.warnings.get(0).getDiagnosticMessage(),
+        containsString("Resource 'adaptresourcefilenames/b.txt' already exists."));
+    assertEquals(getOriginalDataResources().size(), dataResourceConsumer.size());
+  }
+
+  @Test
+  public void testProguardBehavior() throws Exception {
+    Path inputJar = addDataResourcesToExistingJar(TEST_JAR, getOriginalDataResources());
+    Path proguardedJar =
+        File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+    Path proguardMapFile = File.createTempFile("mapping", ".txt", temp.getRoot()).toPath();
+    runProguard6Raw(proguardedJar, inputJar, getProguardConfig(true, null), proguardMapFile);
+    // Extract the names of the generated resources.
+    Set<String> filenames = new HashSet<>();
+    ArchiveResourceProvider.fromArchive(proguardedJar, true)
+        .accept(
+            new Visitor() {
+              @Override
+              public void visit(DataDirectoryResource directory) {}
+
+              @Override
+              public void visit(DataEntryResource file) {
+                filenames.add(file.getName());
+              }
+            });
+    // Check that the generated resources have the expected names.
+    ClassNameMapper mapper = ClassNameMapper.mapperFromFile(proguardMapFile);
+    for (DataEntryResource dataResource : getOriginalDataResources()) {
+      String expectedName = getExpectedRenamingFor(dataResource.getName(), mapper);
+      assertTrue(
+          "Resource not renamed to '" + expectedName + "' as expected: " + dataResource.getName(),
+          filenames.contains(expectedName));
+    }
+  }
+
+  @Test
+  public void testProguardCollisionBehavior() throws Exception {
+    List<DataEntryResource> originalDataResources = getOriginalDataResources();
+    Path inputJar =
+        addDataResourcesToExistingJar(
+            TEST_JAR,
+            ImmutableList.<DataEntryResource>builder()
+                .addAll(originalDataResources)
+                .add(
+                    DataEntryResource.fromBytes(
+                        new byte[0], "adaptresourcefilenames/b.txt", Origin.unknown()))
+                .build());
+    Path proguardedJar =
+        File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+    runProguard6Raw(
+        proguardedJar,
+        inputJar,
+        getProguardConfig(true, null),
+        null,
+        processResult -> {
+          assertEquals(0, processResult.exitCode);
+          assertThat(
+              processResult.stderr,
+              containsString(
+                  "Warning: can't write resource [adaptresourcefilenames/b.txt] "
+                      + "(Duplicate jar entry [adaptresourcefilenames/b.txt])"));
+        });
+    assertEquals(
+        originalDataResources.size(),
+        getDataResources(ArchiveResourceProvider.fromArchive(proguardedJar, true))
+            .stream()
+            .filter(dataResource -> !dataResource.getName().equals("META-INF/MANIFEST.MF"))
+            .count());
+  }
+
+  private AndroidApp compileWithR8(String proguardConfig, DataResourceConsumer dataResourceConsumer)
+      throws CompilationFailedException, IOException {
+    return compileWithR8(proguardConfig, dataResourceConsumer, null);
+  }
+
+  private AndroidApp compileWithR8(
+      String proguardConfig,
+      DataResourceConsumer dataResourceConsumer,
+      StringConsumer proguardMapConsumer)
+      throws CompilationFailedException, IOException {
+    return compileWithR8(
+        proguardConfig, dataResourceConsumer, proguardMapConsumer, getOriginalDataResources());
+  }
+
+  private AndroidApp compileWithR8(
+      String proguardConfig,
+      DataResourceConsumer dataResourceConsumer,
+      StringConsumer proguardMapConsumer,
+      List<DataEntryResource> dataResources)
+      throws CompilationFailedException, IOException {
+    assert backend == Backend.DEX || backend == Backend.CF;
+    R8Command command =
+        ToolHelper.allowTestProguardOptions(
+                ToolHelper.prepareR8CommandBuilder(
+                        getAndroidApp(dataResources),
+                        backend == Backend.DEX
+                            ? DexIndexedConsumer.emptyConsumer()
+                            : ClassFileConsumer.emptyConsumer(),
+                        diagnosticsHandler)
+                    .addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown()))
+            .addLibraryFiles(
+                backend == Backend.DEX
+                    ? ToolHelper.getDefaultAndroidJar()
+                    : ToolHelper.getJava8RuntimeJar())
+            .build();
+    return ToolHelper.runR8(
+        command,
+        options -> {
+          // TODO(christofferqa): Class inliner should respect -neverinline.
+          options.enableClassInlining = false;
+          options.enableClassMerging = true;
+          options.dataResourceConsumer = dataResourceConsumer;
+          options.proguardMapConsumer = proguardMapConsumer;
+          options.testing.suppressExperimentalCfBackendWarning = true;
+        });
+  }
+
+  private void checkR8Renamings(String proguardMap, DiagnosticsHandler handler) {
+    try {
+      // Check that the renamings are as expected. These exact renamings are not important as
+      // such, but the test expectations rely on them.
+      mapper = ClassNameMapper.mapperFromString(proguardMap);
+      assertEquals(
+          "adaptresourcefilenames.TestClass",
+          mapper.deobfuscateClassName("adaptresourcefilenames.TestClass"));
+      assertEquals(
+          "adaptresourcefilenames.B", mapper.deobfuscateClassName("adaptresourcefilenames.b"));
+      assertEquals(
+          "adaptresourcefilenames.B$Inner",
+          mapper.deobfuscateClassName("adaptresourcefilenames.a"));
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private AndroidApp getAndroidApp(List<DataEntryResource> dataResources) throws IOException {
+    AndroidApp.Builder builder = AndroidApp.builder();
+    builder.addProgramFiles(ToolHelper.getClassFilesForTestDirectory(CF_DIR));
+    dataResources.forEach(builder::addDataResource);
+    return builder.build();
+  }
+
+  private static List<DataEntryResource> getOriginalDataResources() {
+    List<String> filenames =
+        ImmutableList.of(
+            // Filename with simple name in root directory.
+            "TestClass",
+            "B",
+            // Filename with qualified name in root directory.
+            "adaptresourcefilenames.TestClass",
+            "adaptresourcefilenames.B",
+            // Filename with qualified directory name in root directory.
+            "adaptresourcefilenames/TestClass",
+            "adaptresourcefilenames/B",
+            // Filename with simple name in sub directory.
+            "foo/bar/baz/TestClass",
+            "foo/bar/baz/B",
+            // Filename with qualified name in sub directory.
+            "foo/bar/baz/adaptresourcefiles.TestClass",
+            "foo/bar/baz/adaptresourcefiles.B",
+            // Filename with qualified directory name in sub directory.
+            "foo/bar/baz/adaptresourcefilenames/TestClass",
+            "foo/bar/baz/adaptresourcefilenames/B",
+            //
+            // SUFFIX VARIANTS:
+            //
+            // Filename with simple name and extension in root directory.
+            "TestClass.txt",
+            "B.txt",
+            // Filename with qualified name and extension in root directory.
+            "adaptresourcefilenames.TestClass.txt",
+            "adaptresourcefilenames.B.txt",
+            // Filename with qualified directory name and extension in root directory.
+            "adaptresourcefilenames/TestClass.txt",
+            "adaptresourcefilenames/B.txt",
+            // Filename with simple name and extension in sub directory.
+            "foo/bar/baz/TestClass.txt",
+            "foo/bar/baz/B.txt",
+            // Filename with qualified name and extension in sub directory.
+            "foo/bar/baz/adaptresourcefiles.TestClass.txt",
+            "foo/bar/baz/adaptresourcefiles.B.txt",
+            // Filename with qualified directory name and extension in sub directory.
+            "foo/bar/baz/adaptresourcefilenames/TestClass.txt",
+            "foo/bar/baz/adaptresourcefilenames/B.txt",
+            // Filename with other extension (used to test filtering).
+            "adaptresourcefilenames/TestClass.md",
+            "adaptresourcefilenames/B.md",
+            // Filename with dot suffix only.
+            "adaptresourcefilenames/TestClass.",
+            "adaptresourcefilenames/B.",
+            // Filename with dot suffix and extension.
+            "adaptresourcefilenames/TestClass.suffix.txt",
+            "adaptresourcefilenames/B.suffix.txt",
+            // Filename with dash suffix and extension.
+            "adaptresourcefilenames/TestClass-suffix.txt",
+            "adaptresourcefilenames/B-suffix.txt",
+            // Filename with dollar suffix and extension.
+            "adaptresourcefilenames/TestClass$suffix.txt",
+            "adaptresourcefilenames/B$suffix.txt",
+            // Filename with dollar suffix matching inner class and extension.
+            "adaptresourcefilenames/TestClass$Inner.txt",
+            "adaptresourcefilenames/B$Inner.txt",
+            // Filename with underscore suffix and extension.
+            "adaptresourcefilenames/TestClass_suffix.txt",
+            "adaptresourcefilenames/B_suffix.txt",
+            // Filename with whitespace suffix and extension.
+            "adaptresourcefilenames/TestClass suffix.txt",
+            "adaptresourcefilenames/B suffix.txt",
+            // Filename with identifier suffix and extension.
+            "adaptresourcefilenames/TestClasssuffix.txt",
+            "adaptresourcefilenames/Bsuffix.txt",
+            // Filename with numeric suffix and extension.
+            "adaptresourcefilenames/TestClass42.txt",
+            "adaptresourcefilenames/B42.txt",
+            //
+            // PREFIX VARIANTS:
+            //
+            // Filename with dot prefix and extension.
+            "adaptresourcefilenames/prefix.TestClass.txt",
+            "adaptresourcefilenames/prefix.B.txt",
+            // Filename with dash prefix and extension.
+            "adaptresourcefilenames/prefix-TestClass.txt",
+            "adaptresourcefilenames/prefix-B.txt",
+            // Filename with dollar prefix and extension.
+            "adaptresourcefilenames/prefix$TestClass.txt",
+            "adaptresourcefilenames/prefix$B.txt",
+            // Filename with identifier prefix and extension.
+            "adaptresourcefilenames/prefixTestClass.txt",
+            "adaptresourcefilenames/prefixB.txt",
+            // Filename with numeric prefix and extension.
+            "adaptresourcefilenames/42TestClass.txt",
+            "adaptresourcefilenames/42B.txt",
+            //
+            // PACKAGE RENAMING TESTS:
+            //
+            // Filename that matches a type, but only the directory should be renamed.
+            "adaptresourcefilenames/pkg/C",
+            // Filename that matches a type that should be renamed.
+            "adaptresourcefilenames/pkg/C.txt",
+            // Filename that does not match a type, but where the directory should be renamed.
+            "adaptresourcefilenames/pkg/file.txt",
+            // Filename that does not match a type, but where a directory-prefix should be renamed.
+            "adaptresourcefilenames/pkg/directory/file.txt",
+            // Filename that matches a type, but only the directory should be renamed.
+            "adaptresourcefilenames/pkg/innerpkg/D",
+            // Filename that matches a type that should be renamed.
+            "adaptresourcefilenames/pkg/innerpkg/D.txt",
+            // Filename that does not match a type, but where the directory should be renamed.
+            "adaptresourcefilenames/pkg/innerpkg/file.txt",
+            // Filename that does not match a type, but where a directory-prefix should be renamed.
+            "adaptresourcefilenames/pkg/innerpkg/directory/file.txt"
+            );
+    return filenames
+        .stream()
+        .map(filename -> DataEntryResource.fromBytes(new byte[0], filename, Origin.unknown()))
+        .collect(Collectors.toList());
+  }
+
+  private static String getExpectedRenamingFor(String filename, ClassNameMapper mapper) {
+    String typeName = null;
+    String suffix = null;
+    switch (filename) {
+        // Filename with dot only.
+      case "adaptresourcefilenames/B.":
+        typeName = "adaptresourcefilenames.B";
+        suffix = ".";
+        break;
+        // Filename with extension.
+      case "adaptresourcefilenames/B.txt":
+        typeName = "adaptresourcefilenames.B";
+        suffix = ".txt";
+        break;
+        // Filename with other extension (used to test filtering).
+      case "adaptresourcefilenames/B.md":
+        typeName = "adaptresourcefilenames.B";
+        suffix = ".md";
+        break;
+        // Filename with dot suffix and extension.
+      case "adaptresourcefilenames/B.suffix.txt":
+        typeName = "adaptresourcefilenames.B";
+        suffix = ".suffix.txt";
+        break;
+        // Filename with dash suffix and extension.
+      case "adaptresourcefilenames/B-suffix.txt":
+        typeName = "adaptresourcefilenames.B";
+        suffix = "-suffix.txt";
+        break;
+        // Filename with dollar suffix and extension.
+      case "adaptresourcefilenames/B$suffix.txt":
+        typeName = "adaptresourcefilenames.B";
+        suffix = "$suffix.txt";
+        break;
+        // Filename with dollar suffix matching inner class and extension.
+      case "adaptresourcefilenames/B$Inner.txt":
+        typeName = "adaptresourcefilenames.B$Inner";
+        suffix = ".txt";
+        break;
+        // Filename with underscore suffix and extension.
+      case "adaptresourcefilenames/B_suffix.txt":
+        typeName = "adaptresourcefilenames.B";
+        suffix = "_suffix.txt";
+        break;
+        // Filename with whitespace suffix and extension.
+      case "adaptresourcefilenames/B suffix.txt":
+        typeName = "adaptresourcefilenames.B";
+        suffix = " suffix.txt";
+        break;
+        //
+        // PACKAGE RENAMING TESTS
+        //
+      case "adaptresourcefilenames/pkg/C.txt":
+        typeName = "adaptresourcefilenames.pkg.C";
+        suffix = ".txt";
+        break;
+      case "adaptresourcefilenames/pkg/innerpkg/D.txt":
+        typeName = "adaptresourcefilenames.pkg.innerpkg.D";
+        suffix = ".txt";
+        break;
+    }
+    if (typeName != null) {
+      String renamedName = mapper.getObfuscatedToOriginalMapping().inverse().get(typeName);
+      assertNotNull(renamedName);
+      assertNotEquals(typeName, renamedName);
+      return renamedName.replace('.', '/') + suffix;
+    }
+    // Renamings for files in directories that match packages that have been renamed,
+    // but where the filename itself should not be renamed.
+    String samePackageAsType = null;
+    switch (filename) {
+      case "adaptresourcefilenames/pkg/C":
+        samePackageAsType = "adaptresourcefilenames.pkg.C";
+        suffix = "C";
+        break;
+      case "adaptresourcefilenames/pkg/file.txt":
+        samePackageAsType = "adaptresourcefilenames.pkg.C";
+        suffix = "file.txt";
+        break;
+      case "adaptresourcefilenames/pkg/directory/file.txt":
+        samePackageAsType = "adaptresourcefilenames.pkg.C";
+        suffix = "directory/file.txt";
+        break;
+      case "adaptresourcefilenames/pkg/innerpkg/D":
+        samePackageAsType = "adaptresourcefilenames.pkg.innerpkg.D";
+        suffix = "D";
+        break;
+      case "adaptresourcefilenames/pkg/innerpkg/file.txt":
+        samePackageAsType = "adaptresourcefilenames.pkg.innerpkg.D";
+        suffix = "file.txt";
+        break;
+      case "adaptresourcefilenames/pkg/innerpkg/directory/file.txt":
+        samePackageAsType = "adaptresourcefilenames.pkg.innerpkg.D";
+        suffix = "directory/file.txt";
+        break;
+    }
+    if (samePackageAsType != null) {
+      String renamedName = mapper.getObfuscatedToOriginalMapping().inverse().get(samePackageAsType);
+      assertNotNull(renamedName);
+      assertNotEquals(samePackageAsType, renamedName);
+      if (renamedName.contains(".")) {
+        String renamedPackageName = renamedName.substring(0, renamedName.lastIndexOf('.'));
+        return renamedPackageName.replace('.', '/') + "/" + suffix;
+      }
+    }
+    return filename;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
index bf6c895..aaecd58 100644
--- a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -15,25 +16,30 @@
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.code.NewInstance;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
-import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.NewInstanceInstructionSubject;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.concurrent.ExecutionException;
 import org.junit.Before;
 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 ApplyMappingTest extends TestBase {
 
   private static final String MAPPING = "test-mapping.txt";
@@ -52,9 +58,20 @@
 
   private Path out;
 
+  private Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public ApplyMappingTest(Backend backend) {
+    this.backend = backend;
+  }
+
   @Before
   public void setup() throws IOException {
-    out = temp.newFolder("outdex").toPath();
+    out = temp.newFolder("out").toPath();
   }
 
   @Test
@@ -97,8 +114,8 @@
                     pgConfig -> pgConfig.setApplyMappingFile(proguardMap))
                 .build());
 
-    DexInspector inspector = new DexInspector(instrApp);
-    MethodSubject main = inspector.clazz("applymapping044.Main").method(DexInspector.MAIN);
+    CodeInspector inspector = createDexInspector(instrApp);
+    MethodSubject main = inspector.clazz("applymapping044.Main").method(CodeInspector.MAIN);
     Iterator<InvokeInstructionSubject> iterator =
         main.iterateInstructions(InstructionSubject::isInvoke);
     // B#m()
@@ -152,8 +169,8 @@
                 .build());
 
     // Make sure the given proguard map is indeed applied.
-    DexInspector inspector = new DexInspector(outputApp);
-    MethodSubject main = inspector.clazz("applymapping044.Main").method(DexInspector.MAIN);
+    CodeInspector inspector = createDexInspector(outputApp);
+    MethodSubject main = inspector.clazz("applymapping044.Main").method(CodeInspector.MAIN);
     Iterator<InvokeInstructionSubject> iterator =
         main.iterateInstructions(InstructionSubject::isInvoke);
     // B#m() -> y#n()
@@ -194,6 +211,15 @@
     assertEquals("p", original_f.invokedMethod().name.toString());
   }
 
+  private static CodeInspector createDexInspector(AndroidApp outputApp)
+      throws IOException, ExecutionException {
+    return new CodeInspector(
+        outputApp,
+        o -> {
+          o.enableCfFrontend = true;
+        });
+  }
+
   @Test
   public void test_naming001_rule105() throws Exception {
     // keep rules to reserve D and E, along with a proguard map.
@@ -210,8 +236,8 @@
                 .build());
 
     // Make sure the given proguard map is indeed applied.
-    DexInspector inspector = new DexInspector(outputApp);
-    MethodSubject main = inspector.clazz("naming001.D").method(DexInspector.MAIN);
+    CodeInspector inspector = createDexInspector(outputApp);
+    MethodSubject main = inspector.clazz("naming001.D").method(CodeInspector.MAIN);
     Iterator<InvokeInstructionSubject> iterator =
         main.iterateInstructions(InstructionSubject::isInvoke);
     // mapping-105 simply includes: naming001.D#keep -> peek
@@ -242,13 +268,20 @@
                 .build());
 
     // Make sure the given proguard map is indeed applied.
-    DexInspector inspector = new DexInspector(outputApp);
-    MethodSubject main = inspector.clazz("naming001.D").method(DexInspector.MAIN);
+    CodeInspector inspector = createDexInspector(outputApp);
+    MethodSubject main = inspector.clazz("naming001.D").method(CodeInspector.MAIN);
 
+    Iterator<InstructionSubject> iterator = main.iterateInstructions();
     // naming001.E is renamed to a.a, so first instruction must be: new-instance La/a;
-    Instruction[] instructions = main.getMethod().getCode().asDexCode().instructions;
-    assertTrue(instructions[0] instanceof NewInstance);
-    NewInstance newInstance = (NewInstance) instructions[0];
+    NewInstanceInstructionSubject newInstance = null;
+    while (iterator.hasNext()) {
+      InstructionSubject instruction = iterator.next();
+      if (instruction.isNewInstance()) {
+        newInstance = (NewInstanceInstructionSubject) instruction;
+        break;
+      }
+    }
+    assertNotNull(newInstance);
     assertEquals( "La/a;", newInstance.getType().toSmaliString());
   }
 
@@ -267,26 +300,35 @@
   private R8Command.Builder getCommandForInstrumentation(
       Path out, Path flag, Path mainApp, Path instrApp) throws IOException {
     return R8Command.builder()
-        .addLibraryFiles(ToolHelper.getDefaultAndroidJar(), mainApp)
+        .addLibraryFiles(
+            backend == Backend.DEX
+                ? ToolHelper.getDefaultAndroidJar()
+                : ToolHelper.getJava8RuntimeJar(),
+            mainApp)
         .addProgramFiles(instrApp)
-        .setOutput(out, OutputMode.DexIndexed)
+        .setOutput(out, backend == Backend.DEX ? OutputMode.DexIndexed : OutputMode.ClassFile)
         .addProguardConfigurationFiles(flag);
   }
 
   private R8Command.Builder getCommandForApps(Path out, Path flag, Path... jars)
       throws IOException {
     return R8Command.builder()
-        .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+        .addLibraryFiles(
+            backend == Backend.DEX
+                ? ToolHelper.getDefaultAndroidJar()
+                : ToolHelper.getJava8RuntimeJar())
         .addProgramFiles(jars)
-        .setOutput(out, OutputMode.DexIndexed)
+        .setOutput(out, backend == Backend.DEX ? OutputMode.DexIndexed : OutputMode.ClassFile)
         .addProguardConfigurationFiles(flag);
   }
 
   private static AndroidApp runR8(R8Command command)
       throws ProguardRuleParserException, ExecutionException, IOException {
-    return ToolHelper.runR8(command, options -> {
-      // Disable inlining to make this test not depend on inlining decisions.
-      options.enableInlining = false;
-    });
+    return ToolHelper.runR8(
+        command,
+        options -> {
+          // Disable inlining to make this test not depend on inlining decisions.
+          options.enableInlining = false;
+        });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index f27c88a..73cdaaf 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -6,28 +6,29 @@
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
 import static com.android.tools.r8.utils.DescriptorUtils.isValidJavaType;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.code.ConstString;
-import com.android.tools.r8.code.ConstStringJumbo;
-import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ConstStringInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+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 com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
@@ -37,9 +38,7 @@
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
@@ -49,15 +48,20 @@
 
   private final String appFileName;
   private final List<String> keepRulesFiles;
-  private final Consumer<DexInspector> inspection;
+  private final Consumer<CodeInspector> inspection;
+  private final Backend backend;
 
   public IdentifierMinifierTest(
+      Backend backend,
       String test,
       List<String> keepRulesFiles,
-      Consumer<DexInspector> inspection) {
-    this.appFileName = ToolHelper.EXAMPLES_BUILD_DIR + test + "/classes.dex";
+      Consumer<CodeInspector> inspection) {
+    assert backend == Backend.DEX || backend == Backend.CF;
+    this.appFileName =
+        ToolHelper.EXAMPLES_BUILD_DIR + test + (backend == Backend.DEX ? "/classes.dex" : ".jar");
     this.keepRulesFiles = keepRulesFiles;
     this.inspection = inspection;
+    this.backend = backend;
   }
 
   private AndroidApp processedApp;
@@ -67,25 +71,34 @@
     Path out = temp.getRoot().toPath();
     R8Command.Builder builder =
         ToolHelper.addProguardConfigurationConsumer(
-            R8Command.builder(),
-            pgConfig -> {
-              pgConfig.setPrintMapping(true);
-              pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
-            })
-            .setOutput(out, OutputMode.DexIndexed)
-            .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
-            .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+                R8Command.builder(),
+                pgConfig -> {
+                  pgConfig.setPrintMapping(true);
+                  pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
+                })
+            .setOutput(out, backend == Backend.DEX ? OutputMode.DexIndexed : OutputMode.ClassFile)
+            .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get));
+    if (backend == Backend.DEX) {
+      builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+    } else if (backend == Backend.CF) {
+      builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+    }
     ToolHelper.getAppBuilder(builder).addProgramFiles(Paths.get(appFileName));
     processedApp = ToolHelper.runR8(builder.build(), o -> o.debug = false);
   }
 
   @Test
   public void identiferMinifierTest() throws Exception {
-    DexInspector dexInspector = new DexInspector(processedApp);
-    inspection.accept(dexInspector);
+    CodeInspector codeInspector =
+        new CodeInspector(
+            processedApp,
+            options -> {
+              options.enableCfFrontend = true;
+            });
+    inspection.accept(codeInspector);
   }
 
-  @Parameters(name = "test: {0} keep: {1}")
+  @Parameters(name = "[{0}] test: {1} keep: {2}")
   public static Collection<Object[]> data() {
     List<String> tests = Arrays.asList(
         "adaptclassstrings",
@@ -94,7 +107,7 @@
         "getmembers",
         "identifiernamestring");
 
-    Map<String, Consumer<DexInspector>> inspections = new HashMap<>();
+    Map<String, Consumer<CodeInspector>> inspections = new HashMap<>();
     inspections.put("adaptclassstrings:keep-rules-1.txt", IdentifierMinifierTest::test1_rule1);
     inspections.put("adaptclassstrings:keep-rules-2.txt", IdentifierMinifierTest::test1_rule2);
     inspections.put(
@@ -105,25 +118,38 @@
     inspections.put("identifiernamestring:keep-rules-2.txt", IdentifierMinifierTest::test2_rule2);
     inspections.put("identifiernamestring:keep-rules-3.txt", IdentifierMinifierTest::test2_rule3);
 
-    return NamingTestBase.createTests(tests, inspections);
+    Collection<Object[]> parameters = NamingTestBase.createTests(tests, inspections);
+
+    // Duplicate parameters for each backend.
+    List<Object[]> parametersWithBackend = new ArrayList<>();
+    for (Backend backend : Backend.values()) {
+      for (Object[] row : parameters) {
+        Object[] newRow = new Object[row.length + 1];
+        newRow[0] = backend;
+        System.arraycopy(row, 0, newRow, 1, row.length);
+        parametersWithBackend.add(newRow);
+      }
+    }
+
+    return parametersWithBackend;
   }
 
   // Without -adaptclassstrings
-  private static void test1_rule1(DexInspector inspector) {
+  private static void test1_rule1(CodeInspector inspector) {
     ClassSubject mainClass = inspector.clazz("adaptclassstrings.Main");
-    MethodSubject main = mainClass.method(DexInspector.MAIN);
-    Code mainCode = main.getMethod().getCode();
-    verifyPresenceOfConstString(mainCode.asDexCode().instructions);
-    int renamedYetFoundIdentifierCount =
-        countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
-    assertEquals(0, renamedYetFoundIdentifierCount);
+    MethodSubject main = mainClass.method(CodeInspector.MAIN);
+    assertTrue(main instanceof FoundMethodSubject);
+    FoundMethodSubject foundMain = (FoundMethodSubject) main;
+    verifyPresenceOfConstString(foundMain);
+    int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundMain);
+    assertEquals(4, renamedYetFoundIdentifierCount);
 
     ClassSubject aClass = inspector.clazz("adaptclassstrings.A");
     MethodSubject bar = aClass.method("void", "bar", ImmutableList.of());
-    Code barCode = bar.getMethod().getCode();
-    verifyPresenceOfConstString(barCode.asDexCode().instructions);
-    renamedYetFoundIdentifierCount =
-        countRenamedClassIdentifier(inspector, barCode.asDexCode().instructions);
+    assertTrue(bar instanceof FoundMethodSubject);
+    FoundMethodSubject foundBar = (FoundMethodSubject) bar;
+    verifyPresenceOfConstString(foundBar);
+    renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundBar);
     assertEquals(0, renamedYetFoundIdentifierCount);
 
     renamedYetFoundIdentifierCount =
@@ -132,21 +158,21 @@
   }
 
   // With -adaptclassstrings *.*A
-  private static void test1_rule2(DexInspector inspector) {
+  private static void test1_rule2(CodeInspector inspector) {
     ClassSubject mainClass = inspector.clazz("adaptclassstrings.Main");
-    MethodSubject main = mainClass.method(DexInspector.MAIN);
-    Code mainCode = main.getMethod().getCode();
-    verifyPresenceOfConstString(mainCode.asDexCode().instructions);
-    int renamedYetFoundIdentifierCount =
-        countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
-    assertEquals(0, renamedYetFoundIdentifierCount);
+    MethodSubject main = mainClass.method(CodeInspector.MAIN);
+    assertTrue(main instanceof FoundMethodSubject);
+    FoundMethodSubject foundMain = (FoundMethodSubject) main;
+    verifyPresenceOfConstString(foundMain);
+    int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundMain);
+    assertEquals(4, renamedYetFoundIdentifierCount);
 
     ClassSubject aClass = inspector.clazz("adaptclassstrings.A");
     MethodSubject bar = aClass.method("void", "bar", ImmutableList.of());
-    Code barCode = bar.getMethod().getCode();
-    verifyPresenceOfConstString(barCode.asDexCode().instructions);
-    renamedYetFoundIdentifierCount =
-        countRenamedClassIdentifier(inspector, barCode.asDexCode().instructions);
+    assertTrue(bar instanceof FoundMethodSubject);
+    FoundMethodSubject foundBar = (FoundMethodSubject) bar;
+    verifyPresenceOfConstString(foundBar);
+    renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundBar);
     assertEquals(1, renamedYetFoundIdentifierCount);
 
     renamedYetFoundIdentifierCount =
@@ -154,64 +180,66 @@
     assertEquals(1, renamedYetFoundIdentifierCount);
   }
 
-  private static void test_atomicfieldupdater(DexInspector inspector) {
+  private static void test_atomicfieldupdater(CodeInspector inspector) {
     ClassSubject mainClass = inspector.clazz("atomicfieldupdater.Main");
-    MethodSubject main = mainClass.method(DexInspector.MAIN);
-    Code mainCode = main.getMethod().getCode();
-    verifyPresenceOfConstString(mainCode.asDexCode().instructions);
+    MethodSubject main = mainClass.method(CodeInspector.MAIN);
+    assertTrue(main instanceof FoundMethodSubject);
+    FoundMethodSubject foundMain = (FoundMethodSubject) main;
+    verifyPresenceOfConstString(foundMain);
 
     ClassSubject a = inspector.clazz("atomicfieldupdater.A");
-    Set<Instruction> constStringInstructions =
-        getRenamedMemberIdentifierConstStrings(a, mainCode.asDexCode().instructions);
+    Set<InstructionSubject> constStringInstructions =
+        getRenamedMemberIdentifierConstStrings(a, foundMain);
     assertEquals(3, constStringInstructions.size());
   }
 
-  private static void test_forname(DexInspector inspector) {
+  private static void test_forname(CodeInspector inspector) {
     ClassSubject mainClass = inspector.clazz("forname.Main");
-    MethodSubject main = mainClass.method(DexInspector.MAIN);
-    Code mainCode = main.getMethod().getCode();
-    verifyPresenceOfConstString(mainCode.asDexCode().instructions);
-    int renamedYetFoundIdentifierCount =
-        countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
+    MethodSubject main = mainClass.method(CodeInspector.MAIN);
+    assertTrue(main instanceof FoundMethodSubject);
+    FoundMethodSubject foundMain = (FoundMethodSubject) main;
+    verifyPresenceOfConstString(foundMain);
+    int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundMain);
     assertEquals(1, renamedYetFoundIdentifierCount);
   }
 
-  private static void test_getmembers(DexInspector inspector) {
+  private static void test_getmembers(CodeInspector inspector) {
     ClassSubject mainClass = inspector.clazz("getmembers.Main");
-    MethodSubject main = mainClass.method(DexInspector.MAIN);
-    Code mainCode = main.getMethod().getCode();
-    verifyPresenceOfConstString(mainCode.asDexCode().instructions);
+    MethodSubject main = mainClass.method(CodeInspector.MAIN);
+    assertTrue(main instanceof FoundMethodSubject);
+    FoundMethodSubject foundMain = (FoundMethodSubject) main;
+    verifyPresenceOfConstString(foundMain);
 
     ClassSubject a = inspector.clazz("getmembers.A");
-    Set<Instruction> constStringInstructions =
-        getRenamedMemberIdentifierConstStrings(a, mainCode.asDexCode().instructions);
+    Set<InstructionSubject> constStringInstructions =
+        getRenamedMemberIdentifierConstStrings(a, foundMain);
     assertEquals(2, constStringInstructions.size());
 
     ClassSubject b = inspector.clazz("getmembers.B");
     MethodSubject inliner = b.method("java.lang.String", "inliner", ImmutableList.of());
-    Code inlinerCode = inliner.getMethod().getCode();
-    constStringInstructions =
-        getRenamedMemberIdentifierConstStrings(a, inlinerCode.asDexCode().instructions);
+    assertTrue(inliner instanceof FoundMethodSubject);
+    FoundMethodSubject foundInliner = (FoundMethodSubject) inliner;
+    constStringInstructions = getRenamedMemberIdentifierConstStrings(a, foundInliner);
     assertEquals(1, constStringInstructions.size());
   }
 
   // Without -identifiernamestring
-  private static void test2_rule1(DexInspector inspector) {
+  private static void test2_rule1(CodeInspector inspector) {
     ClassSubject mainClass = inspector.clazz("identifiernamestring.Main");
-    MethodSubject main = mainClass.method(DexInspector.MAIN);
-    Code mainCode = main.getMethod().getCode();
-    verifyPresenceOfConstString(mainCode.asDexCode().instructions);
-    int renamedYetFoundIdentifierCount =
-        countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
-    assertEquals(0, renamedYetFoundIdentifierCount);
+    MethodSubject main = mainClass.method(CodeInspector.MAIN);
+    assertTrue(main instanceof FoundMethodSubject);
+    FoundMethodSubject foundMain = (FoundMethodSubject) main;
+    verifyPresenceOfConstString(foundMain);
+    int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundMain);
+    assertEquals(1, renamedYetFoundIdentifierCount);
 
     ClassSubject aClass = inspector.clazz("identifiernamestring.A");
     MethodSubject aInit =
         aClass.method("void", "<init>", ImmutableList.of());
-    Code initCode = aInit.getMethod().getCode();
-    verifyPresenceOfConstString(initCode.asDexCode().instructions);
-    renamedYetFoundIdentifierCount =
-        countRenamedClassIdentifier(inspector, initCode.asDexCode().instructions);
+    assertTrue(aInit instanceof FoundMethodSubject);
+    FoundMethodSubject foundAInit = (FoundMethodSubject) aInit;
+    verifyPresenceOfConstString(foundAInit);
+    renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundAInit);
     assertEquals(0, renamedYetFoundIdentifierCount);
 
     renamedYetFoundIdentifierCount =
@@ -220,24 +248,22 @@
   }
 
   // With -identifiernamestring for annotations and name-based filters
-  private static void test2_rule2(DexInspector inspector) {
+  private static void test2_rule2(CodeInspector inspector) {
     ClassSubject mainClass = inspector.clazz("identifiernamestring.Main");
-    MethodSubject main = mainClass.method(DexInspector.MAIN);
-    assertTrue(main.isPresent());
-    Code mainCode = main.getMethod().getCode();
-    assertTrue(mainCode.isDexCode());
-    verifyPresenceOfConstString(mainCode.asDexCode().instructions);
-    int renamedYetFoundIdentifierCount =
-        countRenamedClassIdentifier(inspector, mainCode.asDexCode().instructions);
-    assertEquals(1, renamedYetFoundIdentifierCount);
+    MethodSubject main = mainClass.method(CodeInspector.MAIN);
+    assertTrue(main instanceof FoundMethodSubject);
+    FoundMethodSubject foundMain = (FoundMethodSubject) main;
+    verifyPresenceOfConstString(foundMain);
+    int renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundMain);
+    assertEquals(2, renamedYetFoundIdentifierCount);
 
     ClassSubject aClass = inspector.clazz("identifiernamestring.A");
     MethodSubject aInit =
         aClass.method("void", "<init>", ImmutableList.of());
-    Code initCode = aInit.getMethod().getCode();
-    verifyPresenceOfConstString(initCode.asDexCode().instructions);
-    renamedYetFoundIdentifierCount =
-        countRenamedClassIdentifier(inspector, initCode.asDexCode().instructions);
+    assertTrue(aInit instanceof FoundMethodSubject);
+    FoundMethodSubject foundAInit = (FoundMethodSubject) aInit;
+    verifyPresenceOfConstString(foundAInit);
+    renamedYetFoundIdentifierCount = countRenamedClassIdentifier(inspector, foundAInit);
     assertEquals(1, renamedYetFoundIdentifierCount);
 
     renamedYetFoundIdentifierCount =
@@ -245,63 +271,55 @@
     assertEquals(2, renamedYetFoundIdentifierCount);
   }
 
-  // With -identifiernamestring for reflective methods
-  private static void test2_rule3(DexInspector inspector) {
+  // With -identifiernamestring for reflective methods in testing class R.
+  private static void test2_rule3(CodeInspector inspector) {
     ClassSubject mainClass = inspector.clazz("identifiernamestring.Main");
-    MethodSubject main = mainClass.method(DexInspector.MAIN);
-    assertTrue(main.isPresent());
-    Code mainCode = main.getMethod().getCode();
-    assertTrue(mainCode.isDexCode());
-    verifyPresenceOfConstString(mainCode.asDexCode().instructions);
+    MethodSubject main = mainClass.method(CodeInspector.MAIN);
+    assertTrue(main instanceof FoundMethodSubject);
+    FoundMethodSubject foundMain = (FoundMethodSubject) main;
+    verifyPresenceOfConstString(foundMain);
 
     ClassSubject b = inspector.clazz("identifiernamestring.B");
-    Set<Instruction> constStringInstructions =
-        getRenamedMemberIdentifierConstStrings(b, mainCode.asDexCode().instructions);
+    Set<InstructionSubject> constStringInstructions =
+        getRenamedMemberIdentifierConstStrings(b, foundMain);
     assertEquals(2, constStringInstructions.size());
   }
 
-  private static void verifyPresenceOfConstString(Instruction[] instructions) {
-    boolean presence =
-        Arrays.stream(instructions)
-            .anyMatch(instr -> instr instanceof ConstString || instr instanceof ConstStringJumbo);
-    assertTrue(presence);
+  private static void verifyPresenceOfConstString(FoundMethodSubject method) {
+    assertTrue(
+        method
+            .iterateInstructions(instruction -> instruction.isConstString(JumboStringMode.ALLOW))
+            .hasNext());
   }
 
-  private static String retrieveString(Instruction instr) {
-    if (instr instanceof ConstString) {
-      ConstString cnst = (ConstString) instr;
-      return cnst.getString().toString();
-    } else if (instr instanceof ConstStringJumbo) {
-      ConstStringJumbo cnst = (ConstStringJumbo) instr;
-      return cnst.getString().toString();
-    }
-    return null;
-  }
-
-  private static Stream<Instruction> getConstStringInstructions(Instruction[] instructions) {
-    return Arrays.stream(instructions)
-        .filter(instr -> instr instanceof ConstString || instr instanceof ConstStringJumbo);
+  private static Stream<InstructionSubject> getConstStringInstructions(FoundMethodSubject method) {
+    return Streams.stream(method.iterateInstructions())
+        .filter(instr -> instr.isConstString(JumboStringMode.ALLOW));
   }
 
   private static int countRenamedClassIdentifier(
-      DexInspector inspector, Instruction[] instructions) {
-    return getConstStringInstructions(instructions)
-        .reduce(0, (cnt, instr) -> {
-          String cnstString = retrieveString(instr);
-          assertNotNull(cnstString);
-          if (isValidJavaType(cnstString)) {
-            ClassSubject classSubject = inspector.clazz(cnstString);
-            if (classSubject.isRenamed()
-                && descriptorToJavaType(classSubject.getFinalDescriptor()).equals(cnstString)) {
-              return cnt + 1;
-            }
-          }
-          return cnt;
-        }, Integer::sum);
+      CodeInspector inspector, FoundMethodSubject method) {
+    return getConstStringInstructions(method)
+        .reduce(
+            0,
+            (cnt, instr) -> {
+              assert (instr instanceof ConstStringInstructionSubject);
+              String cnstString =
+                  ((ConstStringInstructionSubject) instr).getString().toSourceString();
+              if (isValidJavaType(cnstString)) {
+                ClassSubject classSubject = inspector.clazz(cnstString);
+                if (classSubject.isRenamed()
+                    && descriptorToJavaType(classSubject.getFinalDescriptor()).equals(cnstString)) {
+                  return cnt + 1;
+                }
+              }
+              return cnt;
+            },
+            Integer::sum);
   }
 
   private static int countRenamedClassIdentifier(
-      DexInspector inspector, DexEncodedField[] fields) {
+      CodeInspector inspector, DexEncodedField[] fields) {
     return Arrays.stream(fields)
         .filter(encodedField -> encodedField.getStaticValue() instanceof DexValueString)
         .reduce(0, (cnt, encodedField) -> {
@@ -318,22 +336,27 @@
         }, Integer::sum);
   }
 
-  private static Set<Instruction> getRenamedMemberIdentifierConstStrings(
-      ClassSubject clazz, Instruction[] instructions) {
-    Set<Instruction> result = Sets.newIdentityHashSet();
-    getConstStringInstructions(instructions).forEach(instr -> {
-      String cnstString = retrieveString(instr);
-      clazz.forAllMethods(foundMethodSubject -> {
-        if (foundMethodSubject.getFinalSignature().name.equals(cnstString)) {
-          result.add(instr);
-        }
-      });
-      clazz.forAllFields(foundFieldSubject -> {
-        if (foundFieldSubject.getFinalSignature().name.equals(cnstString)) {
-          result.add(instr);
-        }
-      });
-    });
+  private static Set<InstructionSubject> getRenamedMemberIdentifierConstStrings(
+      ClassSubject clazz, FoundMethodSubject method) {
+    Set<InstructionSubject> result = Sets.newIdentityHashSet();
+    getConstStringInstructions(method)
+        .forEach(
+            instr -> {
+              String cnstString =
+                  ((ConstStringInstructionSubject) instr).getString().toSourceString();
+              clazz.forAllMethods(
+                  foundMethodSubject -> {
+                    if (foundMethodSubject.getFinalSignature().name.equals(cnstString)) {
+                      result.add(instr);
+                    }
+                  });
+              clazz.forAllFields(
+                  foundFieldSubject -> {
+                    if (foundFieldSubject.getFinalSignature().name.equals(cnstString)) {
+                      result.add(instr);
+                    }
+                  });
+            });
     return result;
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
index 982e935..28884cc 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -26,9 +26,9 @@
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.smali.SmaliTestBase;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
@@ -52,7 +52,7 @@
         "-identifiernamestring class " + CLASS_NAME + " { java.lang.String aClassName; }",
         "-keep class " + CLASS_NAME,
         "-dontoptimize");
-    DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -84,7 +84,7 @@
         "-identifiernamestring class " + CLASS_NAME + " { java.lang.String aClassName; }",
         "-keep class " + CLASS_NAME,
         "-dontoptimize");
-    DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -126,7 +126,7 @@
         "-keep class " + CLASS_NAME,
         "-keep,allowobfuscation class " + BOO,
         "-dontoptimize");
-    DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -163,7 +163,7 @@
         "-identifiernamestring class " + CLASS_NAME + " { static java.lang.String sClassName; }",
         "-keep class " + CLASS_NAME,
         "-dontoptimize");
-    DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -194,7 +194,7 @@
         "-identifiernamestring class " + CLASS_NAME + " { static java.lang.String sClassName; }",
         "-keep class " + CLASS_NAME,
         "-dontoptimize");
-    DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -232,7 +232,7 @@
         "-keep class " + CLASS_NAME,
         "-keep,allowobfuscation class " + BOO,
         "-dontoptimize");
-    DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -262,7 +262,7 @@
         "-identifiernamestring class " + CLASS_NAME + " { static java.lang.String sClassName; }",
         "-keep class " + CLASS_NAME + " { static java.lang.String sClassName; }",
         "-dontshrink");
-    DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -284,7 +284,7 @@
         "-keep class " + CLASS_NAME + " { static java.lang.String sClassName; }",
         "-keep,allowobfuscation class " + BOO,
         "-dontshrink");
-    DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -308,7 +308,7 @@
         "-keep class " + CLASS_NAME + " { static java.lang.String sFieldName; }",
         "-keep,allowobfuscation class " + BOO + " { <fields>; }",
         "-dontshrink");
-    DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -332,7 +332,7 @@
         "-keep class " + CLASS_NAME + " { static java.lang.String sMethodName; }",
         "-keep,allowobfuscation class " + BOO + " { <methods>; }",
         "-dontshrink");
-    DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -362,7 +362,7 @@
     List<String> pgConfigs = ImmutableList.of(
         "-identifiernamestring class " + CLASS_NAME + " { static void foo(...); }",
         "-keep class " + CLASS_NAME);
-    DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -404,7 +404,7 @@
     List<String> pgConfigs = ImmutableList.of(
         "-identifiernamestring class " + CLASS_NAME + " { static void foo(...); }",
         "-keep class " + CLASS_NAME);
-    DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -450,7 +450,7 @@
         "-identifiernamestring class " + CLASS_NAME + " { static void foo(...); }",
         "-keep class " + CLASS_NAME,
         "-keep,allowobfuscation class " + BOO);
-    DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -504,7 +504,7 @@
             + "}",
         "-keep class " + CLASS_NAME,
         "-keep class R { *; }");
-    DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -554,7 +554,7 @@
             + "}",
         "-keep class " + CLASS_NAME,
         "-keep,allowobfuscation class R { *; }");
-    DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -611,7 +611,7 @@
             + "}",
         "-keep class " + CLASS_NAME,
         "-keep class R { *; }");
-    DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -672,7 +672,7 @@
             + "}",
         "-keep class " + CLASS_NAME,
         "-keep,allowobfuscation class R { *; }");
-    DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
+    CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
@@ -696,8 +696,8 @@
     assertNotEquals("foo", constString.getString().toString());
   }
 
-  private DexInspector getInspectorAfterRunR8(
+  private CodeInspector getInspectorAfterRunR8(
       SmaliBuilder builder, List<String> proguardConfigurations) throws Exception {
-    return new DexInspector(runR8(builder, proguardConfigurations));
+    return new CodeInspector(runR8(builder, proguardConfigurations));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
index c7c74e9..02bca3a 100644
--- a/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
+++ b/src/test/java/com/android/tools/r8/naming/LambdaRenamingTestRunner.java
@@ -20,7 +20,6 @@
 import com.android.tools.r8.utils.FileUtils;
 import java.io.IOException;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -129,7 +128,7 @@
     Path outPg = temp.getRoot().toPath().resolve(outName);
     ProcessResult proguardResult =
         ToolHelper.runProguard6Raw(
-            inputJar, outPg, Paths.get(ToolHelper.JAVA_8_RUNTIME), pgConfig, null);
+            inputJar, outPg, ToolHelper.getJava8RuntimeJar(), pgConfig, null);
     System.out.println(proguardResult.stdout);
     if (proguardResult.exitCode != 0) {
       System.out.println(proguardResult.stderr);
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
index b9ff7ea..a5bbdf6 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
@@ -4,10 +4,9 @@
 
 package com.android.tools.r8.naming;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.not;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
@@ -21,6 +20,7 @@
 import static org.objectweb.asm.Opcodes.RETURN;
 import static org.objectweb.asm.Opcodes.V1_8;
 
+import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DiagnosticsChecker;
 import com.android.tools.r8.R8Command;
@@ -29,15 +29,21 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.invokesuper.Consumer;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Collection;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 import org.objectweb.asm.ClassWriter;
 import org.objectweb.asm.FieldVisitor;
 import org.objectweb.asm.MethodVisitor;
 
+@RunWith(Parameterized.class)
 public class MinifierClassSignatureTest extends TestBase {
   /*
 
@@ -62,6 +68,16 @@
   String outerSignature = "<T:Ljava/lang/Object;>Ljava/lang/Object;";
   String extendsInnerSignature = "LOuter<TT;>.Inner;";
   String extendsInnerInnerSignature = "LOuter<TT;>.Inner.InnerInner;";
+  Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public MinifierClassSignatureTest(Backend backend) {
+    this.backend = backend;
+  }
 
   private byte[] dumpSimple(String classSignature) throws Exception {
 
@@ -287,29 +303,39 @@
   public void runTest(
       ImmutableMap<String, String> signatures,
       Consumer<DiagnosticsChecker> diagnostics,
-      Consumer<DexInspector> inspect)
+      Consumer<CodeInspector> inspect)
       throws Exception {
     DiagnosticsChecker checker = new DiagnosticsChecker();
-    DexInspector inspector = new DexInspector(
-      ToolHelper.runR8(R8Command.builder(checker)
-          .addClassProgramData(dumpSimple(signatures.get("Simple")), Origin.unknown())
-          .addClassProgramData(dumpBase(signatures.get("Base")), Origin.unknown())
-          .addClassProgramData(dumpOuter(signatures.get("Outer")), Origin.unknown())
-          .addClassProgramData(dumpInner(signatures.get("Outer$Inner")), Origin.unknown())
-          .addClassProgramData(
-              dumpExtendsInner(signatures.get("Outer$ExtendsInner")), Origin.unknown())
-          .addClassProgramData(
-              dumpInnerInner(signatures.get("Outer$Inner$InnerInner")), Origin.unknown())
-          .addClassProgramData(
-              dumpExtendsInnerInner(
-                  signatures.get("Outer$Inner$ExtendsInnerInner")), Origin.unknown())
-          .addProguardConfiguration(ImmutableList.of(
-              "-keepattributes InnerClasses,EnclosingMethod,Signature",
-              "-keep,allowobfuscation class **"
-          ), Origin.unknown())
-          .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
-          .setProguardMapConsumer(StringConsumer.emptyConsumer())
-          .build()));
+    assert (backend == Backend.CF || backend == Backend.DEX);
+    CodeInspector inspector =
+        new CodeInspector(
+            ToolHelper.runR8(
+                R8Command.builder(checker)
+                    .addClassProgramData(dumpSimple(signatures.get("Simple")), Origin.unknown())
+                    .addClassProgramData(dumpBase(signatures.get("Base")), Origin.unknown())
+                    .addClassProgramData(dumpOuter(signatures.get("Outer")), Origin.unknown())
+                    .addClassProgramData(dumpInner(signatures.get("Outer$Inner")), Origin.unknown())
+                    .addClassProgramData(
+                        dumpExtendsInner(signatures.get("Outer$ExtendsInner")), Origin.unknown())
+                    .addClassProgramData(
+                        dumpInnerInner(signatures.get("Outer$Inner$InnerInner")), Origin.unknown())
+                    .addClassProgramData(
+                        dumpExtendsInnerInner(signatures.get("Outer$Inner$ExtendsInnerInner")),
+                        Origin.unknown())
+                    .addProguardConfiguration(
+                        ImmutableList.of(
+                            "-keepattributes InnerClasses,EnclosingMethod,Signature",
+                            "-keep,allowobfuscation class **"),
+                        Origin.unknown())
+                    .setProgramConsumer(
+                        backend == Backend.DEX
+                            ? DexIndexedConsumer.emptyConsumer()
+                            : ClassFileConsumer.emptyConsumer())
+                    .setProguardMapConsumer(StringConsumer.emptyConsumer())
+                    .build(),
+                options -> {
+                  options.testing.suppressExperimentalCfBackendWarning = true;
+                }));
     // All classes are kept, and renamed.
     assertThat(inspector.clazz("Simple"), isRenamed());
     assertThat(inspector.clazz("Base"), isRenamed());
@@ -350,7 +376,7 @@
 
   private void testSingleClass(String name, String signature,
       Consumer<DiagnosticsChecker> diagnostics,
-      Consumer<DexInspector> inspector)
+      Consumer<CodeInspector> inspector)
       throws Exception {
     ImmutableMap<String, String> signatures = ImmutableMap.of(name, signature);
     runTest(signatures, diagnostics, inspector);
@@ -364,7 +390,7 @@
     assertEquals(0, checker.warnings.size());
   }
 
-  private void noInspection(DexInspector inspector) {
+  private void noInspection(CodeInspector inspector) {
   }
 
   private void noSignatureAttribute(ClassSubject clazz) {
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
index e48ce56..c470661 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.naming;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -21,6 +21,7 @@
 import static org.objectweb.asm.Opcodes.RETURN;
 import static org.objectweb.asm.Opcodes.V1_8;
 
+import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DiagnosticsChecker;
 import com.android.tools.r8.R8Command;
@@ -29,18 +30,23 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.invokesuper.Consumer;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Map;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 import org.objectweb.asm.ClassWriter;
 import org.objectweb.asm.FieldVisitor;
 import org.objectweb.asm.MethodVisitor;
 
-
+@RunWith(Parameterized.class)
 public class MinifierFieldSignatureTest extends TestBase {
   /*
 
@@ -59,6 +65,16 @@
   private String anArrayOfXSignature = "[TX;";
   private String aFieldsOfXSignature = "LFields<TX;>;";
   private String aFieldsOfXInnerSignature = "LFields<TX;>.Inner;";
+  private Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public MinifierFieldSignatureTest(Backend backend) {
+    this.backend = backend;
+  }
 
   public byte[] dumpFields(Map<String, String> signatures) throws Exception {
 
@@ -143,17 +159,17 @@
     return cw.toByteArray();
   }
 
-  private FieldSubject lookupAnX(DexInspector inspector) {
+  private FieldSubject lookupAnX(CodeInspector inspector) {
     ClassSubject clazz = inspector.clazz("Fields");
     return clazz.field("java.lang.String", "anX");
   }
 
-  private FieldSubject lookupAnArrayOfX(DexInspector inspector) {
+  private FieldSubject lookupAnArrayOfX(CodeInspector inspector) {
     ClassSubject clazz = inspector.clazz("Fields");
     return clazz.field("java.lang.String[]", "anArrayOfX");
   }
 
-  private FieldSubject lookupAFieldsOfX(DexInspector inspector) {
+  private FieldSubject lookupAFieldsOfX(CodeInspector inspector) {
     ClassSubject clazz = inspector.clazz("Fields");
     return clazz.field("Fields", "aFieldsOfX");
   }
@@ -161,20 +177,30 @@
   public void runTest(
       ImmutableMap<String, String> signatures,
       Consumer<DiagnosticsChecker> diagnostics,
-      Consumer<DexInspector> inspect)
+      Consumer<CodeInspector> inspect)
       throws Exception {
     DiagnosticsChecker checker = new DiagnosticsChecker();
-    DexInspector inspector = new DexInspector(
-      ToolHelper.runR8(R8Command.builder(checker)
-          .addClassProgramData(dumpFields(signatures), Origin.unknown())
-          .addClassProgramData(dumpInner(), Origin.unknown())
-          .addProguardConfiguration(ImmutableList.of(
-              "-keepattributes InnerClasses,EnclosingMethod,Signature",
-              "-keep,allowobfuscation class ** { *; }"
-          ), Origin.unknown())
-          .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
-          .setProguardMapConsumer(StringConsumer.emptyConsumer())
-          .build()));
+    assert (backend == Backend.CF || backend == Backend.DEX);
+    CodeInspector inspector =
+        new CodeInspector(
+            ToolHelper.runR8(
+                R8Command.builder(checker)
+                    .addClassProgramData(dumpFields(signatures), Origin.unknown())
+                    .addClassProgramData(dumpInner(), Origin.unknown())
+                    .addProguardConfiguration(
+                        ImmutableList.of(
+                            "-keepattributes InnerClasses,EnclosingMethod,Signature",
+                            "-keep,allowobfuscation class ** { *; }"),
+                        Origin.unknown())
+                    .setProgramConsumer(
+                        backend == Backend.DEX
+                            ? DexIndexedConsumer.emptyConsumer()
+                            : ClassFileConsumer.emptyConsumer())
+                    .setProguardMapConsumer(StringConsumer.emptyConsumer())
+                    .build(),
+                options -> {
+                  options.testing.suppressExperimentalCfBackendWarning = true;
+                }));
     // All classes are kept, and renamed.
     ClassSubject clazz = inspector.clazz("Fields");
     assertThat(clazz, isRenamed());
@@ -216,7 +242,7 @@
 
   private void testSingleField(String name, String signature,
       Consumer<DiagnosticsChecker> diagnostics,
-      Consumer<DexInspector> inspector)
+      Consumer<CodeInspector> inspector)
       throws Exception {
     ImmutableMap<String, String> signatures = ImmutableMap.of(name, signature);
     runTest(signatures, diagnostics, inspector);
@@ -230,7 +256,7 @@
     assertEquals(0, checker.warnings.size());
   }
 
-  private void noInspection(DexInspector inspector) {
+  private void noInspection(CodeInspector inspector) {
   }
 
   private void noSignatureAttribute(FieldSubject field) {
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
index 41750ae..aa5307d 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.naming;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -24,6 +24,7 @@
 import static org.objectweb.asm.Opcodes.RETURN;
 import static org.objectweb.asm.Opcodes.V1_8;
 
+import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DiagnosticsChecker;
 import com.android.tools.r8.R8Command;
@@ -32,17 +33,23 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.invokesuper.Consumer;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Map;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 import org.objectweb.asm.ClassWriter;
 import org.objectweb.asm.FieldVisitor;
 import org.objectweb.asm.MethodVisitor;
 
+@RunWith(Parameterized.class)
 public class MinifierMethodSignatureTest extends TestBase {
   /*
 
@@ -61,6 +68,16 @@
   private String parameterizedReturnSignature = "()LMethods<TX;>.Inner;";
   private String parameterizedArgumentsSignature = "(TX;LMethods<TX;>.Inner;)V";
   private String parametrizedThrowsSignature = "()V^TX;";
+  Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public MinifierMethodSignatureTest(Backend backend) {
+    this.backend = backend;
+  }
 
   private byte[] dumpMethods(Map<String, String> signatures) throws Exception {
 
@@ -161,19 +178,19 @@
     return cw.toByteArray();
   }
 
-  private MethodSubject lookupGeneric(DexInspector inspector) {
+  private MethodSubject lookupGeneric(CodeInspector inspector) {
     ClassSubject clazz = inspector.clazz("Methods");
     return clazz.method(
         "java.lang.Throwable", "generic", ImmutableList.of("java.lang.Throwable", "Methods$Inner"));
   }
 
-  private MethodSubject lookupParameterizedReturn(DexInspector inspector) {
+  private MethodSubject lookupParameterizedReturn(CodeInspector inspector) {
     ClassSubject clazz = inspector.clazz("Methods");
     return clazz.method(
         "Methods$Inner", "parameterizedReturn", ImmutableList.of());
   }
 
-  private MethodSubject lookupParameterizedArguments(DexInspector inspector) {
+  private MethodSubject lookupParameterizedArguments(CodeInspector inspector) {
     ClassSubject clazz = inspector.clazz("Methods");
     return clazz.method(
         "void", "parameterizedArguments", ImmutableList.of("java.lang.Throwable", "Methods$Inner"));
@@ -182,20 +199,30 @@
   public void runTest(
       ImmutableMap<String, String> signatures,
       Consumer<DiagnosticsChecker> diagnostics,
-      Consumer<DexInspector> inspect)
+      Consumer<CodeInspector> inspect)
       throws Exception {
     DiagnosticsChecker checker = new DiagnosticsChecker();
-    DexInspector inspector = new DexInspector(
-      ToolHelper.runR8(R8Command.builder(checker)
-          .addClassProgramData(dumpMethods(signatures), Origin.unknown())
-          .addClassProgramData(dumpInner(), Origin.unknown())
-          .addProguardConfiguration(ImmutableList.of(
-              "-keepattributes InnerClasses,EnclosingMethod,Signature",
-              "-keep,allowobfuscation class ** { *; }"
-          ), Origin.unknown())
-          .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
-          .setProguardMapConsumer(StringConsumer.emptyConsumer())
-          .build()));
+    assert (backend == Backend.CF || backend == Backend.DEX);
+    CodeInspector inspector =
+        new CodeInspector(
+            ToolHelper.runR8(
+                R8Command.builder(checker)
+                    .addClassProgramData(dumpMethods(signatures), Origin.unknown())
+                    .addClassProgramData(dumpInner(), Origin.unknown())
+                    .addProguardConfiguration(
+                        ImmutableList.of(
+                            "-keepattributes InnerClasses,EnclosingMethod,Signature",
+                            "-keep,allowobfuscation class ** { *; }"),
+                        Origin.unknown())
+                    .setProgramConsumer(
+                        backend == Backend.DEX
+                            ? DexIndexedConsumer.emptyConsumer()
+                            : ClassFileConsumer.emptyConsumer())
+                    .setProguardMapConsumer(StringConsumer.emptyConsumer())
+                    .build(),
+                options -> {
+                  options.testing.suppressExperimentalCfBackendWarning = true;
+                }));
     // All classes are kept, and renamed.
     ClassSubject clazz = inspector.clazz("Methods");
     assertThat(clazz, isRenamed());
@@ -236,7 +263,7 @@
 
   private void testSingleMethod(String name, String signature,
       Consumer<DiagnosticsChecker> diagnostics,
-      Consumer<DexInspector> inspector)
+      Consumer<CodeInspector> inspector)
       throws Exception {
     ImmutableMap<String, String> signatures = ImmutableMap.of(name, signature);
     runTest(signatures, diagnostics, inspector);
@@ -250,7 +277,7 @@
     assertEquals(0, checker.warnings.size());
   }
 
-  private void noInspection(DexInspector inspector) {
+  private void noInspection(CodeInspector inspector) {
   }
 
   private void noSignatureAttribute(MethodSubject method) {
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index 5cc07a4..ed95627 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -84,7 +84,9 @@
           new RootSetBuilder(appInfo, program, configuration.getRules(), options).run(executor);
     }
 
-    Enqueuer enqueuer = new Enqueuer(appInfo, options, options.forceProguardCompatibility);
+    Enqueuer enqueuer =
+        new Enqueuer(
+            appInfo, GraphLense.getIdentityLense(), options, options.forceProguardCompatibility);
     appInfo = enqueuer.traceApplication(rootSet, executor, timing);
     return new Minifier(appInfo.withLiveness(), rootSet, options).run(timing);
   }
diff --git a/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java b/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
index ddd0aa2..49ba186 100644
--- a/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
+++ b/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
@@ -3,12 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.DiagnosticsChecker;
 import com.android.tools.r8.OutputMode;
@@ -19,10 +20,10 @@
 import com.android.tools.r8.VmTestRunner;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.KeepingDiagnosticHandler;
 import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.lang.reflect.Method;
 import java.nio.file.Path;
@@ -72,15 +73,17 @@
       byte[][] classes,
       Class main,
       Path out,
+      boolean explicitRule,
       boolean enableMinification,
       boolean forceProguardCompatibility)
       throws Exception {
     String minificationModifier = enableMinification ? ",allowobfuscation " : " ";
-    String reflectionRules = forceProguardCompatibility ? ""
-        : "-identifiernamestring class java.lang.Class {\n"
+    String reflectionRules = explicitRule
+        ? "-identifiernamestring class java.lang.Class {\n"
             + "static java.lang.Class forName(java.lang.String);\n"
             + "java.lang.reflect.Method getDeclaredMethod(java.lang.String, java.lang.Class[]);\n"
-            + "}\n";
+            + "}\n"
+        : "";
     R8Command.Builder commandBuilder =
         R8Command.builder(reporter)
             .addProguardConfiguration(
@@ -102,7 +105,8 @@
     });
   }
 
-  private void reflectionWithBuildter(
+  private void reflectionWithBuilder(
+      boolean explicitRule,
       boolean enableMinification,
       boolean forceProguardCompatibility) throws Exception {
     byte[][] classes = {
@@ -110,11 +114,11 @@
     };
     Path out = temp.getRoot().toPath();
     AndroidApp processedApp = runR8(classes, WarnReflectiveAccessTestMain.class, out,
-        enableMinification, forceProguardCompatibility);
+        explicitRule, enableMinification, forceProguardCompatibility);
 
     String main = WarnReflectiveAccessTestMain.class.getCanonicalName();
-    DexInspector dexInspector = new DexInspector(processedApp);
-    ClassSubject mainSubject = dexInspector.clazz(main);
+    CodeInspector codeInspector = new CodeInspector(processedApp);
+    ClassSubject mainSubject = codeInspector.clazz(main);
     assertThat(mainSubject, isPresent());
 
     ProcessResult javaOutput = runOnJavaRaw(main, classes);
@@ -133,35 +137,59 @@
   }
 
   @Test
-  public void test_minification_forceProguardCompatibility() throws Exception {
-    reflectionWithBuildter(true, true);
+  public void test_explicit_minification_forceProguardCompatibility() throws Exception {
+    reflectionWithBuilder(true, true, true);
     assertFalse(handler.warnings.isEmpty());
-    DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 54, 1,
-        "Cannot determine", "getDeclaredMethod", "resolution failure");
-  }
-
-  @Test
-  public void test_noMinification_forceProguardCompatibility() throws Exception {
-    reflectionWithBuildter(false, true);
-    assertFalse(handler.warnings.isEmpty());
-    DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 54, 1,
-        "Cannot determine", "getDeclaredMethod", "resolution failure");
-  }
-
-  @Test
-  public void test_minification_R8() throws Exception {
-    reflectionWithBuildter(true, false);
-    assertFalse(handler.warnings.isEmpty());
-    DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 54, 1,
+    DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 55, 1,
         "Cannot determine", "getDeclaredMethod", "-identifiernamestring", "resolution failure");
   }
 
   @Test
-  public void test_noMinification_R8() throws Exception {
-    reflectionWithBuildter(false, false);
+  public void test_explicit_noMinification_forceProguardCompatibility() throws Exception {
+    reflectionWithBuilder(true, false, true);
     assertFalse(handler.warnings.isEmpty());
-    DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 54, 1,
+    DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 55, 1,
         "Cannot determine", "getDeclaredMethod", "-identifiernamestring", "resolution failure");
   }
 
+  @Test
+  public void test_explicit_minification_R8() throws Exception {
+    reflectionWithBuilder(true, true, false);
+    assertFalse(handler.warnings.isEmpty());
+    DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 55, 1,
+        "Cannot determine", "getDeclaredMethod", "-identifiernamestring", "resolution failure");
+  }
+
+  @Test
+  public void test_explicit_noMinification_R8() throws Exception {
+    reflectionWithBuilder(true, false, false);
+    assertFalse(handler.warnings.isEmpty());
+    DiagnosticsChecker.checkDiagnostic(handler.warnings.get(0), (Path) null, 55, 1,
+        "Cannot determine", "getDeclaredMethod", "-identifiernamestring", "resolution failure");
+  }
+
+  @Test
+  public void test_implicit_minification_forceProguardCompatibility() throws Exception {
+    reflectionWithBuilder(false, true, true);
+    assertTrue(handler.warnings.isEmpty());
+  }
+
+  @Test
+  public void test_implicit_noMinification_forceProguardCompatibility() throws Exception {
+    reflectionWithBuilder(false, false, true);
+    assertTrue(handler.warnings.isEmpty());
+  }
+
+  @Test
+  public void test_implicit_minification_R8() throws Exception {
+    reflectionWithBuilder(false, true, false);
+    assertTrue(handler.warnings.isEmpty());
+  }
+
+  @Test
+  public void test_implicit_noMinification_R8() throws Exception {
+    reflectionWithBuilder(false, false, false);
+    assertTrue(handler.warnings.isEmpty());
+  }
+
 }
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
index ec27895..9eeb2f7 100644
--- a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.naming.b72391662;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -15,11 +15,11 @@
 import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
 import com.android.tools.r8.naming.b72391662.subpackage.OtherPackageSuper;
 import com.android.tools.r8.naming.b72391662.subpackage.OtherPackageTestClass;
-import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import java.util.List;
@@ -27,7 +27,7 @@
 import org.junit.runner.RunWith;
 
 @RunWith(VmTestRunner.class)
-public class B72391662 extends ProguardCompatabilityTestBase {
+public class B72391662 extends ProguardCompatibilityTestBase {
 
   private static final List<Class> CLASSES = ImmutableList.of(
       TestMain.class, Interface.class, Super.class, TestClass.class,
@@ -57,12 +57,12 @@
         "-dontwarn java.lang.invoke.*"
     );
 
-    AndroidApp app = runShrinkerRaw(shrinker, CLASSES, config);
+    AndroidApp app = runShrinker(shrinker, CLASSES, config);
     assertEquals("123451234567\nABC\n", runOnArt(app, mainClass.getCanonicalName()));
 
-    DexInspector dexInspector =
-        isR8(shrinker) ? new DexInspector(app) : new DexInspector(app, proguardMap);
-    ClassSubject testClass = dexInspector.clazz(TestClass.class);
+    CodeInspector codeInspector =
+        isR8(shrinker) ? new CodeInspector(app) : new CodeInspector(app, proguardMap);
+    ClassSubject testClass = codeInspector.clazz(TestClass.class);
     assertThat(testClass, isPresent());
 
     // Test the totally unused method.
@@ -147,12 +147,12 @@
         "-dontwarn java.lang.invoke.*"
     );
 
-    AndroidApp app = runShrinkerRaw(shrinker, CLASSES, config);
+    AndroidApp app = runShrinker(shrinker, CLASSES, config);
     assertEquals("123451234567\nABC\n", runOnArt(app, mainClass.getCanonicalName()));
 
-    DexInspector dexInspector =
-        isR8(shrinker) ? new DexInspector(app) : new DexInspector(app, proguardMap);
-    ClassSubject testClass = dexInspector.clazz(TestClass.class);
+    CodeInspector codeInspector =
+        isR8(shrinker) ? new CodeInspector(app) : new CodeInspector(app, proguardMap);
+    ClassSubject testClass = codeInspector.clazz(TestClass.class);
     assertThat(testClass, isPresent());
 
     // Test the totally unused method.
@@ -244,12 +244,12 @@
       ));
     }
 
-    AndroidApp app = runShrinkerRaw(shrinker, CLASSES, config);
+    AndroidApp app = runShrinker(shrinker, CLASSES, config);
     assertEquals("123451234567\nABC\n", runOnArt(app, mainClass.getCanonicalName()));
 
-    DexInspector dexInspector =
-        isR8(shrinker) ? new DexInspector(app) : new DexInspector(app, proguardMap);
-    ClassSubject testClass = dexInspector.clazz(TestClass.class);
+    CodeInspector codeInspector =
+        isR8(shrinker) ? new CodeInspector(app) : new CodeInspector(app, proguardMap);
+    ClassSubject testClass = codeInspector.clazz(TestClass.class);
     assertThat(testClass, isPresent());
 
     // Test the totally unused method.
diff --git a/src/test/java/com/android/tools/r8/naming/b80083341/B80083341.java b/src/test/java/com/android/tools/r8/naming/b80083341/B80083341.java
index 008e4ff..9791142 100644
--- a/src/test/java/com/android/tools/r8/naming/b80083341/B80083341.java
+++ b/src/test/java/com/android/tools/r8/naming/b80083341/B80083341.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.naming.b80083341;
 
 import static com.android.tools.r8.utils.DescriptorUtils.getClassNameFromDescriptor;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
@@ -12,8 +12,8 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.VmTestRunner;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
@@ -40,7 +40,7 @@
         PackagePrivateClass.class, PackagePrivateClass.Itf.class, PackagePrivateClass.Impl.class
     ));
     AndroidApp processedApp = compileWithR8(app, String.join(System.lineSeparator(), config));
-    DexInspector inspector = new DexInspector(processedApp);
+    CodeInspector inspector = new CodeInspector(processedApp);
     ClassSubject mainSubject = inspector.clazz(mainClass);
     assertThat(mainSubject, isPresent());
 
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
index a9eb622..f289674 100644
--- a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
@@ -18,8 +18,8 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import org.junit.Test;
@@ -60,8 +60,8 @@
     Path out = temp.getRoot().toPath();
     AndroidApp processedApp = runR8(originalApp, FieldUpdater.class, out, overloadaggressively);
 
-    DexInspector dexInspector = new DexInspector(processedApp);
-    ClassSubject a = dexInspector.clazz(A.class.getCanonicalName());
+    CodeInspector codeInspector = new CodeInspector(processedApp);
+    ClassSubject a = codeInspector.clazz(A.class.getCanonicalName());
     DexEncodedField f1 = a.field("int", "f1").getField();
     assertNotNull(f1);
     DexEncodedField f2 = a.field("java.lang.Object", "f2").getField();
@@ -112,8 +112,8 @@
     Path out = temp.getRoot().toPath();
     AndroidApp processedApp = runR8(originalApp, FieldResolution.class, out, overloadaggressively);
 
-    DexInspector dexInspector = new DexInspector(processedApp);
-    ClassSubject a = dexInspector.clazz(A.class.getCanonicalName());
+    CodeInspector codeInspector = new CodeInspector(processedApp);
+    ClassSubject a = codeInspector.clazz(A.class.getCanonicalName());
     DexEncodedField f1 = a.field("int", "f1").getField();
     assertNotNull(f1);
     DexEncodedField f3 = a.field(B.class.getCanonicalName(), "f3").getField();
@@ -157,8 +157,8 @@
     Path out = temp.getRoot().toPath();
     AndroidApp processedApp = runR8(originalApp, MethodResolution.class, out, overloadaggressively);
 
-    DexInspector dexInspector = new DexInspector(processedApp);
-    ClassSubject b = dexInspector.clazz(B.class.getCanonicalName());
+    CodeInspector codeInspector = new CodeInspector(processedApp);
+    ClassSubject b = codeInspector.clazz(B.class.getCanonicalName());
     DexEncodedMethod m1 =
         b.method("int", "getF1", ImmutableList.of()).getMethod();
     assertNotNull(m1);
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
index 3e5ba7c..efead2d 100644
--- a/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
@@ -14,10 +14,10 @@
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.jasmin.JasminTestBase;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import java.util.List;
@@ -94,8 +94,8 @@
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
-    DexInspector dexInspector = new DexInspector(app);
-    ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+    CodeInspector codeInspector = new CodeInspector(app);
+    ClassSubject clazz = codeInspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
     FieldSubject f1 = clazz.field("java.lang.String", "same");
     assertTrue(f1.isPresent());
@@ -122,8 +122,8 @@
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
-    DexInspector dexInspector = new DexInspector(app);
-    ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+    CodeInspector codeInspector = new CodeInspector(app);
+    ClassSubject clazz = codeInspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
     FieldSubject f1 = clazz.field("java.lang.String", "same");
     assertTrue(f1.isPresent());
@@ -152,8 +152,8 @@
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
-    DexInspector dexInspector = new DexInspector(app);
-    ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+    CodeInspector codeInspector = new CodeInspector(app);
+    ClassSubject clazz = codeInspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
     FieldSubject f1 = clazz.field("java.lang.String", "same");
     assertTrue(f1.isPresent());
@@ -179,8 +179,8 @@
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
-    DexInspector dexInspector = new DexInspector(app);
-    ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+    CodeInspector codeInspector = new CodeInspector(app);
+    ClassSubject clazz = codeInspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
     FieldSubject f1 = clazz.field("java.lang.String", "same");
     assertTrue(f1.isPresent());
@@ -207,8 +207,8 @@
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
-    DexInspector dexInspector = new DexInspector(app);
-    ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+    CodeInspector codeInspector = new CodeInspector(app);
+    ClassSubject clazz = codeInspector.clazz(CLASS_NAME);
     assertTrue(clazz.isPresent());
     FieldSubject f1 = clazz.field("java.lang.String", "same");
     assertTrue(f1.isPresent());
@@ -263,8 +263,8 @@
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
-    DexInspector dexInspector = new DexInspector(app);
-    ClassSubject clazz = dexInspector.clazz(ANOTHER_CLASS);
+    CodeInspector codeInspector = new CodeInspector(app);
+    ClassSubject clazz = codeInspector.clazz(ANOTHER_CLASS);
     assertTrue(clazz.isPresent());
     MethodSubject m1 = clazz.method("java.lang.String", "same", ImmutableList.of());
     assertTrue(m1.isPresent());
@@ -291,8 +291,8 @@
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
-    DexInspector dexInspector = new DexInspector(app);
-    ClassSubject clazz = dexInspector.clazz(ANOTHER_CLASS);
+    CodeInspector codeInspector = new CodeInspector(app);
+    ClassSubject clazz = codeInspector.clazz(ANOTHER_CLASS);
     assertTrue(clazz.isPresent());
     MethodSubject m1 = clazz.method("java.lang.String", "same", ImmutableList.of());
     assertTrue(m1.isPresent());
@@ -321,8 +321,8 @@
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
-    DexInspector dexInspector = new DexInspector(app);
-    ClassSubject clazz = dexInspector.clazz(ANOTHER_CLASS);
+    CodeInspector codeInspector = new CodeInspector(app);
+    ClassSubject clazz = codeInspector.clazz(ANOTHER_CLASS);
     assertTrue(clazz.isPresent());
     MethodSubject m1 = clazz.method("java.lang.String", "same", ImmutableList.of());
     assertTrue(m1.isPresent());
@@ -348,8 +348,8 @@
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
-    DexInspector dexInspector = new DexInspector(app);
-    ClassSubject clazz = dexInspector.clazz(ANOTHER_CLASS);
+    CodeInspector codeInspector = new CodeInspector(app);
+    ClassSubject clazz = codeInspector.clazz(ANOTHER_CLASS);
     assertTrue(clazz.isPresent());
     MethodSubject m1 = clazz.method("java.lang.String", "same", ImmutableList.of());
     assertTrue(m1.isPresent());
@@ -376,8 +376,8 @@
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
-    DexInspector dexInspector = new DexInspector(app);
-    ClassSubject clazz = dexInspector.clazz(ANOTHER_CLASS);
+    CodeInspector codeInspector = new CodeInspector(app);
+    ClassSubject clazz = codeInspector.clazz(ANOTHER_CLASS);
     assertTrue(clazz.isPresent());
     MethodSubject m1 = clazz.method("java.lang.String", "same", ImmutableList.of());
     assertTrue(m1.isPresent());
@@ -445,8 +445,8 @@
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
-    DexInspector dexInspector = new DexInspector(app);
-    ClassSubject sup = dexInspector.clazz(SUPER_CLASS);
+    CodeInspector codeInspector = new CodeInspector(app);
+    ClassSubject sup = codeInspector.clazz(SUPER_CLASS);
     assertTrue(sup.isPresent());
     MethodSubject m1 = sup.method("java.lang.String", "same", ImmutableList.of());
     assertTrue(m1.isPresent());
@@ -456,7 +456,7 @@
     assertFalse(m2.isRenamed());
     assertEquals(m1.getFinalName(), m2.getFinalName());
 
-    ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
+    ClassSubject sub = codeInspector.clazz(ANOTHER_CLASS);
     assertTrue(sub.isPresent());
     MethodSubject subM1 = sub.method("java.lang.String", "same", ImmutableList.of());
     assertTrue(subM1.isPresent());
@@ -487,8 +487,8 @@
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
-    DexInspector dexInspector = new DexInspector(app);
-    ClassSubject sup = dexInspector.clazz(SUPER_CLASS);
+    CodeInspector codeInspector = new CodeInspector(app);
+    ClassSubject sup = codeInspector.clazz(SUPER_CLASS);
     assertTrue(sup.isPresent());
     MethodSubject m1 = sup.method("java.lang.String", "same", ImmutableList.of());
     assertTrue(m1.isPresent());
@@ -498,7 +498,7 @@
     assertTrue(m2.isRenamed());
     assertEquals(m1.getFinalName(), m2.getFinalName());
 
-    ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
+    ClassSubject sub = codeInspector.clazz(ANOTHER_CLASS);
     assertTrue(sub.isPresent());
     MethodSubject subM1 = sub.method("java.lang.String", "same", ImmutableList.of());
     assertTrue(subM1.isPresent());
@@ -531,8 +531,8 @@
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
-    DexInspector dexInspector = new DexInspector(app);
-    ClassSubject sup = dexInspector.clazz(SUPER_CLASS);
+    CodeInspector codeInspector = new CodeInspector(app);
+    ClassSubject sup = codeInspector.clazz(SUPER_CLASS);
     assertTrue(sup.isPresent());
     MethodSubject m1 = sup.method("java.lang.String", "same", ImmutableList.of());
     assertTrue(m1.isPresent());
@@ -542,7 +542,7 @@
     assertTrue(m2.isRenamed());
     assertEquals(m1.getFinalName(), m2.getFinalName());
 
-    ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
+    ClassSubject sub = codeInspector.clazz(ANOTHER_CLASS);
     assertTrue(sub.isPresent());
     MethodSubject subM1 = sub.method("java.lang.String", "same", ImmutableList.of());
     assertTrue(subM1.isPresent());
@@ -572,8 +572,8 @@
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
-    DexInspector dexInspector = new DexInspector(app);
-    ClassSubject sup = dexInspector.clazz(SUPER_CLASS);
+    CodeInspector codeInspector = new CodeInspector(app);
+    ClassSubject sup = codeInspector.clazz(SUPER_CLASS);
     assertTrue(sup.isPresent());
     MethodSubject m1 = sup.method("java.lang.String", "same", ImmutableList.of());
     assertTrue(m1.isPresent());
@@ -583,7 +583,7 @@
     assertTrue(m2.isRenamed());
     assertNotEquals(m1.getFinalName(), m2.getFinalName());
 
-    ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
+    ClassSubject sub = codeInspector.clazz(ANOTHER_CLASS);
     assertTrue(sub.isPresent());
     MethodSubject subM1 = sub.method("java.lang.String", "same", ImmutableList.of());
     assertTrue(subM1.isPresent());
@@ -614,9 +614,9 @@
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
-    DexInspector dexInspector = new DexInspector(app);
+    CodeInspector codeInspector = new CodeInspector(app);
 
-    ClassSubject sup = dexInspector.clazz(SUPER_CLASS);
+    ClassSubject sup = codeInspector.clazz(SUPER_CLASS);
     assertTrue(sup.isPresent());
     MethodSubject m1 = sup.method("java.lang.String", "same", ImmutableList.of());
     assertTrue(m1.isPresent());
@@ -626,7 +626,7 @@
     assertTrue(m2.isRenamed());
     assertEquals(m1.getFinalName(), m2.getFinalName());
 
-    ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
+    ClassSubject sub = codeInspector.clazz(ANOTHER_CLASS);
     assertTrue(sub.isPresent());
     MethodSubject subM1 = sub.method("java.lang.String", "same", ImmutableList.of());
     assertTrue(subM1.isPresent());
diff --git a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
index c929eaf..73dcdb3 100644
--- a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
+++ b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
@@ -7,30 +7,61 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
-import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.CfInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.DexInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.function.BiConsumer;
 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 NeverReturnsNormallyTest extends TestBase {
+
+  private Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public NeverReturnsNormallyTest(Backend backend) {
+    this.backend = backend;
+  }
+
   private void runTest(
-      BiConsumer<DexInspector, CompilationMode> inspection,
+      BiConsumer<CodeInspector, CompilationMode> inspection,
       boolean enableClassInliner, CompilationMode mode) throws Exception {
     R8Command.Builder builder = R8Command.builder();
     builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
-    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+    assert (backend == Backend.DEX || backend == Backend.CF);
+    builder.setProgramConsumer(
+        backend == Backend.DEX
+            ? DexIndexedConsumer.emptyConsumer()
+            : ClassFileConsumer.emptyConsumer());
+    builder.addLibraryFiles(
+        backend == Backend.DEX
+            ? ToolHelper.getDefaultAndroidJar()
+            : ToolHelper.getJava8RuntimeJar());
     builder.setMode(mode);
     builder.addProguardConfiguration(
         ImmutableList.of(
@@ -46,18 +77,28 @@
             "}",
             "",
             "-dontobfuscate",
-            "-allowaccessmodification"
-        ),
+            "-allowaccessmodification"),
         Origin.unknown());
-    AndroidApp app = ToolHelper.runR8(builder.build(),
-        opts -> opts.enableClassInlining = enableClassInliner);
-    inspection.accept(new DexInspector(app), mode);
+    AndroidApp app =
+        ToolHelper.runR8(builder.build(), opts -> opts.enableClassInlining = enableClassInliner);
+    inspection.accept(
+        new CodeInspector(
+            app,
+            options -> {
+              options.enableCfFrontend = true;
+            }),
+        mode);
 
-    // Run on Art to check generated code against verifier.
-    runOnArt(app, TestClass.class);
+    if (backend == Backend.DEX) {
+      // Run on Art to check generated code against verifier.
+      runOnArt(app, TestClass.class);
+    } else {
+      runOnJava(app, TestClass.class);
+    }
   }
 
-  private void validate(DexInspector inspector, CompilationMode mode) {
+  private void validate(CodeInspector inspector, CompilationMode mode) {
+    assert (backend == Backend.DEX || backend == Backend.CF);
     ClassSubject clazz = inspector.clazz(TestClass.class);
     assertTrue(clazz.isPresent());
 
@@ -77,37 +118,34 @@
     assertTrue(methodThrowToBeInlined.isPresent());
     Iterator<InstructionSubject> instructions = methodThrowToBeInlined.iterateInstructions();
     // Call, followed by throw null.
-    assertTrue(nextInstruction(instructions).isConstString());
-    InstructionSubject insn = nextInstruction(instructions);
+    InstructionSubject insn = nextInstructionSkippingCfPositionAndLabel(instructions);
+    assertTrue(insn != null && insn.isConstString(JumboStringMode.ALLOW));
+    insn = nextInstruction(instructions);
     assertTrue(insn.isInvoke());
     assertTrue(((InvokeInstructionSubject) insn)
         .invokedMethod().name.toString().equals("throwNpe"));
-    assertTrue(nextInstruction(instructions).isConst4());
-    assertTrue(nextInstruction(instructions).isThrow());
-    assertFalse(instructions.hasNext());
+    verifyTrailingPattern(instructions);
 
     // Check the instruction used for testInlinedIntoVoidMethod
     MethodSubject methodTestInlinedIntoVoidMethod =
         clazz.method("void", "testInlinedIntoVoidMethod", ImmutableList.of());
     assertTrue(methodTestInlinedIntoVoidMethod.isPresent());
     instructions = methodTestInlinedIntoVoidMethod.iterateInstructions();
+    insn = nextInstructionSkippingCfPositionAndLabel(instructions);
     if (mode == CompilationMode.DEBUG) {
       // Not inlined call to throwToBeInlined.
-      insn = nextInstruction(instructions);
       assertTrue(insn.isInvoke());
       assertTrue(((InvokeInstructionSubject) insn)
           .invokedMethod().name.toString().equals("throwToBeInlined"));
     } else {
       // Inlined code from throwToBeInlined.
-      assertTrue(nextInstruction(instructions).isConstString());
+      assertTrue(insn.isConstString(JumboStringMode.ALLOW));
       insn = nextInstruction(instructions);
       assertTrue(insn.isInvoke());
       assertTrue(((InvokeInstructionSubject) insn)
           .invokedMethod().name.toString().equals("throwNpe"));
     }
-    assertTrue(nextInstruction(instructions).isConst4());
-    assertTrue(nextInstruction(instructions).isThrow());
-    assertFalse(instructions.hasNext());
+    verifyTrailingPattern(instructions);
 
     // Check the instruction used for testInlinedIntoVoidMethod
     MethodSubject methodOuterTrivial =
@@ -115,13 +153,11 @@
     assertTrue(methodOuterTrivial.isPresent());
     instructions = methodOuterTrivial.iterateInstructions();
     // Call, followed by [nop, goto]
-    insn = nextInstruction(instructions);
+    insn = nextInstructionSkippingCfPositionAndLabel(instructions);
     assertTrue(insn.isInvoke());
     assertTrue(((InvokeInstructionSubject) insn)
         .invokedMethod().name.toString().equals("innerNotReachable"));
-    assertTrue(nextInstruction(instructions).isConst4());
-    assertTrue(nextInstruction(instructions).isThrow());
-    assertFalse(instructions.hasNext());
+    verifyTrailingPattern(instructions);
   }
 
   private InstructionSubject nextInstruction(Iterator<InstructionSubject> instructions) {
@@ -129,6 +165,39 @@
     return instructions.next();
   }
 
+  private InstructionSubject nextInstructionSkippingCfPositionAndLabel(
+      Iterator<InstructionSubject> instructions) {
+    InstructionSubject insn = null;
+    while (instructions.hasNext()) {
+      insn = instructions.next();
+      if (!(insn instanceof CfInstructionSubject)) {
+        break;
+      }
+      CfInstructionSubject cfInsn = (CfInstructionSubject) insn;
+      if (!cfInsn.isLabel() && !cfInsn.isPosition()) {
+        break;
+      }
+    }
+    return insn;
+  }
+
+  private void verifyTrailingPattern(Iterator<InstructionSubject> instructions) {
+    InstructionSubject insn = nextInstruction(instructions);
+    if (backend == Backend.DEX) {
+      assertTrue(
+          insn instanceof DexInstructionSubject && ((DexInstructionSubject) insn).isConst4());
+    } else {
+      assertTrue(insn instanceof CfInstructionSubject);
+      assertTrue(((CfInstructionSubject) insn).isStackInstruction(Opcode.Pop));
+      assertTrue(instructions.hasNext());
+      insn = instructions.next();
+      assertTrue(insn instanceof CfInstructionSubject);
+      assertTrue(((CfInstructionSubject) insn).isConstNull());
+    }
+    assertTrue(nextInstruction(instructions).isThrow());
+    assertFalse(instructions.hasNext());
+  }
+
   @Test
   public void test() throws Exception {
     runTest(this::validate, true, CompilationMode.DEBUG);
diff --git a/src/test/java/com/android/tools/r8/regress/B76025099.java b/src/test/java/com/android/tools/r8/regress/B76025099.java
index 0a0e861..c7a093f 100644
--- a/src/test/java/com/android/tools/r8/regress/B76025099.java
+++ b/src/test/java/com/android/tools/r8/regress/B76025099.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.regress;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
@@ -19,10 +19,10 @@
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.io.File;
 import java.nio.file.Files;
@@ -104,7 +104,7 @@
 
   private void verifyFieldAccess(AndroidApp processedApp, ProcessResult jvmOutput)
       throws Exception {
-    DexInspector inspector = new DexInspector(processedApp);
+    CodeInspector inspector = new CodeInspector(processedApp);
     ClassSubject impl = inspector.clazz(Impl.class);
     assertThat(impl, isPresent());
     MethodSubject init = impl.init(ImmutableList.of("java.lang.String"));
diff --git a/src/test/java/com/android/tools/r8/regress/Regress37740372.java b/src/test/java/com/android/tools/r8/regress/Regress37740372.java
index 64df6ed..e6ac9f8 100644
--- a/src/test/java/com/android/tools/r8/regress/Regress37740372.java
+++ b/src/test/java/com/android/tools/r8/regress/Regress37740372.java
@@ -18,8 +18,8 @@
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.util.Base64;
 import java.util.Set;
 import org.junit.Test;
@@ -129,7 +129,7 @@
   }
 
   private void checkApplicationOnlyHasJavaLangObject(AndroidApp app) throws Throwable {
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     inspector.forAllClasses(this::assertIsJavaLangObjet);
   }
 
diff --git a/src/test/java/com/android/tools/r8/regress/b111080693/B111080693.java b/src/test/java/com/android/tools/r8/regress/b111080693/B111080693.java
new file mode 100644
index 0000000..f079016
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b111080693/B111080693.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b111080693;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.regress.b111080693.a.Observable;
+import com.android.tools.r8.regress.b111080693.b.RecyclerView;
+import com.android.tools.r8.utils.AndroidApp;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class B111080693 extends TestBase {
+  @Test
+  public void test() throws Exception {
+    R8Command.Builder builder = R8Command.builder();
+    builder.addProgramFiles(
+        ToolHelper.getClassFilesForTestPackage(Observable.class.getPackage()));
+    builder.addProgramFiles(
+        ToolHelper.getClassFilesForTestPackage(RecyclerView.class.getPackage()));
+    builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestMain.class));
+    builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestMain.TestAdapter.class));
+    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+    builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
+    String config = keepMainProguardConfiguration(TestMain.class);
+    builder.addProguardConfiguration(
+        ImmutableList.of(config,
+            "-keepattributes Signature, InnerClasses, EnclosingMethod, *Annotation*"),
+        Origin.unknown());
+    AndroidApp app = ToolHelper.runR8(builder.build());
+    ProcessResult result = runOnArtRaw(app, TestMain.class);
+    assertEquals(0, result.exitCode);
+    assertThat(result.stderr, not(containsString("IllegalAccessError")));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b111080693/TestMain.java b/src/test/java/com/android/tools/r8/regress/b111080693/TestMain.java
new file mode 100644
index 0000000..8979250
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b111080693/TestMain.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b111080693;
+
+import com.android.tools.r8.regress.b111080693.b.RecyclerView;
+import com.android.tools.r8.regress.b111080693.b.RecyclerView.Adapter;
+
+public class TestMain {
+  static final class TestAdapter extends Adapter {
+    TestAdapter() {
+    }
+  }
+
+  public static void main(String[] args) {
+    TestAdapter adapter = new TestAdapter();
+    RecyclerView view = new RecyclerView();
+    view.setAdapter(adapter);
+    adapter.notifyDataSetChanged();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b111080693/a/Observable.java b/src/test/java/com/android/tools/r8/regress/b111080693/a/Observable.java
new file mode 100644
index 0000000..c7a4858
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b111080693/a/Observable.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b111080693.a;
+
+import java.util.ArrayList;
+
+public abstract class Observable<T> {
+  /**
+   * The list of observers.  An observer can be in the list at most
+   * once and will never be null.
+   */
+  protected final ArrayList<T> mObservers = new ArrayList<T>();
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b111080693/b/RecyclerView.java b/src/test/java/com/android/tools/r8/regress/b111080693/b/RecyclerView.java
new file mode 100644
index 0000000..06347fe
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b111080693/b/RecyclerView.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b111080693.b;
+
+import com.android.tools.r8.regress.b111080693.a.Observable;
+
+public class RecyclerView {
+
+  final State mState = new State();
+
+  public static class State {
+    boolean mStructureChanged = false;
+  }
+
+  public abstract static class AdapterDataObserver {
+    public void onChanged() {
+      // Do nothing
+    }
+  }
+
+  private class RecyclerViewDataObserver extends AdapterDataObserver {
+    RecyclerViewDataObserver() {
+    }
+
+    @Override
+    public void onChanged() {
+      // This is the single target of AdapterDataObserver#onChanged(), and could be inlined to
+      // AdapterDataObservable#notifyChanged() as long as this preserves null check of the receiver.
+      // To do so, access the enclosing class' member to use the receiver.
+      mState.mStructureChanged = true;
+    }
+  }
+
+  static class AdapterDataObservable extends Observable<AdapterDataObserver> {
+    public void registerObserver(AdapterDataObserver observer) {
+      mObservers.add(observer);
+    }
+    public void notifyChanged() {
+      for (int i = mObservers.size() - 1; i >= 0; i--) {
+        // The single target, RecyclerViewDataObserver#onChange is inlined, along with check-cast:
+        //    AdapterDataObserver observer_i = mObservers.get(i);
+        //    RecyclerViewDataObserver casted_obs = (RecyclerViewDataObserver) observer_i;
+        //    // inlining RecyclerViewDataObserver#onChanged():
+        mObservers.get(i).onChanged();
+      }
+    }
+  }
+
+  public abstract static class Adapter {
+    private final AdapterDataObservable mObservable = new AdapterDataObservable();
+
+    public void registerAdapterDataObserver(AdapterDataObserver observer) {
+      mObservable.registerObserver(observer);
+    }
+
+    public final void notifyDataSetChanged() {
+      // Single callee, AdapterDataObservable#notifyChanged(), could be inlined, but should not.
+      // Accessing AdapterDataObservable.mObservers, which is a protected field in Observable,
+      // results in an illegal access error.
+      //
+      // Without the above inlining, the inlining constraint for the target here is SUBCLASS due to
+      // that protected field, and thus decline to inline because the holder, Adapter, is not a
+      // subtype of the target holder, AdapterDataObservable.
+      // However, after the above inlining, check-cast to RecyclerViewDataObserver overrides that
+      // condition to PACKAGE, which accidentally allows the target to be inlined here.
+      mObservable.notifyChanged();
+    }
+  }
+
+  private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
+
+  public void setAdapter(Adapter adapter) {
+    adapter.registerAdapterDataObserver(mObserver);
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java b/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
index 90fba5f..91d8460 100644
--- a/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
+++ b/src/test/java/com/android/tools/r8/regress/b111250398/B111250398.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.regress.b111250398;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
@@ -15,11 +15,11 @@
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
 import org.junit.Test;
@@ -91,6 +91,16 @@
     t = f;
   }
 
+  public void mfWithMonitor() {
+    t = f;
+    synchronized (this) {
+      t = f;
+      t = f;
+    }
+    t = f;
+    t = f;
+  }
+
   public void msf() {
     t = sf;
     t = sf;
@@ -226,11 +236,13 @@
         .count();
   }
 
-  private void check(DexInspector inspector, int mfOnBGets, int msfOnBGets) {
+  private void check(CodeInspector inspector, int mfOnBGets, int msfOnBGets) {
     ClassSubject classA = inspector.clazz(A.class);
     assertThat(classA, isPresent());
     MethodSubject mfOnA = classA.method("void", "mf", ImmutableList.of());
     assertThat(mfOnA, isPresent());
+    MethodSubject mfWithMonitorOnA = classA.method("void", "mfWithMonitor", ImmutableList.of());
+    assertThat(mfWithMonitorOnA, isPresent());
     MethodSubject msfOnA = classA.method("void", "msf", ImmutableList.of());
     assertThat(msfOnA, isPresent());
     MethodSubject mvOnA = classA.method("void", "mv", ImmutableList.of());
@@ -264,6 +276,13 @@
     // compilation (R8) will eliminate field loads on non-volatile fields.
     assertEquals(1, countIget(mfOnA.getMethod().getCode().asDexCode(), fOnA.getField().field));
     assertEquals(1, countSget(msfOnA.getMethod().getCode().asDexCode(), sfOnA.getField().field));
+    // TODO(111380066). This could be 2 in stead of 4, but right now the optimization tracks the
+    // combined set of fields for all successors, and for synchronized code all blocks have
+    // exceptional edges for ensuring monitor exit causing the active load to be invalidated for
+    // both normal and exceptional successors.
+    assertEquals(4,
+        countIget(mfWithMonitorOnA.getMethod().getCode().asDexCode(), fOnA.getField().field));
+
     // For fields on other class both separate compilation (D8) and whole program
     // compilation (R8) will differ in the eliminated field loads of non-volatile fields.
     assertEquals(mfOnBGets,
@@ -274,22 +293,20 @@
 
   @Test
   public void testSeparateCompilation() throws Exception {
-    DexInspector inspector =
-        new DexInspector(compileWithD8(readClasses(A.class, B.class), this::releaseMode));
+    CodeInspector inspector =
+        new CodeInspector(compileWithD8(readClasses(A.class, B.class), this::releaseMode));
     check(inspector, 5, 5);
   }
 
   @Test
   public void testWholeProgram() throws Exception {
-    DexInspector inspector =
-        new DexInspector(compileWithR8(readClasses(A.class, B.class), this::releaseMode));
-    // The reason for getting two Igets in B.mf is that the first Iget inserts a NonNull
-    // instruction which creates a new value for the remaining Igets.
-    check(inspector, 2, 1);
+    CodeInspector inspector =
+        new CodeInspector(compileWithR8(readClasses(A.class, B.class), this::releaseMode));
+    check(inspector, 1, 1);
   }
 
   private void checkMixed(AndroidApp app) throws Exception{
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     ClassSubject classC = inspector.clazz(C.class);
     assertThat(classC, isPresent());
     MethodSubject totalDays = classC.method("int", "totalDays", ImmutableList.of());
@@ -324,7 +341,7 @@
   }
 
   private void checkDaggerSingleProviderGet(AndroidApp app) throws Exception {
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     MethodSubject get =
         inspector.clazz(SingleCheck.class).method("java.lang.Object", "get", ImmutableList.of());
     assertThat(get, isPresent());
diff --git a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
index a185f4c..911ceb7 100644
--- a/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b69825683/Regress69825683Test.java
@@ -7,19 +7,35 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class Regress69825683Test extends TestBase {
+  private Backend backend;
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public Regress69825683Test(Backend backend) {
+    this.backend = backend;
+  }
 
   @Test
   public void outerConstructsInner() throws Exception {
@@ -32,9 +48,18 @@
         "}",
         "-dontobfuscate"),
         Origin.unknown());
-    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+    if (backend == Backend.DEX) {
+      builder
+          .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+          .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+    } else {
+      assert backend == Backend.CF;
+      builder
+          .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+          .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+    }
     AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app, o -> o.enableCfFrontend = true);
     List<FoundClassSubject> classes = inspector.allClasses();
 
     // Check that the synthetic class is still present.
@@ -51,7 +76,9 @@
     String innerName = innerClass.getCanonicalName();
     int index = innerName.lastIndexOf('.');
     innerName = innerName.substring(0, index) + "$" + innerName.substring(index + 1);
-    assertTrue(runOnArt(app, mainClass).startsWith(innerName));
+    assertTrue(
+        (backend == Backend.DEX ? runOnArt(app, mainClass) : runOnJava(app, mainClass))
+            .startsWith(innerName));
   }
 
   @Test
@@ -65,9 +92,18 @@
         "}",
         "-dontobfuscate"),
         Origin.unknown());
-    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+    if (backend == Backend.DEX) {
+      builder
+          .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+          .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+    } else {
+      assert backend == Backend.CF;
+      builder
+          .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+          .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+    }
     AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app, o -> o.enableCfFrontend = true);
     List<FoundClassSubject> classes = inspector.allClasses();
 
     // Check that the synthetic class is still present.
@@ -79,6 +115,8 @@
             .count());
 
     // Run code to check that the constructor with synthetic class as argument is present.
-    assertTrue(runOnArt(app, mainClass).startsWith(mainClass.getCanonicalName()));
+    assertTrue(
+        (backend == Backend.DEX ? runOnArt(app, mainClass) : runOnJava(app, mainClass))
+            .startsWith(mainClass.getCanonicalName()));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/regress/b71604169/Regress71604169Test.java b/src/test/java/com/android/tools/r8/regress/b71604169/Regress71604169Test.java
index f40ecfd..cc356c3 100644
--- a/src/test/java/com/android/tools/r8/regress/b71604169/Regress71604169Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b71604169/Regress71604169Test.java
@@ -6,15 +6,32 @@
 
 import static junit.framework.TestCase.assertEquals;
 
+import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.origin.Origin;
 import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class Regress71604169Test extends TestBase {
+  private Backend backend;
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public Regress71604169Test(Backend backend) {
+    this.backend = backend;
+  }
+
   @Test
   public void test() throws Exception {
     R8Command.Builder builder = R8Command.builder();
@@ -28,7 +45,20 @@
     builder.addProguardConfiguration(
         ImmutableList.of(keepMainProguardConfiguration(mainClass, true, false)), Origin.unknown());
 
-    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    assertEquals("Hello, world!", runOnArt(ToolHelper.runR8(builder.build()), mainClass));
+    if (backend == Backend.DEX) {
+      builder
+          .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+          .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+    } else {
+      assert backend == Backend.CF;
+      builder
+          .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+          .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+    }
+    assertEquals(
+        "Hello, world!",
+        backend == Backend.DEX
+            ? runOnArt(ToolHelper.runR8(builder.build()), mainClass)
+            : runOnJava(ToolHelper.runR8(builder.build()), mainClass));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java b/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java
index 03614b9..a052d86 100644
--- a/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java
+++ b/src/test/java/com/android/tools/r8/regress/b77496850/B77496850.java
@@ -15,9 +15,9 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
@@ -431,7 +431,7 @@
 
   private void checkPathParserMethods(AndroidApp app, Class testClass, int a, int b)
       throws Exception {
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     DexItemFactory factory = inspector.getFactory();
     ClassSubject clazz = inspector.clazz(testClass);
     MethodSubject drawArc = clazz.method(
@@ -484,7 +484,7 @@
       assert compiler == Tool.R8;
       app = compileWithR8(app, "-keep class * { *; }", o -> o.minApiLevel = apiLevel.getLevel());
     }
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     DexItemFactory factory = inspector.getFactory();
     ClassSubject clazz = inspector.clazz(testClass);
     MethodSubject arcToBezier = clazz.method(
diff --git a/src/test/java/com/android/tools/r8/resolution/B77944861.java b/src/test/java/com/android/tools/r8/resolution/B77944861.java
index aa5552a..2118b44 100644
--- a/src/test/java/com/android/tools/r8/resolution/B77944861.java
+++ b/src/test/java/com/android/tools/r8/resolution/B77944861.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.resolution;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
@@ -19,10 +19,10 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -64,8 +64,8 @@
     ProcessResult jvmOutput = ToolHelper.runJava(ImmutableList.of(jarPath), mainName);
     assertEquals(0, jvmOutput.exitCode);
     AndroidApp processedApp = runR8(readJar(jarPath), SomeView.class, out);
-    DexInspector dexInspector = new DexInspector(processedApp);
-    ClassSubject view = dexInspector.clazz("regress_77944861.SomeView");
+    CodeInspector codeInspector = new CodeInspector(processedApp);
+    ClassSubject view = codeInspector.clazz("regress_77944861.SomeView");
     assertThat(view, isPresent());
     String className = "regress_77944861.inner.TopLevelPolicy$MobileIconState";
     MethodSubject initView = view.method("java.lang.String", "get", ImmutableList.of(className));
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index 22cab2a..23c8933 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.resolution.singletarget.Main;
 import com.android.tools.r8.resolution.singletarget.one.AbstractSubClass;
 import com.android.tools.r8.resolution.singletarget.one.AbstractTopClass;
@@ -107,8 +108,13 @@
     ExecutorService executor = Executors.newSingleThreadExecutor();
     RootSet rootSet = new RootSetBuilder(appInfoWithSubtyping, application,
         buildKeepRuleForClass(Main.class, application.dexItemFactory), options).run(executor);
-    appInfo = new Enqueuer(appInfoWithSubtyping, options, options.forceProguardCompatibility)
-        .traceApplication(rootSet, executor, timing);
+    appInfo =
+        new Enqueuer(
+                appInfoWithSubtyping,
+                GraphLense.getIdentityLense(),
+                options,
+                options.forceProguardCompatibility)
+            .traceApplication(rootSet, executor, timing);
     // We do not run the tree pruner to ensure that the hierarchy is as designed and not modified
     // due to liveness.
   }
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java b/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java
new file mode 100644
index 0000000..772ef09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/ChromuimAssertionHookMock.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.rewrite.assertions;
+
+public class ChromuimAssertionHookMock {
+  public static void assertFailureHandler(AssertionError assertion) {
+    System.out.println("Got AssertionError " + assertion);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java b/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
index cc56790..49fe85e 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/ClassWithAssertions.java
@@ -16,7 +16,9 @@
   }
 
   int getX() {
+    System.out.println("1");
     assert condition();
+    System.out.println("2");
     return x;
   }
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
index 3c724c0..fff9ac3 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
@@ -4,17 +4,139 @@
 
 package com.android.tools.r8.rewrite.assertions;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+import java.util.function.Function;
 import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+// This ASM class visitor has been adapted from
+// https://chromium.googlesource.com/chromium/src/+/164e81fcd0828b40f5496e9025349ea728cde7f5/build/android/bytecode/java/org/chromium/bytecode/AssertionEnablerClassAdapter.java
+// See b/110887293.
+
+/**
+ * An ClassVisitor for replacing Java ASSERT statements with a function by modifying Java bytecode.
+ *
+ * We do this in two steps, first step is to enable assert.
+ * Following bytecode is generated for each class with ASSERT statements:
+ * 0: ldc #8 // class CLASSNAME
+ * 2: invokevirtual #9 // Method java/lang/Class.desiredAssertionStatus:()Z
+ * 5: ifne 12
+ * 8: iconst_1
+ * 9: goto 13
+ * 12: iconst_0
+ * 13: putstatic #2 // Field $assertionsDisabled:Z
+ * Replaces line #13 to the following:
+ * 13: pop
+ * Consequently, $assertionsDisabled is assigned the default value FALSE.
+ * This is done in the first if statement in overridden visitFieldInsn. We do this per per-assert.
+ *
+ * Second step is to replace assert statement with a function:
+ * The followed instructions are generated by a java assert statement:
+ * getstatic     #3     // Field $assertionsDisabled:Z
+ * ifne          118    // Jump to instruction as if assertion if not enabled
+ * ...
+ * ifne          19
+ * new           #4     // class java/lang/AssertionError
+ * dup
+ * ldc           #5     // String (don't have this line if no assert message given)
+ * invokespecial #6     // Method java/lang/AssertionError.
+ * athrow
+ * Replace athrow with:
+ * invokestatic  #7     // Method org/chromium/base/JavaExceptionReporter.assertFailureHandler
+ * goto          118
+ * JavaExceptionReporter.assertFailureHandler is a function that handles the AssertionError,
+ * 118 is the instruction to execute as if assertion if not enabled.
+ */
+class AssertionEnablerClassAdapter extends ClassVisitor {
+  AssertionEnablerClassAdapter(ClassVisitor visitor) {
+    super(Opcodes.ASM6, visitor);
+  }
+
+  @Override
+  public MethodVisitor visitMethod(final int access, final String name, String desc,
+      String signature, String[] exceptions) {
+    return new RewriteAssertMethodVisitor(
+        Opcodes.ASM5, super.visitMethod(access, name, desc, signature, exceptions));
+  }
+
+  static class RewriteAssertMethodVisitor extends MethodVisitor {
+    static final String ASSERTION_DISABLED_NAME = "$assertionsDisabled";
+    static final String INSERT_INSTRUCTION_NAME = "assertFailureHandler";
+    static final String INSERT_INSTRUCTION_DESC =
+        Type.getMethodDescriptor(Type.VOID_TYPE, Type.getObjectType("java/lang/AssertionError"));
+    static final boolean INSERT_INSTRUCTION_ITF = false;
+
+    boolean mStartLoadingAssert;
+    Label mGotoLabel;
+
+    public RewriteAssertMethodVisitor(int api, MethodVisitor mv) {
+      super(api, mv);
+    }
+
+    @Override
+    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+      if (opcode == Opcodes.PUTSTATIC && name.equals(ASSERTION_DISABLED_NAME)) {
+        super.visitInsn(Opcodes.POP); // enable assert
+      } else if (opcode == Opcodes.GETSTATIC && name.equals(ASSERTION_DISABLED_NAME)) {
+        mStartLoadingAssert = true;
+        super.visitFieldInsn(opcode, owner, name, desc);
+      } else {
+        super.visitFieldInsn(opcode, owner, name, desc);
+      }
+    }
+
+    @Override
+    public void visitJumpInsn(int opcode, Label label) {
+      if (mStartLoadingAssert && opcode == Opcodes.IFNE && mGotoLabel == null) {
+        mGotoLabel = label;
+      }
+      super.visitJumpInsn(opcode, label);
+    }
+
+    @Override
+    public void visitInsn(int opcode) {
+      if (!mStartLoadingAssert || opcode != Opcodes.ATHROW) {
+        super.visitInsn(opcode);
+      } else {
+        super.visitMethodInsn(
+            Opcodes.INVOKESTATIC,
+            ChromuimAssertionHookMock.class.getCanonicalName().replace('.', '/'),
+            INSERT_INSTRUCTION_NAME,
+            INSERT_INSTRUCTION_DESC,
+            INSERT_INSTRUCTION_ITF);
+        super.visitJumpInsn(Opcodes.GOTO, mGotoLabel);
+        mStartLoadingAssert = false;
+        mGotoLabel = null;
+      }
+    }
+  }
+}
 
 public class RemoveAssertionsTest extends TestBase {
 
@@ -27,7 +149,7 @@
         ImmutableList.of(testClass),
         keepMainProguardConfiguration(testClass, true, false),
         options -> options.enableInlining = false);
-    DexInspector x = new DexInspector(app);
+    CodeInspector x = new CodeInspector(app);
 
     ClassSubject clazz = x.clazz(ClassWithAssertions.class);
     assertTrue(clazz.isPresent());
@@ -38,4 +160,76 @@
         clazz.method(new MethodSignature(Constants.CLASS_INITIALIZER_NAME, "void", new String[]{}));
     assertTrue(!clinit.isPresent());
   }
+
+  private Path buildTestToCf(Consumer<InternalOptions> consumer) throws Exception {
+    Path outputJar = temp.getRoot().toPath().resolve("output.jar");
+    R8Command command =
+        ToolHelper.prepareR8CommandBuilder(readClasses(ClassWithAssertions.class))
+            .setMode(CompilationMode.DEBUG)
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+            .setOutput(outputJar, OutputMode.ClassFile)
+            .build();
+    ToolHelper.runR8(command, consumer);
+    return outputJar;
+  }
+
+  @Test
+  public void testCfOutput() throws Exception {
+    String main = ClassWithAssertions.class.getCanonicalName();
+    ProcessResult result;
+    // Assertion is hit.
+    result = ToolHelper.runJava(buildTestToCf(options -> {}), "-ea", main, "0");
+    assertEquals(1, result.exitCode);
+    assertEquals("1\n".replace("\n", System.lineSeparator()), result.stdout);
+    // Assertion is not hit.
+    result = ToolHelper.runJava(buildTestToCf(options -> {}), "-ea", main, "1");
+    assertEquals(0, result.exitCode);
+    assertEquals("1\n2\n".replace("\n", System.lineSeparator()), result.stdout);
+    // Assertion is hit, but removed.
+    result = ToolHelper.runJava(
+        buildTestToCf(
+            options -> options.disableAssertions = true), "-ea", main, "0");
+    assertEquals(0, result.exitCode);
+    assertEquals("1\n2\n".replace("\n", System.lineSeparator()), result.stdout);
+  }
+
+  private byte[] identity(byte[] classBytes) {
+    return classBytes;
+  }
+
+  private byte[] chromiumAssertionEnabler(byte[] classBytes) {
+    ClassWriter writer = new ClassWriter(0);
+    new ClassReader(classBytes).accept(new AssertionEnablerClassAdapter(writer), 0);
+    return writer.toByteArray();
+  }
+
+  private AndroidApp runRegress110887293(Function<byte[], byte[]> rewriter) throws Exception {
+    return ToolHelper.runR8(
+        R8Command.builder()
+            .addClassProgramData(
+                rewriter.apply(ToolHelper.getClassAsBytes(ClassWithAssertions.class)),
+                Origin.unknown())
+            .addClassProgramData(
+                ToolHelper.getClassAsBytes(ChromuimAssertionHookMock.class), Origin.unknown())
+            .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+            .setMode(CompilationMode.DEBUG)
+            .build());
+  }
+
+  @Test
+  public void regress110887293() throws Exception {
+    AndroidApp app;
+    // Assertions removed for default assertion code.
+    app = runRegress110887293(this::identity);
+    assertEquals("1\n2\n", runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "0"));
+    assertEquals("1\n2\n", runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "1"));
+    // Assertions not removed when default assertion code is not present.
+    app = runRegress110887293(this::chromiumAssertionEnabler);
+    assertEquals(
+        "1\nGot AssertionError java.lang.AssertionError\n2\n".replace("\n", System.lineSeparator()),
+        runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "0"));
+    assertEquals(
+        "1\n2\n".replace("\n", System.lineSeparator()),
+        runOnArt(app, ClassWithAssertions.class.getCanonicalName(), "1"));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/rewrite/longcompare/LongCompare.java b/src/test/java/com/android/tools/r8/rewrite/longcompare/LongCompare.java
index e0d9ecf..16b7922 100644
--- a/src/test/java/com/android/tools/r8/rewrite/longcompare/LongCompare.java
+++ b/src/test/java/com/android/tools/r8/rewrite/longcompare/LongCompare.java
@@ -9,12 +9,12 @@
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
-import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -65,8 +65,8 @@
 
     Path dexPath = outputPath.resolve("classes.dex");
 
-    DexInspector dexInspector = new DexInspector(dexPath);
-    ClassSubject classSubject = dexInspector.clazz("rewrite.LongCompare");
+    CodeInspector codeInspector = new CodeInspector(dexPath);
+    ClassSubject classSubject = codeInspector.clazz("rewrite.LongCompare");
     MethodSubject methodSubject = classSubject
         .method("int", "simpleCompare", Arrays.asList("long", "long"));
     // Check that exception handler is removed since it is no longer needed.
diff --git a/src/test/java/com/android/tools/r8/rewrite/longcompare/RequireNonNullRewriteTest.java b/src/test/java/com/android/tools/r8/rewrite/longcompare/RequireNonNullRewriteTest.java
index a50a667..278bc8b 100644
--- a/src/test/java/com/android/tools/r8/rewrite/longcompare/RequireNonNullRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/longcompare/RequireNonNullRewriteTest.java
@@ -11,11 +11,11 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
-import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -75,8 +75,8 @@
 
     Path dexPath = outputPath.resolve("classes.dex");
 
-    DexInspector dexInspector = new DexInspector(dexPath);
-    ClassSubject classSubject = dexInspector.clazz("rewrite.RequireNonNull");
+    CodeInspector codeInspector = new CodeInspector(dexPath);
+    ClassSubject classSubject = codeInspector.clazz("rewrite.RequireNonNull");
     MethodSubject methodSubject = classSubject
         .method("java.lang.Object", "nonnullRemove", Collections.emptyList());
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
index 087b5f8..d3784ee 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
@@ -25,9 +25,9 @@
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import org.junit.Test;
 
 public class StaticValuesTest extends SmaliTestBase {
@@ -94,7 +94,7 @@
     AndroidApp originalApplication = buildApplication(builder);
     AndroidApp processedApplication = processApplication(originalApplication);
 
-    DexInspector inspector = new DexInspector(processedApplication);
+    CodeInspector inspector = new CodeInspector(processedApplication);
     // Test is running without tree-shaking, so the empty <clinit> is not removed.
     assertTrue(
         inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
@@ -178,7 +178,7 @@
     AndroidApp originalApplication = buildApplication(builder);
     AndroidApp processedApplication = processApplication(originalApplication);
 
-    DexInspector inspector = new DexInspector(processedApplication);
+    CodeInspector inspector = new CodeInspector(processedApplication);
     MethodSubject clinit = inspector.clazz("Test").clinit();
     // Nothing changed in the class initializer.
     assertEquals(5, clinit.getMethod().getCode().asDexCode().instructions.length);
@@ -219,7 +219,7 @@
     AndroidApp originalApplication = buildApplication(builder);
     AndroidApp processedApplication = processApplication(originalApplication);
 
-    DexInspector inspector = new DexInspector(processedApplication);
+    CodeInspector inspector = new CodeInspector(processedApplication);
     // Test is running without tree-shaking, so the empty <clinit> is not removed.
     assertTrue(
         inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
@@ -261,7 +261,7 @@
     AndroidApp originalApplication = buildApplication(builder);
     AndroidApp processedApplication = processApplication(originalApplication);
 
-    DexInspector inspector = new DexInspector(processedApplication);
+    CodeInspector inspector = new CodeInspector(processedApplication);
     // Test is running without tree-shaking, so the empty <clinit> is not removed.
     assertTrue(
         inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
@@ -311,7 +311,7 @@
     AndroidApp originalApplication = buildApplication(builder);
     AndroidApp processedApplication = processApplication(originalApplication);
 
-    DexInspector inspector = new DexInspector(processedApplication);
+    CodeInspector inspector = new CodeInspector(processedApplication);
     // Test is running without tree-shaking, so the empty <clinit> is not removed.
     assertTrue(
         inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod());
@@ -384,7 +384,7 @@
     AndroidApp originalApplication = buildApplication(builder);
     AndroidApp processedApplication = processApplication(originalApplication);
 
-    DexInspector inspector = new DexInspector(processedApplication);
+    CodeInspector inspector = new CodeInspector(processedApplication);
     assertTrue(inspector.clazz("Test").clinit().isPresent());
 
     DexValue value;
@@ -471,7 +471,7 @@
     AndroidApp originalApplication = buildApplication(builder);
     AndroidApp processedApplication = processApplication(originalApplication);
 
-    DexInspector inspector = new DexInspector(processedApplication);
+    CodeInspector inspector = new CodeInspector(processedApplication);
     assertTrue(inspector.clazz(className).isPresent());
     // Test is running without tree-shaking, so the empty <clinit> is not removed.
     assertTrue(
@@ -520,7 +520,7 @@
     AndroidApp originalApplication = buildApplication(builder);
     AndroidApp processedApplication = processApplication(originalApplication);
 
-    DexInspector inspector = new DexInspector(processedApplication);
+    CodeInspector inspector = new CodeInspector(processedApplication);
     assertTrue(inspector.clazz(className).isPresent());
     assertTrue(inspector.clazz(className).clinit().isPresent());
 
@@ -553,7 +553,7 @@
     AndroidApp originalApplication = buildApplication(builder);
     AndroidApp processedApplication = processApplication(originalApplication);
 
-    DexInspector inspector = new DexInspector(processedApplication);
+    CodeInspector inspector = new CodeInspector(processedApplication);
     MethodSubject clinit = inspector.clazz("Test").clinit();
     // Nothing changed in the class initializer.
     assertEquals(3, clinit.getMethod().getCode().asDexCode().instructions.length);
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
index ae88c12..bffcfc5 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
@@ -18,9 +18,9 @@
 import com.android.tools.r8.jasmin.JasminTestBase;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -282,7 +282,7 @@
     appBuilder.addProgramFiles(ToolHelper.getClassFileForTestClass(CheckSwitchInTestClass.class));
     AndroidApp app = compileWithR8(appBuilder.build());
 
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     MethodSubject method = inspector.clazz("Test").method("int", "test", ImmutableList.of("int"));
     DexCode code = method.getMethod().getCode().asDexCode();
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java b/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
index 38247bd..b2bcb4e 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Paths;
 import java.util.List;
@@ -45,7 +45,7 @@
         .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
         .build();
     AndroidApp result = ToolHelper.runR8(command);
-    DexInspector inspector = new DexInspector(result);
+    CodeInspector inspector = new CodeInspector(result);
     Assert.assertFalse(inspector.clazz(SWITCHMAP_CLASS_NAME).isPresent());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
new file mode 100644
index 0000000..d28770a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
@@ -0,0 +1,176 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+class B111974287 {
+  B111974287 self;
+  B111974287[] clones;
+
+  B111974287() {
+    self = this;
+    clones = new B111974287[1];
+    clones[0] = self;
+  }
+
+  B111974287 fooX() {
+    System.out.println("fooX");
+    return self;
+  }
+
+  B111974287 fooYY() {
+    System.out.println("fooYY");
+    return self;
+  }
+
+  B111974287 fooZZZ() {
+    System.out.println("fooZZZ");
+    return self;
+  }
+}
+
+@RunWith(Parameterized.class)
+public class AsterisksTest extends ProguardCompatibilityTestBase {
+  private final static List<Class> CLASSES = ImmutableList.of(B111974287.class);
+  private final Shrinker shrinker;
+
+  public AsterisksTest(Shrinker shrinker) {
+    this.shrinker = shrinker;
+  }
+
+  @Parameters(name = "shrinker: {0}")
+  public static Collection<Object> data() {
+    return ImmutableList.of(Shrinker.PROGUARD6, Shrinker.R8, Shrinker.R8_CF);
+  }
+
+  @Test
+  public void doubleAsterisksInField() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **." + B111974287.class.getSimpleName() + "{",
+        "  ** **;",
+        "}"
+    );
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject, not(isRenamed()));
+    FieldSubject fieldSubject = classSubject.field(B111974287.class.getTypeName(), "self");
+    assertThat(fieldSubject, isPresent());
+    assertThat(fieldSubject, not(isRenamed()));
+    fieldSubject = classSubject.field(B111974287.class.getTypeName() + "[]", "clones");
+    // TODO(b/111974287): Proguard6 kept and renamed the field with array type.
+    if (shrinker == Shrinker.PROGUARD6) {
+      return;
+    }
+    assertThat(fieldSubject, not(isPresent()));
+  }
+
+  @Test
+  public void doubleAsterisksInMethod() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **." + B111974287.class.getSimpleName() + "{",
+        "  ** foo**(...);",
+        "}"
+    );
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject, not(isRenamed()));
+    DexClass clazz = classSubject.getDexClass();
+    assertEquals(3, clazz.virtualMethods().length);
+    for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
+      assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
+      MethodSubject methodSubject =
+          classSubject.method(MethodSignature.fromDexMethod(encodedMethod.method));
+      assertThat(methodSubject, isPresent());
+      assertThat(methodSubject, not(isRenamed()));
+    }
+  }
+
+  @Test
+  public void tripleAsterisksInField() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **." + B111974287.class.getSimpleName() + "{",
+        "  *** ***;",
+        "}"
+    );
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject, not(isRenamed()));
+    FieldSubject fieldSubject = classSubject.field(B111974287.class.getTypeName(), "self");
+    assertThat(fieldSubject, isPresent());
+    assertThat(fieldSubject, not(isRenamed()));
+    fieldSubject = classSubject.field(B111974287.class.getTypeName() + "[]", "clones");
+    assertThat(fieldSubject, isPresent());
+    assertThat(fieldSubject, not(isRenamed()));
+  }
+
+  @Test
+  public void tripleAsterisksInMethod() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **." + B111974287.class.getSimpleName() + "{",
+        "  *** foo***(...);",
+        "}"
+    );
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject, not(isRenamed()));
+    DexClass clazz = classSubject.getDexClass();
+    assertEquals(3, clazz.virtualMethods().length);
+    for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
+      assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
+      MethodSubject methodSubject =
+          classSubject.method(MethodSignature.fromDexMethod(encodedMethod.method));
+      assertThat(methodSubject, isPresent());
+      assertThat(methodSubject, not(isRenamed()));
+    }
+  }
+
+  @Test
+  public void quadrupleAsterisksInType() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **** {",
+        "  **** foo***(...);",
+        "}"
+    );
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject, not(isRenamed()));
+    DexClass clazz = classSubject.getDexClass();
+    assertEquals(3, clazz.virtualMethods().length);
+    for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
+      assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
+      MethodSubject methodSubject =
+          classSubject.method(MethodSignature.fromDexMethod(encodedMethod.method));
+      assertThat(methodSubject, isPresent());
+      assertThat(methodSubject, not(isRenamed()));
+    }
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/B112290098.java b/src/test/java/com/android/tools/r8/shaking/B112290098.java
new file mode 100644
index 0000000..429853c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/B112290098.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import static junit.framework.TestCase.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class B112290098 extends TestBase {
+
+  @Ignore("b/112290098")
+  @Test
+  public void test() throws Exception {
+    String mainClass = TestClass.class.getName();
+    AndroidApp input = readClasses(TestClass.class, C.class);
+    AndroidApp output =
+        compileWithR8(
+            input,
+            String.join(
+                System.lineSeparator(),
+                "-keep public class " + mainClass + " {",
+                "  public static void main(...);",
+                "}"));
+    assertEquals(runOnArt(compileWithD8(input), mainClass), runOnArt(output, mainClass));
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      // Instantiation that will be removed as a result of class inlining.
+      new C();
+
+      C obj = null;
+      try {
+        // After inlining this will lead to an iget instruction.
+        obj.getField();
+      } catch (NullPointerException e) {
+        System.out.println("Caught NullPointerException");
+      }
+    }
+  }
+
+  public static class C {
+
+    // In the second round of tree shaking, C is no longer instantiated, but we should still
+    // keep this field to avoid a NoSuchFieldError instead of a NullPointerException at the
+    // obj.getField() invocation.
+    public int field = 42;
+
+    public int getField() {
+      return field;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ClassKindTest.java b/src/test/java/com/android/tools/r8/shaking/ClassKindTest.java
index 3475871..89cbbbe 100644
--- a/src/test/java/com/android/tools/r8/shaking/ClassKindTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ClassKindTest.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.shaking.classkinds.Enum;
 import com.android.tools.r8.shaking.classkinds.Interface;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.Collection;
@@ -59,7 +59,7 @@
       Assert.assertTrue(classes.isEmpty());
       return;
     }
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     HashSet<java.lang.Class<?>> expected = Sets.newHashSet(classes);
     CLASSES_TO_INCLUDE.forEach(c -> {
       Assert.assertEquals(expected.contains(c), inspector.clazz(c).isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
index 8f00f8d..2b4865d 100644
--- a/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
@@ -17,8 +17,8 @@
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import org.junit.Test;
@@ -126,7 +126,7 @@
     assertThat(artResult.stdout, containsString(impl2.name));
     assertEquals(-1, artResult.stderr.indexOf("DoFieldPut"));
 
-    DexInspector inspector = new DexInspector(processedApp);
+    CodeInspector inspector = new CodeInspector(processedApp);
     ClassSubject itf1Subject = inspector.clazz(itf1.name);
     assertThat(itf1Subject, isPresent());
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java
index 474af4d..12ef651 100644
--- a/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java
+++ b/src/test/java/com/android/tools/r8/shaking/InstantiatedLambdasTestRunner.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.utils.FileUtils;
 import java.io.IOException;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -122,7 +121,7 @@
     Path outPg = temp.getRoot().toPath().resolve(outName);
     ProcessResult proguardResult =
         ToolHelper.runProguard6Raw(
-            inputJar, outPg, Paths.get(ToolHelper.JAVA_8_RUNTIME), pgConfig, null);
+            inputJar, outPg, ToolHelper.getJava8RuntimeJar(), pgConfig, null);
     System.out.println(proguardResult.stdout);
     if (proguardResult.exitCode != 0) {
       System.out.println(proguardResult.stderr);
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
index 6ecdb09..dba2caa 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.shaking;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -14,6 +14,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.DataResourceProvider;
 import com.android.tools.r8.DexIndexedConsumer;
@@ -31,7 +32,7 @@
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.ByteStreams;
 import com.google.common.io.CharSource;
@@ -39,12 +40,16 @@
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.jar.JarOutputStream;
 import java.util.zip.ZipEntry;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 class A {
   private static String buildClassName(String className) {
@@ -65,7 +70,20 @@
 
 }
 
+@RunWith(Parameterized.class)
 public class LibraryProvidedProguardRulesTest extends TestBase {
+
+  private Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public LibraryProvidedProguardRulesTest(Backend backend) {
+    this.backend = backend;
+  }
+
   private void addTextJarEntry(JarOutputStream out, String name, String content) throws Exception {
     out.putNextEntry(new ZipEntry(name));
     ByteStreams.copy(
@@ -76,7 +94,7 @@
   private AndroidApp runTest(List<String> rules, DiagnosticsHandler handler) throws Exception {
     Path jar = temp.newFile("test.jar").toPath();
     try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar.toFile()))) {
-      addTestClassesToJar(out, A.class, B.class);
+      addTestClassesToJar(out, ImmutableList.of(A.class, B.class));
       for (int i =  0; i < rules.size(); i++) {
         String name = "META-INF/proguard/jar" + (i == 0 ? "" : i) + ".rules";
         addTextJarEntry(out, name, rules.get(i));
@@ -84,11 +102,19 @@
     }
 
     try {
-      R8Command command = (handler != null ? R8Command.builder(handler) : R8Command.builder())
-          .addProgramFiles(jar)
-          .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
-          .build();
-      return ToolHelper.runR8(command);
+      R8Command.Builder builder =
+          (handler != null ? R8Command.builder(handler) : R8Command.builder()).addProgramFiles(jar);
+      if (backend == Backend.DEX) {
+        builder
+            .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+            .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+      } else {
+        assert backend == Backend.CF;
+        builder
+            .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+      }
+      return ToolHelper.runR8(builder.build());
     } catch (CompilationFailedException e) {
       assertNotNull(handler);
       return null;
@@ -103,7 +129,7 @@
   @Test
   public void keepOnlyA() throws Exception {
     AndroidApp app = runTest("-keep class " + A.class.getTypeName() +" {}", null);
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     assertThat(inspector.clazz(A.class), isPresent());
     assertThat(inspector.clazz(B.class), not(isPresent()));
   }
@@ -111,7 +137,7 @@
   @Test
   public void keepOnlyB() throws Exception {
     AndroidApp app = runTest("-keep class **B {}", null);
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     assertThat(inspector.clazz(A.class), not(isPresent()));
     assertThat(inspector.clazz(B.class), isPresent());
   }
@@ -119,7 +145,7 @@
   @Test
   public void keepBoth() throws Exception {
     AndroidApp app = runTest("-keep class ** {}", null);
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     assertThat(inspector.clazz(A.class), isPresent());
     assertThat(inspector.clazz(B.class), isPresent());
   }
@@ -127,7 +153,7 @@
   @Test
   public void multipleFiles() throws Exception {
     AndroidApp app = runTest(ImmutableList.of("-keep class **A {}", "-keep class **B {}"), null);
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     assertThat(inspector.clazz(A.class), isPresent());
     assertThat(inspector.clazz(B.class), isPresent());
   }
@@ -186,10 +212,19 @@
   public void throwingDataResourceProvider() throws Exception {
     DiagnosticsChecker checker = new DiagnosticsChecker();
     try {
-      R8Command command = R8Command.builder(checker)
-          .addProgramResourceProvider(new TestProvider())
-          .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
-          .build();
+      R8Command.Builder builder =
+          R8Command.builder(checker).addProgramResourceProvider(new TestProvider());
+      if (backend == Backend.DEX) {
+        builder
+            .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+            .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+      } else {
+        assert backend == Backend.CF;
+        builder
+            .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+      }
+      builder.build();
       fail("Should not succeed");
     } catch (CompilationFailedException e) {
       DiagnosticsChecker.checkDiagnostic(
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index bce2e8b..3b5a830 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -41,8 +41,14 @@
 @RunWith(Parameterized.class)
 public class PrintUsageTest {
 
+  private enum Backend {
+    CF,
+    DEX
+  }
+
   private static final String PRINT_USAGE_FILE_SUFFIX = "-print-usage.txt";
 
+  private final Backend backend;
   private final String test;
   private final String programFile;
   private final List<String> keepRulesFiles;
@@ -52,9 +58,11 @@
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
   public PrintUsageTest(
+      Backend backend,
       String test,
       List<String> keepRulesFiles,
       Consumer<PrintUsageInspector> inspection) {
+    this.backend = backend;
     this.test = test;
     this.programFile = ToolHelper.EXAMPLES_BUILD_DIR + test + ".jar";
     this.keepRulesFiles = keepRulesFiles;
@@ -64,22 +72,29 @@
   @Before
   public void runR8andGetPrintUsage() throws Exception {
     Path out = temp.getRoot().toPath();
-    R8Command command =
+    R8Command.Builder builder =
         ToolHelper.addProguardConfigurationConsumer(
                 R8Command.builder(),
                 pgConfig -> {
                   pgConfig.setPrintUsage(true);
                   pgConfig.setPrintUsageFile(out.resolve(test + PRINT_USAGE_FILE_SUFFIX));
                 })
-            .setOutput(out, OutputMode.DexIndexed)
             .addProgramFiles(Paths.get(programFile))
-            .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
-            .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
-            .build();
-    ToolHelper.runR8(command, options -> {
-      // Disable inlining to make this test not depend on inlining decisions.
-      options.enableInlining = false;
-    });
+            .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get));
+
+    if (backend == Backend.DEX) {
+      builder
+          .setOutput(out, OutputMode.DexIndexed)
+          .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+    } else {
+      builder.setOutput(out, OutputMode.ClassFile).addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+    }
+    ToolHelper.runR8(
+        builder.build(),
+        options -> {
+          // Disable inlining to make this test not depend on inlining decisions.
+          options.enableInlining = false;
+        });
   }
 
   @Test
@@ -106,6 +121,7 @@
     inspections.put("shaking12:keep-rules-printusage.txt", PrintUsageTest::inspectShaking12);
 
     List<Object[]> testCases = new ArrayList<>();
+    for (Backend backend : Backend.values()) {
     Set<String> usedInspections = new HashSet<>();
     for (String test : tests) {
       File[] keepFiles = new File(ToolHelper.EXAMPLES_DIR + test)
@@ -115,11 +131,13 @@
         Consumer<PrintUsageInspector> inspection =
             getTestOptionalParameter(inspections, usedInspections, test, keepName);
         if (inspection != null) {
-          testCases.add(new Object[]{test, ImmutableList.of(keepFile.getPath()), inspection});
+            testCases.add(
+                new Object[] {backend, test, ImmutableList.of(keepFile.getPath()), inspection});
         }
       }
     }
-    assert usedInspections.size() == inspections.size();
+      assert usedInspections.size() == inspections.size();
+    }
     return testCases;
   }
 
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 1379b95..f79e01f 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -27,11 +27,11 @@
 import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
 import com.android.tools.r8.utils.AbortException;
 import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
-import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
 import com.android.tools.r8.utils.KeepingDiagnosticHandler;
 import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.io.File;
 import java.io.IOException;
@@ -41,6 +41,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.function.Function;
+import java.util.stream.Collectors;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -1233,7 +1234,7 @@
         "-keepdirectories"
     ));
     checkFileFilterMatchAnything(config.getAdaptResourceFilenames());
-    checkFileFilterMatchAnything(config.getAdaptResourceFilecontents());
+    checkFileFilterMatchAnything(config.getAdaptResourceFileContents());
     checkFileFilterMatchAnything(config.getKeepDirectories());
   }
 
@@ -1246,7 +1247,7 @@
         "-adaptresourcefilecontents"
     ));
     checkFileFilterMatchAnything(config.getAdaptResourceFilenames());
-    checkFileFilterMatchAnything(config.getAdaptResourceFilecontents());
+    checkFileFilterMatchAnything(config.getAdaptResourceFileContents());
     checkFileFilterMatchAnything(config.getKeepDirectories());
   }
 
@@ -1259,7 +1260,7 @@
         "-adaptresourcefilenames"
     ));
     checkFileFilterMatchAnything(config.getAdaptResourceFilenames());
-    checkFileFilterMatchAnything(config.getAdaptResourceFilecontents());
+    checkFileFilterMatchAnything(config.getAdaptResourceFileContents());
     checkFileFilterMatchAnything(config.getKeepDirectories());
   }
 
@@ -1281,7 +1282,7 @@
         "-keepdirectories " + FILE_FILTER_SINGLE
     ));
     checkFileFilterSingle(config.getAdaptResourceFilenames());
-    checkFileFilterSingle(config.getAdaptResourceFilecontents());
+    checkFileFilterSingle(config.getAdaptResourceFileContents());
     checkFileFilterSingle(config.getKeepDirectories());
   }
 
@@ -1314,7 +1315,7 @@
         "-keepdirectories " + FILE_FILTER_MULTIPLE
     ));
     checkFileFilterMultiple(config.getAdaptResourceFilenames());
-    checkFileFilterMultiple(config.getAdaptResourceFilecontents());
+    checkFileFilterMultiple(config.getAdaptResourceFileContents());
     checkFileFilterMultiple(config.getKeepDirectories());
   }
 
@@ -1807,12 +1808,70 @@
   }
 
   @Test
-  public void parse_pariallyImplemented_notSupported() {
+  public void parse_partiallyImplemented_notSupported() {
     testNotSupported("-keepdirectories");
-    testNotSupported("-adaptresourcefilenames");
-    testNotSupported("-adaptresourcefilecontents");
   }
 
+  private void checkRulesSourceSnippet(List<String> sourceRules) {
+    checkRulesSourceSnippet(sourceRules, sourceRules, false);
+  }
+
+  private void checkRulesSourceSnippet(
+      List<String> sourceRules, List<String> expected, boolean trim) {
+    reset();
+    parser.parse(createConfigurationForTesting(sourceRules));
+    verifyParserEndsCleanly();
+    List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
+    assertEquals(expected.size(), rules.size());
+    for (int i = 0; i < expected.size(); i++) {
+      assertEquals(trim ? expected.get(i).trim() : expected.get(i), rules.get(i).getSource());
+    }
+  }
+
+  @Test
+  public void accurateSourceSnippet() {
+    String rule1 = String.join(System.lineSeparator(), ImmutableList.of("-keep class A  {  *;  }"));
+    String rule2 =
+        String.join(System.lineSeparator(), ImmutableList.of("-keep class A  ", "{  *;  ", "}"));
+    String rule3 =
+        String.join(
+            System.lineSeparator(), ImmutableList.of("-checkdiscard class A  ", "{  *;  ", "}"));
+
+    checkRulesSourceSnippet(ImmutableList.of(rule1));
+    checkRulesSourceSnippet(ImmutableList.of(rule2));
+    checkRulesSourceSnippet(ImmutableList.of(rule3));
+    checkRulesSourceSnippet(ImmutableList.of(rule1, rule2, rule3));
+  }
+
+  @Test
+  public void accurateSourceSnippet_withWhitespace() {
+    Iterable<String> nonEmptyWhiteSpace =
+        whiteSpace.stream().filter(space -> !space.equals("")).collect(Collectors.toList());
+    for (String space : nonEmptyWhiteSpace) {
+      String rule1 =
+          String.join(System.lineSeparator(), ImmutableList.of("  -keep class A  {  *;  }  "))
+              .replaceAll(" {2}", space);
+      String rule2 =
+          String.join(
+                  System.lineSeparator(), ImmutableList.of("-keep class A  ", "{  *;  ", "}", ""))
+              .replaceAll(" {2}", space);
+
+      checkRulesSourceSnippet(ImmutableList.of(rule1), ImmutableList.of(rule1), true);
+      checkRulesSourceSnippet(
+          ImmutableList.of("#Test comment ", "", rule1), ImmutableList.of(rule1), true);
+      checkRulesSourceSnippet(
+          ImmutableList.of("#Test comment ", "", rule1, "", "#Test comment ", ""),
+          ImmutableList.of(rule1),
+          true);
+      checkRulesSourceSnippet(ImmutableList.of(rule2), ImmutableList.of(rule2), true);
+      checkRulesSourceSnippet(ImmutableList.of(rule1, rule2), ImmutableList.of(rule1, rule2), true);
+      checkRulesSourceSnippet(
+          ImmutableList.of(
+              "#Test comment ", "", rule1, " ", "#Test comment ", "", rule2, "#Test comment ", ""),
+          ImmutableList.of(rule1, rule2),
+          true);
+    }
+  }
 
   private ProguardConfiguration parseAndVerifyParserEndsCleanly(List<String> config) {
     parser.parse(createConfigurationForTesting(config));
@@ -1844,7 +1903,7 @@
           ImmutableList.of(proguardConfig, additionalProguardConfig),
           null);
       assertEquals(0, result.exitCode);
-      DexInspector proguardInspector = new DexInspector(readJar(proguardedJar));
+      CodeInspector proguardInspector = new CodeInspector(readJar(proguardedJar));
       assertEquals(1, proguardInspector.allClasses().size());
     }
   }
@@ -1867,7 +1926,7 @@
           ImmutableList.of(proguardConfig, additionalProguardConfig),
           null);
       assertEquals(0, result.exitCode);
-      DexInspector proguardInspector = new DexInspector(readJar(proguardedJar));
+      CodeInspector proguardInspector = new CodeInspector(readJar(proguardedJar));
       assertEquals(1, proguardInspector.allClasses().size());
     }
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index cba8686..ad67919 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -14,12 +14,15 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import java.io.BufferedReader;
+import java.io.IOException;
 import java.io.PrintStream;
 import java.io.StringReader;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.junit.Assert;
@@ -27,8 +30,28 @@
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
+@RunWith(Parameterized.class)
 public class TreeShakingSpecificTest {
+  enum Backend {
+    DEX,
+    CF
+  }
+
+  private Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public TreeShakingSpecificTest(Backend backend) {
+    this.backend = backend;
+  }
+
   private static final String VALID_PROGUARD_DIR = "src/test/proguard/valid/";
 
   @Rule
@@ -37,18 +60,28 @@
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
+  private void finishBuild(R8Command.Builder builder, Path out, String test) throws IOException {
+    Path input;
+    if (backend == Backend.DEX) {
+      builder.setOutput(out, OutputMode.DexIndexed);
+      input = Paths.get(EXAMPLES_BUILD_DIR, test, "classes.dex");
+    } else {
+      builder.setOutput(out, OutputMode.ClassFile);
+      input = Paths.get(EXAMPLES_BUILD_DIR, test + ".jar");
+    }
+    ToolHelper.getAppBuilder(builder).addProgramFiles(input);
+  }
+
   @Test
   public void testIgnoreWarnings() throws Exception {
     // Generate R8 processed version without library option.
     Path out = temp.getRoot().toPath();
     String test = "shaking2";
-    Path originalDex = Paths.get(EXAMPLES_BUILD_DIR, test, "classes.dex");
     Path keepRules = Paths.get(EXAMPLES_DIR, test, "keep-rules.txt");
     Path ignoreWarnings = Paths.get(VALID_PROGUARD_DIR, "ignorewarnings.flags");
     R8Command.Builder builder = R8Command.builder()
-        .setOutput(out, OutputMode.DexIndexed)
         .addProguardConfigurationFiles(keepRules, ignoreWarnings);
-    ToolHelper.getAppBuilder(builder).addProgramFiles(originalDex);
+    finishBuild(builder, out, test);
     R8.run(builder.build());
   }
 
@@ -57,7 +90,6 @@
     // Generate R8 processed version without library option.
     Path out = temp.getRoot().toPath();
     String test = "shaking2";
-    Path originalDex = Paths.get(EXAMPLES_BUILD_DIR, test, "classes.dex");
     Path keepRules = Paths.get(EXAMPLES_DIR, test, "keep-rules.txt");
     DiagnosticsHandler handler = new DiagnosticsHandler() {
       @Override
@@ -68,9 +100,8 @@
       }
     };
     R8Command.Builder builder = R8Command.builder(handler)
-        .setOutput(out, OutputMode.DexIndexed)
         .addProguardConfigurationFiles(keepRules);
-    ToolHelper.getAppBuilder(builder).addProgramFiles(originalDex);
+    finishBuild(builder, out, test);
     R8.run(builder.build());
   }
 
@@ -79,7 +110,6 @@
     // Generate R8 processed version without library option.
     String test = "shaking1";
     Path out = temp.getRoot().toPath();
-    Path originalDex = Paths.get(EXAMPLES_BUILD_DIR, test, "classes.dex");
     Path keepRules = Paths.get(EXAMPLES_DIR, test, "keep-rules.txt");
 
     // Create a flags file in temp dir requesting dump of the mapping.
@@ -90,10 +120,15 @@
     }
 
     R8Command.Builder builder = R8Command.builder()
-        .setOutput(out, OutputMode.DexIndexed)
         .addProguardConfigurationFiles(keepRules, printMapping);
-    ToolHelper.getAppBuilder(builder).addProgramFiles(originalDex);
     // Turn off inlining, as we want the mapping that is printed to be stable.
+    finishBuild(builder, out, test);
+    if (backend == Backend.DEX) {
+      builder.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+    } else {
+      assert backend == Backend.CF;
+      builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+    }
     ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
 
     Path outputmapping = out.resolve("mapping.txt");
@@ -102,8 +137,15 @@
         Stream.of(new String(Files.readAllBytes(outputmapping), StandardCharsets.UTF_8).split("\n"))
             .filter(line -> !line.startsWith("#"))
             .collect(Collectors.joining("\n"));
-    String refMapping = new String(Files.readAllBytes(
-        Paths.get(EXAMPLES_DIR, "shaking1", "print-mapping.ref")), StandardCharsets.UTF_8);
+    // For the CF backend we treat ConstString/LDC as a (potentially always) throwing instruction,
+    // as opposed to the DEX backend where it's throwing only if its string is ill-formed.
+    // When ConstString is throwing we preserve its position which makes it show up in the
+    // the output Proguard map. That's why the reference CF map is different from the DEX one.
+    String mapping_ref = backend == Backend.CF ? "print-mapping-cf.ref" : "print-mapping.ref";
+    String refMapping =
+        new String(
+            Files.readAllBytes(Paths.get(EXAMPLES_DIR, "shaking1", mapping_ref)),
+            StandardCharsets.UTF_8);
     Assert.assertEquals(sorted(refMapping), sorted(actualMapping));
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index b6a1ac6..9eb7815 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -14,13 +14,13 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FoundFieldSubject;
-import com.android.tools.r8.utils.DexInspector.FoundMethodSubject;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundFieldSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -137,7 +137,7 @@
         });
   }
 
-  protected static void checkSameStructure(DexInspector ref, DexInspector inspector) {
+  protected static void checkSameStructure(CodeInspector ref, CodeInspector inspector) {
     ref.forAllClasses(refClazz -> checkSameStructure(refClazz,
         inspector.clazz(refClazz.getDexClass().toSourceString())));
   }
@@ -167,18 +167,18 @@
   }
 
   protected void runTest(
-      Consumer<DexInspector> inspection,
+      Consumer<CodeInspector> inspection,
       BiConsumer<String, String> outputComparator,
-      BiConsumer<DexInspector, DexInspector> dexComparator,
+      BiConsumer<CodeInspector, CodeInspector> dexComparator,
       List<String> keepRulesFiles)
       throws Exception {
     runTest(inspection, outputComparator, dexComparator, keepRulesFiles, null);
   }
 
   protected void runTest(
-      Consumer<DexInspector> inspection,
+      Consumer<CodeInspector> inspection,
       BiConsumer<String, String> outputComparator,
-      BiConsumer<DexInspector, DexInspector> dexComparator,
+      BiConsumer<CodeInspector, CodeInspector> dexComparator,
       List<String> keepRulesFiles,
       Consumer<InternalOptions> optionsConsumer)
       throws Exception {
@@ -193,7 +193,7 @@
     if (backend == Backend.CF) {
       jarLibraries =
           ImmutableList.of(
-              Paths.get(ToolHelper.JAVA_8_RUNTIME),
+              ToolHelper.getJava8RuntimeJar(),
               Paths.get(ToolHelper.EXAMPLES_BUILD_DIR + "shakinglib.jar"));
     } else {
       jarLibraries =
@@ -218,8 +218,8 @@
         Assert.assertEquals(resultInput.toString(), resultOutput.toString());
       }
       if (inspection != null) {
-        DexInspector inspector =
-            new DexInspector(
+        CodeInspector inspector =
+            new CodeInspector(
                 out,
                 minify.isMinify()
                     ? proguardMap.toString()
@@ -249,8 +249,8 @@
       }
 
       if (dexComparator != null) {
-        DexInspector ref = new DexInspector(Paths.get(originalDex));
-        DexInspector inspector = new DexInspector(out,
+        CodeInspector ref = new CodeInspector(Paths.get(originalDex));
+        CodeInspector inspector = new CodeInspector(out,
             minify.isMinify() ? proguardMap.toString()
                 : null);
         dexComparator.accept(ref, inspector);
@@ -263,7 +263,7 @@
     }
 
     if (inspection != null) {
-      DexInspector inspector = new DexInspector(out,
+      CodeInspector inspector = new CodeInspector(out,
           minify.isMinify() ? proguardMap.toString()
               : null);
       inspection.accept(inspector);
diff --git a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
index 46feb00..d753632 100644
--- a/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/defaultmethods/DefaultMethodsTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
@@ -15,22 +16,47 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class DefaultMethodsTest extends TestBase {
-  private void runTest(List<String> additionalKeepRules, Consumer<DexInspector> inspection)
+
+  private Backend backend;
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public DefaultMethodsTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  private void runTest(List<String> additionalKeepRules, Consumer<CodeInspector> inspection)
       throws Exception {
     R8Command.Builder builder = R8Command.builder();
     builder.addProgramFiles(ToolHelper.getClassFileForTestClass(InterfaceWithDefaultMethods.class));
     builder.addProgramFiles(ToolHelper.getClassFileForTestClass(ClassImplementingInterface.class));
     builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
-    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
+    if (backend == Backend.DEX) {
+      builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+      int apiLevel = AndroidApiLevel.O.getLevel();
+      builder.setMinApiLevel(apiLevel);
+      builder.addLibraryFiles(ToolHelper.getAndroidJar(apiLevel));
+    } else {
+      assert backend == Backend.CF;
+      builder.setProgramConsumer(ClassFileConsumer.emptyConsumer());
+      builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+    }
     // Always keep main in the test class, so the output never becomes empty.
     builder.addProguardConfiguration(ImmutableList.of(
         "-keep class " + TestClass.class.getCanonicalName() + "{",
@@ -40,20 +66,20 @@
         Origin.unknown());
     builder.addProguardConfiguration(additionalKeepRules, Origin.unknown());
     AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
-    inspection.accept(new DexInspector(app));
+    inspection.accept(new CodeInspector(app));
   }
 
-  private void interfaceNotKept(DexInspector inspector) {
+  private void interfaceNotKept(CodeInspector inspector) {
     assertFalse(inspector.clazz(InterfaceWithDefaultMethods.class).isPresent());
   }
 
-  private void defaultMethodNotKept(DexInspector inspector) {
+  private void defaultMethodNotKept(CodeInspector inspector) {
     ClassSubject clazz = inspector.clazz(InterfaceWithDefaultMethods.class);
     assertTrue(clazz.isPresent());
     assertFalse(clazz.method("int", "method", ImmutableList.of()).isPresent());
   }
 
-  private void defaultMethodKept(DexInspector inspector) {
+  private void defaultMethodKept(CodeInspector inspector) {
     ClassSubject clazz = inspector.clazz(InterfaceWithDefaultMethods.class);
     assertTrue(clazz.isPresent());
     MethodSubject method = clazz.method("int", "method", ImmutableList.of());
@@ -61,7 +87,7 @@
     assertFalse(method.isAbstract());
   }
 
-  private void defaultMethodAbstract(DexInspector inspector) {
+  private void defaultMethodAbstract(CodeInspector inspector) {
     ClassSubject clazz = inspector.clazz(InterfaceWithDefaultMethods.class);
     assertTrue(clazz.isPresent());
     MethodSubject method = clazz.method("int", "method", ImmutableList.of());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java b/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java
index 7fcb318..ba0d468 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/InliningClassVersionTest.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.io.ByteStreams;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.Collections;
 import java.util.List;
 import org.junit.Test;
@@ -147,7 +146,7 @@
     ToolHelper.runR8(
         R8Command.builder()
             .addProgramFiles(inputJar)
-            .addLibraryFiles(Paths.get(ToolHelper.JAVA_8_RUNTIME))
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .addProguardConfiguration(keepRule, Origin.unknown())
             .setOutput(outputJar, OutputMode.ClassFile)
             .build());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
index 54bec20..f65c1d4 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -53,24 +53,24 @@
         ImmutableList.of("src/test/examples/shaking11/keep-rules-keep-method.txt"));
   }
 
-  private static void shaking11OnlyOneClassKept(DexInspector dexInspector) {
-    Assert.assertFalse(dexInspector.clazz("shaking11.Subclass").isPresent());
-    Assert.assertTrue(dexInspector.clazz("shaking11.SubclassWithMethod").isPresent());
+  private static void shaking11OnlyOneClassKept(CodeInspector codeInspector) {
+    Assert.assertFalse(codeInspector.clazz("shaking11.Subclass").isPresent());
+    Assert.assertTrue(codeInspector.clazz("shaking11.SubclassWithMethod").isPresent());
   }
 
-  private static void shaking11BothMethodsKept(DexInspector dexInspector) {
+  private static void shaking11BothMethodsKept(CodeInspector codeInspector) {
     Assert.assertFalse(
-        dexInspector
+        codeInspector
             .clazz("shaking11.Subclass")
             .method("void", "aMethod", Collections.emptyList())
             .isPresent());
     Assert.assertTrue(
-        dexInspector
+        codeInspector
             .clazz("shaking11.SuperClass")
             .method("void", "aMethod", Collections.emptyList())
             .isPresent());
     Assert.assertTrue(
-        dexInspector
+        codeInspector
             .clazz("shaking11.SubclassWithMethod")
             .method("void", "aMethod", Collections.emptyList())
             .isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
index 7bbde47..710635d 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking12Test.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -54,7 +54,7 @@
         ImmutableList.of("src/test/examples/shaking12/keep-rules-printusage.txt"));
   }
 
-  private static void shaking12OnlyInstantiatedClassesHaveConstructors(DexInspector inspector) {
+  private static void shaking12OnlyInstantiatedClassesHaveConstructors(CodeInspector inspector) {
     ClassSubject animalClass = inspector.clazz("shaking12.AnimalClass");
     Assert.assertTrue(animalClass.isPresent());
     Assert.assertFalse(animalClass.method("void", "<init>", Collections.emptyList()).isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
index 4729897..2157cd3 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking13Test.java
@@ -5,11 +5,11 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldAccessInstructionSubject;
-import com.android.tools.r8.utils.DexInspector.InstructionSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldAccessInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -49,7 +49,7 @@
         ImmutableList.of("src/test/examples/shaking13/keep-rules.txt"));
   }
 
-  private static void shaking13EnsureFieldWritesCorrect(DexInspector inspector) {
+  private static void shaking13EnsureFieldWritesCorrect(CodeInspector inspector) {
     ClassSubject mainClass = inspector.clazz("shaking13.Shaking");
     MethodSubject testMethod = mainClass.method("void", "fieldTest", Collections.emptyList());
     Assert.assertTrue(testMethod.isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
index 6891708..820afc2 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking14Test.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -44,7 +44,7 @@
         ImmutableList.of("src/test/examples/shaking14/keep-rules.txt"));
   }
 
-  private static void shaking14EnsureRightStaticMethodsLive(DexInspector inspector) {
+  private static void shaking14EnsureRightStaticMethodsLive(CodeInspector inspector) {
     ClassSubject superclass = inspector.clazz("shaking14.Superclass");
     Assert.assertFalse(superclass.method("int", "aMethod", ImmutableList.of("int")).isPresent());
     Assert.assertFalse(
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
index 98a7a28..1dd353e 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking15Test.java
@@ -5,10 +5,10 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -48,7 +48,7 @@
         ImmutableList.of("src/test/examples/shaking15/keep-rules.txt"));
   }
 
-  private static void shaking15testDictionary(DexInspector inspector) {
+  private static void shaking15testDictionary(CodeInspector inspector) {
     inspector.forAllClasses((clazz) -> checkClassAndMemberInDictionary(clazz));
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
index be0f529..fac36d6 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking17Test.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -45,7 +45,7 @@
         ImmutableList.of("src/test/examples/shaking17/keep-rules.txt"));
   }
 
-  private static void abstractMethodRemains(DexInspector inspector) {
+  private static void abstractMethodRemains(CodeInspector inspector) {
     ClassSubject programClass = inspector.clazz("shaking17.AbstractProgramClass");
     Assert.assertTrue(programClass.isPresent());
     Assert.assertTrue(
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
index 97d9bfc..b691046 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking18Test.java
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -43,7 +43,7 @@
         ImmutableList.of("src/test/examples/shaking18/keep-rules.txt"));
   }
 
-  private static void unusedRemoved(DexInspector inspector) {
+  private static void unusedRemoved(CodeInspector inspector) {
     // TODO(b/80455722): Change to assertFalse when tree-shaking detects this case.
     Assert.assertTrue(
         "DerivedUnused should be removed", inspector.clazz("shaking18.DerivedUnused").isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
index e5be477..f30bcac 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking19Test.java
@@ -3,15 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -52,7 +52,7 @@
         opt -> opt.enableClassMerging = false);
   }
 
-  private static void unusedRemoved(DexInspector inspector) {
+  private static void unusedRemoved(CodeInspector inspector) {
     ClassSubject clazz = inspector.clazz("shaking19.Shaking$A");
     assertThat(clazz, isPresent());
 
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
index ad2aea7..17cfb7f 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking1Test.java
@@ -6,8 +6,8 @@
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -165,7 +165,7 @@
             "src/test/proguard/valid/empty.flags"));
   }
 
-  private static void shaking1IsCorrectlyRepackaged(DexInspector inspector) {
+  private static void shaking1IsCorrectlyRepackaged(CodeInspector inspector) {
     inspector.forAllClasses(
         clazz -> {
           String descriptor = clazz.getFinalDescriptor();
@@ -176,7 +176,7 @@
         });
   }
 
-  private static void shaking1HasNoClassUnused(DexInspector inspector) {
+  private static void shaking1HasNoClassUnused(CodeInspector inspector) {
     Assert.assertFalse(inspector.clazz("shaking1.Unused").isPresent());
     ClassSubject used = inspector.clazz("shaking1.Used");
     Assert.assertTrue(used.isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
index 39dd4ab..5f87781 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking2Test.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -60,7 +60,7 @@
         null, null, null, ImmutableList.of("src/test/examples/shaking2/keep-rules-printusage.txt"));
   }
 
-  private static void shaking2SuperClassIsAbstract(DexInspector inspector) {
+  private static void shaking2SuperClassIsAbstract(CodeInspector inspector) {
     ClassSubject clazz = inspector.clazz("shaking2.SuperClass");
     Assert.assertTrue(clazz.isAbstract());
     Assert.assertTrue(clazz.method("void", "virtualMethod", Collections.emptyList()).isAbstract());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
index 0aa431a..6ed0099 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking3Test.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -89,12 +89,12 @@
         ImmutableList.of("src/test/examples/shaking3/keep-no-abstract-classes.txt"));
   }
 
-  private static void shaking3HasNoPrivateClass(DexInspector inspector) {
+  private static void shaking3HasNoPrivateClass(CodeInspector inspector) {
     Assert.assertTrue(inspector.clazz("shaking3.B").isPresent());
     Assert.assertFalse(inspector.clazz("shaking3.AnAbstractClass").isPresent());
   }
 
-  private static void shaking3HasNoClassB(DexInspector inspector) {
+  private static void shaking3HasNoClassB(CodeInspector inspector) {
     Assert.assertFalse(inspector.clazz("shaking3.B").isPresent());
     ClassSubject classA = inspector.clazz("shaking3.A");
     Assert.assertTrue(classA.isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
index f7b0b47..7484255 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking5Test.java
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -44,7 +44,7 @@
         ImmutableList.of("src/test/examples/shaking5/keep-rules.txt"));
   }
 
-  private static void shaking5Inspection(DexInspector inspector) {
+  private static void shaking5Inspection(CodeInspector inspector) {
     Assert.assertFalse(
         inspector
             .clazz("shaking5.Superclass")
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
index f93ccd2..f5733cc 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking6Test.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -72,7 +72,7 @@
         ImmutableList.of("src/test/examples/shaking6/keep-public.txt"));
   }
 
-  private static void hasNoPublicMethodsButPrivate(DexInspector inspector) {
+  private static void hasNoPublicMethodsButPrivate(CodeInspector inspector) {
     inspector.forAllClasses(
         clazz ->
             clazz.forAllMethods(
@@ -88,7 +88,7 @@
             .isPresent());
   }
 
-  private static void hasOnlyIntJustAMethod(DexInspector inspector) {
+  private static void hasOnlyIntJustAMethod(CodeInspector inspector) {
     Assert.assertFalse(
         inspector
             .clazz("shaking6.Superclass")
@@ -105,7 +105,7 @@
         subclass.method("int", "justAMethod", Collections.singletonList("double")).isPresent());
   }
 
-  private static void hasNoPrivateJustAMethod(DexInspector inspector) {
+  private static void hasNoPrivateJustAMethod(CodeInspector inspector) {
     Assert.assertFalse(
         inspector
             .clazz("shaking6.Superclass")
@@ -122,7 +122,7 @@
         subclass.method("int", "justAMethod", Collections.singletonList("double")).isPresent());
   }
 
-  private static void hasNoPrivateMethods(DexInspector inspector) {
+  private static void hasNoPrivateMethods(CodeInspector inspector) {
     inspector.forAllClasses(
         clazz ->
             clazz.forAllMethods(
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
index 7762040..9d2faf3 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking7Test.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -71,7 +71,7 @@
         ImmutableList.of("src/test/examples/shaking7/keep-public-theIntField-fields.txt"));
   }
 
-  private static void shaking7HasOnlyDoubleFields(DexInspector inspector) {
+  private static void shaking7HasOnlyDoubleFields(CodeInspector inspector) {
     inspector.forAllClasses(
         clazz -> {
           clazz.forAllFields(
@@ -86,7 +86,7 @@
     Assert.assertFalse(inspector.clazz("shaking7.Liar").field("int", "theDoubleField").isPresent());
   }
 
-  private static void shaking7HasOnlyPublicFieldsNamedTheIntField(DexInspector inspector) {
+  private static void shaking7HasOnlyPublicFieldsNamedTheIntField(CodeInspector inspector) {
     inspector.forAllClasses(
         clazz -> {
           clazz.forAllFields(
@@ -104,7 +104,7 @@
     Assert.assertTrue(liar.field("double", "theIntField").isPresent());
   }
 
-  private static void shaking7HasOnlyPublicFields(DexInspector inspector) {
+  private static void shaking7HasOnlyPublicFields(CodeInspector inspector) {
     inspector.forAllClasses(
         clazz -> {
           clazz.forAllFields(
@@ -120,7 +120,7 @@
     Assert.assertTrue(inspector.clazz("shaking7.Liar").field("int", "theDoubleField").isPresent());
   }
 
-  private static void shaking7HasOnlyPublicFieldsNamedTheDoubleField(DexInspector inspector) {
+  private static void shaking7HasOnlyPublicFieldsNamedTheDoubleField(CodeInspector inspector) {
     inspector.forAllClasses(
         clazz -> {
           clazz.forAllFields(
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
index afa3003..b957c64 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking8Test.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -50,7 +50,7 @@
         null, null, null, ImmutableList.of("src/test/examples/shaking8/keep-rules-printusage.txt"));
   }
 
-  private static void shaking8ThingClassIsAbstractAndEmpty(DexInspector inspector) {
+  private static void shaking8ThingClassIsAbstractAndEmpty(CodeInspector inspector) {
     ClassSubject clazz = inspector.clazz("shaking8.Thing");
     Assert.assertTrue(clazz.isAbstract());
     clazz.forAllMethods((method) -> Assert.fail());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
index e3c8b8a..e64abe1 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking9Test.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -50,7 +50,7 @@
         null, null, null, ImmutableList.of("src/test/examples/shaking9/keep-rules-printusage.txt"));
   }
 
-  private static void shaking9OnlySuperMethodsKept(DexInspector inspector) {
+  private static void shaking9OnlySuperMethodsKept(CodeInspector inspector) {
     ClassSubject superclass = inspector.clazz("shaking9.Superclass");
     Assert.assertTrue(superclass.isAbstract());
     Assert.assertTrue(superclass.method("void", "aMethod", ImmutableList.of()).isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
index a235df5..d7d45f0 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAnnotationremovalTest.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -68,7 +68,7 @@
         });
   }
 
-  private static void annotationRemovalHasNoInnerClassAnnotations(DexInspector inspector) {
+  private static void annotationRemovalHasNoInnerClassAnnotations(CodeInspector inspector) {
     ClassSubject outer = inspector.clazz("annotationremoval.OuterClass");
     Assert.assertTrue(outer.isPresent());
     Assert.assertTrue(outer.getDexClass().getInnerClasses().isEmpty());
@@ -86,7 +86,7 @@
     Assert.assertTrue(local.getDexClass().getInnerClasses().isEmpty());
   }
 
-  private static void annotationRemovalHasAllInnerClassAnnotations(DexInspector inspector) {
+  private static void annotationRemovalHasAllInnerClassAnnotations(CodeInspector inspector) {
     ClassSubject outer = inspector.clazz("annotationremoval.OuterClass");
     Assert.assertTrue(outer.isPresent());
     Assert.assertFalse(outer.getDexClass().getInnerClasses().isEmpty());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
new file mode 100644
index 0000000..e093f00
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.examples;
+
+import com.android.tools.r8.TestBase.MinifyMode;
+import com.android.tools.r8.shaking.TreeShakingTest;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.ConstStringInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Assert;
+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 TreeShakingAssumevalues6Test extends TreeShakingTest {
+
+  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  public static Collection<Object[]> data() {
+    List<Object[]> parameters = new ArrayList<>();
+    for (MinifyMode minify : MinifyMode.values()) {
+      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
+      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
+      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
+    }
+    return parameters;
+  }
+
+  public TreeShakingAssumevalues6Test(Frontend frontend, Backend backend, MinifyMode minify) {
+    super("examples/assumevalues6", "assumevalues6.Assumevalues", frontend, backend, minify);
+  }
+
+  @Test
+  public void test() throws Exception {
+    runTest(
+        getBackend() == Backend.DEX ? TreeShakingAssumevalues6Test::assumevalues6CheckCode : null,
+        TreeShakingAssumevalues6Test::assumevalues6CheckOutput,
+        null,
+        ImmutableList.of("src/test/examples/assumevalues6/keep-rules.txt"));
+  }
+
+  private static void assumevalues6CheckCode(CodeInspector inspector) {
+    inspector.forAllClasses(c -> {
+      c.forAllMethods(m -> {
+        if (m.getFinalName().equals("main")) {
+          m.iterateInstructions().forEachRemaining(i -> {
+            if (i.isConstString(JumboStringMode.ALLOW)) {
+              ConstStringInstructionSubject str = (ConstStringInstructionSubject) i;
+              assert !str.getString().toASCIIString().contains("NOPE");
+            }
+          });
+        }
+      });
+    });
+  }
+
+  private static void assumevalues6CheckOutput(String output1, String output2) {
+    String expected = StringUtils.lines("YUP1", "YUP2", "YUP3", "OK");
+    Assert.assertEquals(expected, output1);
+    Assert.assertEquals(expected, output2);
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingEnumprotoTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingEnumprotoTest.java
index 7570990..7f211db 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingEnumprotoTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingEnumprotoTest.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -44,7 +44,7 @@
         ImmutableList.of("src/test/examples/enumproto/keep-rules.txt"));
   }
 
-  private static void enumprotoUnusedFieldsAreGone(DexInspector inspector) {
+  private static void enumprotoUnusedFieldsAreGone(CodeInspector inspector) {
     ClassSubject protoClass = inspector.clazz("enumproto.GeneratedEnumProto$Enum");
     Assert.assertTrue(protoClass.isPresent());
     Assert.assertFalse(protoClass.field("int", "id_").isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingNestedproto1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingNestedproto1Test.java
index 2ff5dfc..16b5f59 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingNestedproto1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingNestedproto1Test.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -44,7 +44,7 @@
         ImmutableList.of("src/test/examples/nestedproto1/keep-rules.txt"));
   }
 
-  private static void nestedproto1UnusedFieldsAreGone(DexInspector inspector) {
+  private static void nestedproto1UnusedFieldsAreGone(CodeInspector inspector) {
     ClassSubject protoClass = inspector.clazz("nestedproto1.GeneratedNestedProto$Outer");
     Assert.assertTrue(protoClass.isPresent());
     Assert.assertFalse(protoClass.field("int", "id_").isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingNestedproto2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingNestedproto2Test.java
index cb61962..a679810 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingNestedproto2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingNestedproto2Test.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -44,7 +44,7 @@
         ImmutableList.of("src/test/examples/nestedproto2/keep-rules.txt"));
   }
 
-  private static void nestedproto2UnusedFieldsAreGone(DexInspector inspector) {
+  private static void nestedproto2UnusedFieldsAreGone(CodeInspector inspector) {
     ClassSubject protoClass = inspector.clazz("nestedproto2.GeneratedNestedProto$Outer");
     Assert.assertTrue(protoClass.isPresent());
     Assert.assertTrue(protoClass.field("int", "id_").isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingOneofprotoTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingOneofprotoTest.java
index 44f5f89..e38258c 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingOneofprotoTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingOneofprotoTest.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -44,7 +44,7 @@
         ImmutableList.of("src/test/examples/oneofproto/keep-rules.txt"));
   }
 
-  private static void oneofprotoUnusedFieldsAreGone(DexInspector inspector) {
+  private static void oneofprotoUnusedFieldsAreGone(CodeInspector inspector) {
     ClassSubject protoClass = inspector.clazz("oneofproto.GeneratedOneOfProto$Oneof");
     Assert.assertTrue(protoClass.isPresent());
     Assert.assertFalse(protoClass.field("int", "id_").isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingRepeatedprotoTest.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingRepeatedprotoTest.java
index bd27c7f..923cf34 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingRepeatedprotoTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingRepeatedprotoTest.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -44,7 +44,7 @@
         ImmutableList.of("src/test/examples/repeatedproto/keep-rules.txt"));
   }
 
-  private static void repeatedUnusedFieldsAreGone(DexInspector inspector) {
+  private static void repeatedUnusedFieldsAreGone(CodeInspector inspector) {
     ClassSubject protoClass = inspector.clazz("repeatedproto.GeneratedRepeatedProto$Repeated");
     Assert.assertTrue(protoClass.isPresent());
     Assert.assertFalse(protoClass.field("int", "id_").isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingSimpleproto1Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingSimpleproto1Test.java
index 8c3f4a4..64e3839 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingSimpleproto1Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingSimpleproto1Test.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -44,7 +44,7 @@
         ImmutableList.of("src/test/examples/simpleproto1/keep-rules.txt"));
   }
 
-  private static void simpleproto1UnusedFieldIsGone(DexInspector inspector) {
+  private static void simpleproto1UnusedFieldIsGone(CodeInspector inspector) {
     ClassSubject protoClass = inspector.clazz("simpleproto1.GeneratedSimpleProto$Simple");
     Assert.assertTrue(protoClass.isPresent());
     Assert.assertFalse(protoClass.field("boolean", "other_").isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingSimpleproto2Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingSimpleproto2Test.java
index 30a9131..5304dae 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingSimpleproto2Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingSimpleproto2Test.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -44,7 +44,7 @@
         ImmutableList.of("src/test/examples/simpleproto2/keep-rules.txt"));
   }
 
-  private static void simpleproto2UnusedFieldsAreGone(DexInspector inspector) {
+  private static void simpleproto2UnusedFieldsAreGone(CodeInspector inspector) {
     ClassSubject protoClass = inspector.clazz("simpleproto2.GeneratedSimpleProto$Simple");
     Assert.assertTrue(protoClass.isPresent());
     Assert.assertFalse(protoClass.field("int", "id_").isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 642045c..9cf0a85 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -23,7 +23,6 @@
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
-import com.android.tools.r8.shaking.ProguardIdentifierNameStringRule;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.shaking.ProguardKeepRule;
 import com.android.tools.r8.shaking.ProguardKeepRuleType;
@@ -36,12 +35,12 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
@@ -56,7 +55,7 @@
   private void test(Class mainClass, Class mentionedClass, boolean forceProguardCompatibility)
       throws Exception {
     String proguardConfig = keepMainProguardConfiguration(mainClass, true, false);
-    DexInspector inspector = new DexInspector(
+    CodeInspector inspector = new CodeInspector(
         compileWithR8(
             ImmutableList.of(mainClass, mentionedClass),
             proguardConfig,
@@ -103,7 +102,7 @@
     }
 
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    DexInspector inspector = new DexInspector(ToolHelper.runR8(builder.build()));
+    CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build()));
     assertTrue(inspector.clazz(mainClass.getCanonicalName()).isPresent());
     ClassSubject clazz = inspector.clazz(getJavacGeneratedClassName(mentionedClassWithAnnotations));
     assertTrue(clazz.isPresent());
@@ -138,7 +137,7 @@
     builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules);
 
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    DexInspector inspector = new DexInspector(ToolHelper.runR8(builder.build()));
+    CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build()));
     ClassSubject clazz = inspector.clazz(getJavacGeneratedClassName(testClass));
     assertTrue(clazz.isPresent());
     assertEquals(forceProguardCompatibility && hasDefaultConstructor,
@@ -195,7 +194,7 @@
     builder.addProguardConfiguration(proguardConfig, Origin.unknown());
 
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    DexInspector inspector = new DexInspector(ToolHelper.runR8(builder.build()));
+    CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build()));
     assertTrue(inspector.clazz(getJavacGeneratedClassName(mainClass)).isPresent());
     ClassSubject clazz = inspector.clazz(getJavacGeneratedClassName(instantiatedClass));
     assertEquals(containsCheckCast, clazz.isPresent());
@@ -210,7 +209,7 @@
       FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
       ToolHelper.runProguard(jarTestClasses(ImmutableList.of(mainClass, instantiatedClass)),
           proguardedJar, proguardConfigFile, null);
-      DexInspector proguardInspector = new DexInspector(readJar(proguardedJar));
+      CodeInspector proguardInspector = new CodeInspector(readJar(proguardedJar));
       assertTrue(proguardInspector.clazz(mainClass).isPresent());
       assertEquals(
           containsCheckCast, proguardInspector.clazz(instantiatedClass).isPresent());
@@ -257,11 +256,11 @@
     }
 
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    DexInspector inspector = new DexInspector(ToolHelper.runR8(builder.build()));
+    CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build()));
     assertTrue(inspector.clazz(getJavacGeneratedClassName(mainClass)).isPresent());
     forNameClasses.forEach(clazz -> {
       ClassSubject subject = inspector.clazz(getJavacGeneratedClassName(clazz));
-      assertEquals(forceProguardCompatibility, subject.isPresent());
+      assertTrue(subject.isPresent());
       assertEquals(subject.isPresent() && allowObfuscation, subject.isRenamed());
     });
 
@@ -273,8 +272,10 @@
     ProguardConfiguration configuration = parser.getConfigRawForTesting();
     if (forceProguardCompatibility) {
       List<ProguardConfigurationRule> rules = configuration.getRules();
-      assertEquals(3, rules.size());
-      Iterables.filter(rules, ProguardKeepRule.class).forEach(rule -> {
+      assertEquals(2, rules.size());
+      for (ProguardConfigurationRule r : rules) {
+        assertTrue(r instanceof ProguardKeepRule);
+        ProguardKeepRule rule = (ProguardKeepRule) r;
         assertEquals(ProguardKeepRuleType.KEEP, rule.getType());
         assertTrue(rule.getModifiers().allowsObfuscation);
         assertTrue(rule.getModifiers().allowsOptimization);
@@ -292,18 +293,7 @@
           assertEquals(1, memberRules.size());
           assertEquals(ProguardMemberType.INIT, memberRules.iterator().next().getRuleType());
         }
-      });
-      Iterables.filter(rules, ProguardIdentifierNameStringRule.class).forEach(rule -> {
-        List<ProguardMemberRule> memberRules = rule.getMemberRules();
-        ProguardClassNameList classNames = rule.getClassNames();
-        assertEquals(1, classNames.size());
-        DexType type = classNames.asSpecificDexTypes().get(0);
-        assertEquals("java.lang.Class", type.toSourceString());
-        assertEquals(1, memberRules.size());
-        ProguardMemberRule memberRule = memberRules.iterator().next();
-        assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
-        assertEquals("forName", memberRule.getName().toString());
-      });
+      }
     } else {
       assertEquals(0, configuration.getRules().size());
     }
@@ -316,7 +306,7 @@
       ToolHelper.runProguard(jarTestClasses(
           ImmutableList.of(mainClass, forNameClass1, forNameClass2)),
           proguardedJar, proguardConfigFile, proguardMapFile);
-      DexInspector proguardedInspector = new DexInspector(readJar(proguardedJar), proguardMapFile);
+      CodeInspector proguardedInspector = new CodeInspector(readJar(proguardedJar), proguardMapFile);
       assertEquals(3, proguardedInspector.allClasses().size());
       assertTrue(proguardedInspector.clazz(mainClass).isPresent());
       for (Class clazz : ImmutableList.of(forNameClass1, forNameClass2)) {
@@ -360,18 +350,18 @@
     }
 
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    DexInspector inspector = new DexInspector(ToolHelper.runR8(builder.build()));
+    CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build()));
     assertTrue(inspector.clazz(getJavacGeneratedClassName(mainClass)).isPresent());
     ClassSubject classSubject = inspector.clazz(getJavacGeneratedClassName(withMemberClass));
     // Due to the direct usage of .class
     assertTrue(classSubject.isPresent());
     assertEquals(allowObfuscation, classSubject.isRenamed());
     FieldSubject foo = classSubject.field("java.lang.String", "foo");
-    assertEquals(forceProguardCompatibility, foo.isPresent());
+    assertTrue(foo.isPresent());
     assertEquals(foo.isPresent() && allowObfuscation, foo.isRenamed());
     MethodSubject bar =
         classSubject.method("java.lang.String", "bar", ImmutableList.of("java.lang.String"));
-    assertEquals(forceProguardCompatibility, bar.isPresent());
+    assertTrue(bar.isPresent());
     assertEquals(bar.isPresent() && allowObfuscation, bar.isRenamed());
 
     // Check the Proguard compatibility rules generated.
@@ -382,8 +372,10 @@
     ProguardConfiguration configuration = parser.getConfigRawForTesting();
     if (forceProguardCompatibility) {
       List<ProguardConfigurationRule> rules = configuration.getRules();
-      assertEquals(4, rules.size());
-      Iterables.filter(rules, ProguardKeepRule.class).forEach(rule -> {
+      assertEquals(2, rules.size());
+      for (ProguardConfigurationRule r : rules) {
+        assertTrue(r instanceof ProguardKeepRule);
+        ProguardKeepRule rule = (ProguardKeepRule) r;
         assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS, rule.getType());
         assertTrue(rule.getModifiers().allowsObfuscation);
         assertTrue(rule.getModifiers().allowsOptimization);
@@ -400,18 +392,7 @@
           assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
           assertTrue(memberRule.getName().matches("bar"));
         }
-      });
-      Iterables.filter(rules, ProguardIdentifierNameStringRule.class).forEach(rule -> {
-        List<ProguardMemberRule> memberRules = rule.getMemberRules();
-        ProguardClassNameList classNames = rule.getClassNames();
-        assertEquals(1, classNames.size());
-        DexType type = classNames.asSpecificDexTypes().get(0);
-        assertEquals("java.lang.Class", type.toSourceString());
-        assertEquals(1, memberRules.size());
-        ProguardMemberRule memberRule = memberRules.iterator().next();
-        assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
-        assertTrue(memberRule.getName().toString().startsWith("getDeclared"));
-      });
+      }
     } else {
       assertEquals(0, configuration.getRules().size());
     }
@@ -424,7 +405,7 @@
       ToolHelper.runProguard(jarTestClasses(
           ImmutableList.of(mainClass, withMemberClass)),
           proguardedJar, proguardConfigFile, proguardMapFile);
-      DexInspector proguardedInspector = new DexInspector(readJar(proguardedJar), proguardMapFile);
+      CodeInspector proguardedInspector = new CodeInspector(readJar(proguardedJar), proguardMapFile);
       assertEquals(2, proguardedInspector.allClasses().size());
       assertTrue(proguardedInspector.clazz(mainClass).isPresent());
       classSubject = proguardedInspector.clazz(withMemberClass);
@@ -473,20 +454,20 @@
     }
 
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    DexInspector inspector = new DexInspector(ToolHelper.runR8(builder.build()));
+    CodeInspector inspector = new CodeInspector(ToolHelper.runR8(builder.build()));
     assertTrue(inspector.clazz(getJavacGeneratedClassName(mainClass)).isPresent());
     ClassSubject classSubject = inspector.clazz(getJavacGeneratedClassName(withVolatileFields));
     // Due to the direct usage of .class
     assertTrue(classSubject.isPresent());
     assertEquals(allowObfuscation, classSubject.isRenamed());
     FieldSubject f = classSubject.field("int", "intField");
-    assertEquals(forceProguardCompatibility, f.isPresent());
+    assertTrue(f.isPresent());
     assertEquals(f.isPresent() && allowObfuscation, f.isRenamed());
     f = classSubject.field("long", "longField");
-    assertEquals(forceProguardCompatibility, f.isPresent());
+    assertTrue(f.isPresent());
     assertEquals(f.isPresent() && allowObfuscation, f.isRenamed());
     f = classSubject.field("java.lang.Object", "objField");
-    assertEquals(forceProguardCompatibility, f.isPresent());
+    assertTrue(f.isPresent());
     assertEquals(f.isPresent() && allowObfuscation, f.isRenamed());
 
     // Check the Proguard compatibility rules generated.
@@ -497,9 +478,11 @@
     ProguardConfiguration configuration = parser.getConfigRawForTesting();
     if (forceProguardCompatibility) {
       List<ProguardConfigurationRule> rules = configuration.getRules();
-      assertEquals(6, rules.size());
+      assertEquals(3, rules.size());
       Object2BooleanMap<String> keptFields = new Object2BooleanArrayMap<>();
-      Iterables.filter(rules, ProguardKeepRule.class).forEach(rule -> {
+      for (ProguardConfigurationRule r : rules) {
+        assertTrue(r instanceof ProguardKeepRule);
+        ProguardKeepRule rule = (ProguardKeepRule) r;
         assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS, rule.getType());
         assertTrue(rule.getModifiers().allowsObfuscation);
         assertTrue(rule.getModifiers().allowsOptimization);
@@ -512,23 +495,11 @@
         ProguardMemberRule memberRule = memberRules.iterator().next();
         assertEquals(ProguardMemberType.FIELD, memberRule.getRuleType());
         keptFields.put(memberRule.getName().toString(), true);
-      });
+      }
       assertEquals(3, keptFields.size());
       assertTrue(keptFields.containsKey("intField"));
       assertTrue(keptFields.containsKey("longField"));
       assertTrue(keptFields.containsKey("objField"));
-      Iterables.filter(rules, ProguardIdentifierNameStringRule.class).forEach(rule -> {
-        List<ProguardMemberRule> memberRules = rule.getMemberRules();
-        ProguardClassNameList classNames = rule.getClassNames();
-        assertEquals(1, classNames.size());
-        DexType type = classNames.asSpecificDexTypes().get(0);
-        assertTrue(type.toSourceString().startsWith("java.util.concurrent.atomic.Atomic"));
-        assertTrue(type.toSourceString().endsWith("FieldUpdater"));
-        assertEquals(1, memberRules.size());
-        ProguardMemberRule memberRule = memberRules.iterator().next();
-        assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
-        assertEquals("newUpdater", memberRule.getName().toString());
-      });
     } else {
       assertEquals(0, configuration.getRules().size());
     }
@@ -541,7 +512,7 @@
       ToolHelper.runProguard(jarTestClasses(
           ImmutableList.of(mainClass, withVolatileFields)),
           proguardedJar, proguardConfigFile, proguardMapFile);
-      DexInspector proguardedInspector = new DexInspector(readJar(proguardedJar), proguardMapFile);
+      CodeInspector proguardedInspector = new CodeInspector(readJar(proguardedJar), proguardMapFile);
       assertEquals(2, proguardedInspector.allClasses().size());
       assertTrue(proguardedInspector.clazz(mainClass).isPresent());
       classSubject = proguardedInspector.clazz(withVolatileFields);
@@ -604,7 +575,7 @@
       assertTrue(!forceProguardCompatibility && (!innerClasses || !enclosingMethod));
       return;
     }
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     assertTrue(inspector.clazz(getJavacGeneratedClassName(mainClass)).isPresent());
     assertEquals(innerClasses || enclosingMethod ? "1" : "0", runOnArt(app, mainClass));
 
@@ -639,7 +610,7 @@
 
   private void runKeepDefaultMethodsTest(
       List<String> additionalKeepRules,
-      Consumer<DexInspector> inspection,
+      Consumer<CodeInspector> inspection,
       Consumer<ProguardConfiguration> compatInspection) throws Exception {
     Class mainClass = TestClass.class;
     CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder();
@@ -657,7 +628,7 @@
     Path proguardCompatibilityRules = temp.newFile().toPath();
     builder.setProguardCompatibilityRulesOutput(proguardCompatibilityRules);
     AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableClassInlining = false);
-    inspection.accept(new DexInspector(app));
+    inspection.accept(new CodeInspector(app));
     // Check the Proguard compatibility configuration generated.
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(),
@@ -671,7 +642,7 @@
     assertEquals(0, configuration.getRules().size());
   }
 
-  private void defaultMethodKept(DexInspector inspector) {
+  private void defaultMethodKept(CodeInspector inspector) {
     ClassSubject clazz = inspector.clazz(InterfaceWithDefaultMethods.class);
     assertTrue(clazz.isPresent());
     MethodSubject method = clazz.method("int", "method", ImmutableList.of());
@@ -695,7 +666,7 @@
     assertEquals(0, memberRule.getArguments().size());
   }
 
-  private void defaultMethod2Kept(DexInspector inspector) {
+  private void defaultMethod2Kept(CodeInspector inspector) {
     ClassSubject clazz = inspector.clazz(InterfaceWithDefaultMethods.class);
     assertTrue(clazz.isPresent());
     MethodSubject method =
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
deleted file mode 100644
index e3bffc6..0000000
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
+++ /dev/null
@@ -1,228 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.shaking.forceproguardcompatibility;
-
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-
-import com.android.tools.r8.CompatProguardCommandBuilder;
-import com.android.tools.r8.DexIndexedConsumer;
-import com.android.tools.r8.R8Command;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.collect.ImmutableList;
-import java.io.File;
-import java.nio.file.Path;
-import java.util.List;
-import java.util.function.Consumer;
-
-public class ProguardCompatabilityTestBase extends TestBase {
-
-  protected Path proguardMap;
-
-  public enum Shrinker {
-    PROGUARD5,
-    PROGUARD6,
-    PROGUARD6_THEN_D8,
-    R8_COMPAT,
-    R8
-  }
-
-  protected static boolean isR8(Shrinker shrinker) {
-    return shrinker == Shrinker.R8_COMPAT || shrinker == Shrinker.R8;
-  }
-
-  protected AndroidApp runShrinkerRaw(
-      Shrinker mode, List<Class> programClasses, Iterable<String> proguadConfigs) throws Exception {
-    return runShrinkerRaw(
-        mode, programClasses, String.join(System.lineSeparator(), proguadConfigs));
-  }
-
-  protected AndroidApp runShrinkerRaw(
-      Shrinker mode, List<Class> programClasses, String proguardConfig) throws Exception {
-    proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
-    switch (mode) {
-      case PROGUARD5:
-        return runProguard5Raw(programClasses, proguardConfig, proguardMap);
-      case PROGUARD6:
-        return runProguard6Raw(programClasses, proguardConfig, proguardMap);
-      case PROGUARD6_THEN_D8:
-        return runProguard6AndD8Raw(programClasses, proguardConfig, proguardMap);
-      case R8_COMPAT:
-        return runR8CompatRaw(programClasses, proguardConfig);
-      case R8:
-        return runR8Raw(programClasses, proguardConfig);
-    }
-    throw new IllegalArgumentException("Unknown shrinker: " + mode);
-  }
-
-  protected DexInspector runShrinker(
-      Shrinker mode, List<Class> programClasses, List<String> proguadConfigs) throws Exception {
-    return runShrinker(mode, programClasses, String.join(System.lineSeparator(), proguadConfigs));
-  }
-
-  protected DexInspector runShrinker(
-      Shrinker mode, List<Class> programClasses, String proguardConfig) throws Exception {
-    switch (mode) {
-      case PROGUARD5:
-        return runProguard5(programClasses, proguardConfig);
-      case PROGUARD6:
-        return runProguard6(programClasses, proguardConfig);
-      case PROGUARD6_THEN_D8:
-        return runProguard6AndD8(programClasses, proguardConfig);
-      case R8_COMPAT:
-        return runR8Compat(programClasses, proguardConfig);
-      case R8:
-        return runR8(programClasses, proguardConfig);
-    }
-    throw new IllegalArgumentException("Unknown shrinker: " + mode);
-  }
-
-  protected AndroidApp runR8Raw(List<Class> programClasses, String proguardConfig)
-      throws Exception {
-    return runR8Raw(programClasses, proguardConfig, null);
-  }
-
-  protected AndroidApp runR8Raw(
-      List<Class> programClasses, String proguardConfig, Consumer<InternalOptions> configure)
-      throws Exception {
-    AndroidApp app = readClassesAndAndriodJar(programClasses);
-    R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(app);
-    ToolHelper.allowTestProguardOptions(builder);
-    builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
-    return ToolHelper.runR8(builder.build(), configure);
-  }
-
-  protected DexInspector runR8(List<Class> programClasses, String proguardConfig) throws Exception {
-    return new DexInspector(runR8Raw(programClasses, proguardConfig));
-  }
-
-  protected AndroidApp runR8CompatRaw(
-      List<Class> programClasses, String proguardConfig) throws Exception {
-    CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder(true);
-    ToolHelper.allowTestProguardOptions(builder);
-    builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
-    programClasses.forEach(
-        clazz -> builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz)));
-    builder.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()));
-    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    return ToolHelper.runR8(builder.build());
-  }
-
-  protected DexInspector runR8Compat(
-      List<Class> programClasses, String proguardConfig) throws Exception {
-    return new DexInspector(runR8CompatRaw(programClasses, proguardConfig));
-  }
-
-  protected DexInspector runR8CompatKeepingMain(Class mainClass, List<Class> programClasses)
-      throws Exception {
-    return runR8Compat(programClasses, keepMainProguardConfiguration(mainClass));
-  }
-
-  protected AndroidApp runProguard5Raw(
-      List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
-    Path proguardedJar =
-        File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
-    Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
-    FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
-    ProcessResult result = ToolHelper.runProguardRaw(
-        jarTestClasses(programClasses),
-        proguardedJar,
-        ToolHelper.getAndroidJar(AndroidApiLevel.N),
-        proguardConfigFile,
-        proguardMap);
-    if (result.exitCode != 0) {
-      fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
-    }
-    return readJar(proguardedJar);
-  }
-
-  protected DexInspector runProguard5(
-      List<Class> programClasses, String proguardConfig) throws Exception {
-    proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
-    return new DexInspector(
-        runProguard5Raw(programClasses, proguardConfig, proguardMap), proguardMap);
-  }
-
-  protected AndroidApp runProguard6Raw(
-      List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
-    Path proguardedJar =
-        File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
-    Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
-    FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
-    ProcessResult result = ToolHelper.runProguard6Raw(
-        jarTestClasses(programClasses),
-        proguardedJar,
-        ToolHelper.getAndroidJar(AndroidApiLevel.N),
-        proguardConfigFile,
-        proguardMap);
-    if (result.exitCode != 0) {
-      fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
-    }
-    return readJar(proguardedJar);
-  }
-
-  protected DexInspector runProguard6(
-      List<Class> programClasses, String proguardConfig) throws Exception {
-    proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
-    return new DexInspector(
-        runProguard6Raw(programClasses, proguardConfig, proguardMap), proguardMap);
-  }
-
-  protected AndroidApp runProguard6AndD8Raw(
-      List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
-    Path proguardedJar =
-        File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
-    Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
-    FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
-    ProcessResult result = ToolHelper.runProguard6Raw(
-        jarTestClasses(programClasses),
-        proguardedJar,
-        ToolHelper.getAndroidJar(AndroidApiLevel.N),
-        proguardConfigFile,
-        proguardMap);
-    if (result.exitCode != 0) {
-      fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
-    }
-    return ToolHelper.runD8(readJar(proguardedJar));
-  }
-
-  protected DexInspector runProguard6AndD8(
-      List<Class> programClasses, String proguardConfig) throws Exception {
-    proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
-    return new DexInspector(
-        runProguard6AndD8Raw(programClasses, proguardConfig, proguardMap), proguardMap);
-  }
-
-  protected DexInspector runProguardKeepingMain(Class mainClass, List<Class> programClasses)
-      throws Exception {
-    return runProguard6AndD8(programClasses, keepMainProguardConfiguration(mainClass));
-  }
-
-  protected void verifyClassesPresent(
-      DexInspector dexInspector, Class<?>... classesOfInterest) {
-    for (Class klass : classesOfInterest) {
-      ClassSubject c = dexInspector.clazz(klass);
-      assertThat(c, isPresent());
-    }
-  }
-
-  protected void verifyClassesAbsent(
-      DexInspector dexInspector, Class<?>... classesOfInterest) {
-    for (Class klass : classesOfInterest) {
-      ClassSubject c = dexInspector.clazz(klass);
-      assertThat(c, not(isPresent()));
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
new file mode 100644
index 0000000..24f64c9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
@@ -0,0 +1,297 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.forceproguardcompatibility;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompatProguardCommandBuilder;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class ProguardCompatibilityTestBase extends TestBase {
+
+  protected Path proguardMap;
+
+  public enum Shrinker {
+    PROGUARD5,
+    PROGUARD6,
+    PROGUARD6_THEN_D8,
+    R8_COMPAT,
+    R8_COMPAT_CF,
+    R8,
+    R8_CF
+  }
+
+  protected static boolean isR8(Shrinker shrinker) {
+    return shrinker == Shrinker.R8_COMPAT
+        || shrinker == Shrinker.R8_COMPAT_CF
+        || shrinker == Shrinker.R8
+        || shrinker == Shrinker.R8_CF;
+  }
+
+  protected AndroidApp runShrinker(
+      Shrinker mode, List<Class> programClasses, Iterable<String> proguadConfigs) throws Exception {
+    return runShrinker(mode, programClasses, String.join(System.lineSeparator(), proguadConfigs));
+  }
+
+  protected AndroidApp runShrinker(Shrinker mode, List<Class> programClasses, String proguardConfig)
+      throws Exception {
+    proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
+    switch (mode) {
+      case PROGUARD5:
+        return runProguard5(programClasses, proguardConfig, proguardMap);
+      case PROGUARD6:
+        return runProguard6(programClasses, proguardConfig, proguardMap);
+      case PROGUARD6_THEN_D8:
+        return runProguard6AndD8(programClasses, proguardConfig, proguardMap);
+      case R8_COMPAT:
+        return runR8Compat(programClasses, proguardConfig, Backend.DEX);
+      case R8_COMPAT_CF:
+        return runR8Compat(programClasses, proguardConfig, Backend.CF);
+      case R8:
+        return runR8(programClasses, proguardConfig, Backend.DEX);
+      case R8_CF:
+        return runR8(programClasses, proguardConfig, Backend.CF);
+    }
+    throw new IllegalArgumentException("Unknown shrinker: " + mode);
+  }
+
+  protected CodeInspector inspectAfterShrinking(
+      Shrinker mode, List<Class> programClasses, List<String> proguadConfigs) throws Exception {
+    return inspectAfterShrinking(
+        mode, programClasses, String.join(System.lineSeparator(), proguadConfigs));
+  }
+
+  protected CodeInspector inspectAfterShrinking(
+      Shrinker mode, List<Class> programClasses, String proguardConfig) throws Exception {
+    switch (mode) {
+      case PROGUARD5:
+        return inspectProguard5Result(programClasses, proguardConfig);
+      case PROGUARD6:
+        return inspectProguard6Result(programClasses, proguardConfig);
+      case PROGUARD6_THEN_D8:
+        return inspectProguard6AndD8Result(programClasses, proguardConfig);
+      case R8_COMPAT:
+        return inspectR8CompatResult(programClasses, proguardConfig, Backend.DEX);
+      case R8_COMPAT_CF:
+        return inspectR8CompatResult(programClasses, proguardConfig, Backend.CF);
+      case R8:
+        return inspectR8Result(programClasses, proguardConfig, Backend.DEX);
+      case R8_CF:
+        return inspectR8Result(programClasses, proguardConfig, Backend.CF);
+    }
+    throw new IllegalArgumentException("Unknown shrinker: " + mode);
+  }
+
+  protected AndroidApp runR8(List<Class> programClasses, String proguardConfig, Backend backend)
+      throws Exception {
+    return runR8(programClasses, proguardConfig, null, backend);
+  }
+
+  protected AndroidApp runR8(
+      List<Class> programClasses,
+      String proguardConfig,
+      Consumer<InternalOptions> configure,
+      Backend backend)
+      throws Exception {
+    assert backend == Backend.DEX || backend == Backend.CF;
+    AndroidApp app = readClassesAndAndriodJar(programClasses);
+    R8Command.Builder builder =
+        ToolHelper.prepareR8CommandBuilder(
+                app,
+                backend == Backend.DEX
+                    ? DexIndexedConsumer.emptyConsumer()
+                    : ClassFileConsumer.emptyConsumer())
+            .addLibraryFiles(
+                backend == Backend.DEX
+                    ? ToolHelper.getDefaultAndroidJar()
+                    : ToolHelper.getJava8RuntimeJar());
+    ToolHelper.allowTestProguardOptions(builder);
+    builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
+    return ToolHelper.runR8(builder.build(), configure);
+  }
+
+  protected CodeInspector inspectR8Result(
+      List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
+    return new CodeInspector(runR8(programClasses, proguardConfig, backend));
+  }
+
+  protected AndroidApp runR8Compat(
+      List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
+    CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder(true);
+    ToolHelper.allowTestProguardOptions(builder);
+    builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
+    programClasses.forEach(
+        clazz -> builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz)));
+    if (backend == Backend.DEX) {
+      builder.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()));
+      builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+    } else {
+      assert backend == Backend.CF;
+      builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+      builder.setProgramConsumer(ClassFileConsumer.emptyConsumer());
+    }
+    return ToolHelper.runR8(builder.build());
+  }
+
+  protected CodeInspector inspectR8CompatResult(
+      List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
+    return new CodeInspector(runR8Compat(programClasses, proguardConfig, backend));
+  }
+
+  protected AndroidApp runProguard5(
+      List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
+    Path proguardedJar =
+        File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+    Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
+    FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
+    ProcessResult result = ToolHelper.runProguardRaw(
+        jarTestClasses(programClasses),
+        proguardedJar,
+        ToolHelper.getAndroidJar(AndroidApiLevel.N),
+        proguardConfigFile,
+        proguardMap);
+    if (result.exitCode != 0) {
+      fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
+    }
+    return readJar(proguardedJar);
+  }
+
+  protected CodeInspector inspectProguard5Result(List<Class> programClasses, String proguardConfig)
+      throws Exception {
+    proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
+    return new CodeInspector(
+        runProguard5(programClasses, proguardConfig, proguardMap), proguardMap);
+  }
+
+  protected ProcessResult runProguard6Raw(
+      Path destination,
+      List<Class> programClasses,
+      String proguardConfig,
+      Path proguardMap,
+      List<DataEntryResource> dataResources)
+      throws Exception {
+    return runProguard6Raw(
+        destination,
+        jarTestClasses(programClasses, dataResources),
+        proguardConfig,
+        proguardMap,
+        null);
+  }
+
+  protected ProcessResult runProguard6Raw(
+      Path destination, Path jar, String proguardConfig, Path proguardMap) throws Exception {
+    return runProguard6Raw(destination, jar, proguardConfig, proguardMap, null);
+  }
+
+  protected ProcessResult runProguard6Raw(
+      Path destination,
+      Path jar,
+      String proguardConfig,
+      Path proguardMap,
+      Consumer<ProcessResult> processResultConsumer)
+      throws Exception {
+    Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
+    FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
+    ProcessResult result =
+        ToolHelper.runProguard6Raw(
+            jar,
+            destination,
+            ToolHelper.getAndroidJar(AndroidApiLevel.N),
+            proguardConfigFile,
+            proguardMap);
+    if (result.exitCode != 0) {
+      fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
+    }
+    if (processResultConsumer != null) {
+      processResultConsumer.accept(result);
+    }
+    return result;
+  }
+
+  protected AndroidApp runProguard6(
+      List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
+    return runProguard6(programClasses, proguardConfig, proguardMap, null);
+  }
+
+  protected AndroidApp runProguard6(
+      List<Class> programClasses,
+      String proguardConfig,
+      Path proguardMap,
+      List<DataEntryResource> dataResources)
+      throws Exception {
+    Path proguardedJar =
+        File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+    runProguard6Raw(proguardedJar, programClasses, proguardConfig, proguardMap, dataResources);
+    return readJar(proguardedJar);
+  }
+
+  protected CodeInspector inspectProguard6Result(List<Class> programClasses, String proguardConfig)
+      throws Exception {
+    proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
+    return new CodeInspector(
+        runProguard6(programClasses, proguardConfig, proguardMap), proguardMap);
+  }
+
+  protected AndroidApp runProguard6AndD8(
+      List<Class> programClasses, String proguardConfig, Path proguardMap) throws Exception {
+    Path proguardedJar =
+        File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, temp.getRoot()).toPath();
+    Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
+    FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
+    ProcessResult result = ToolHelper.runProguard6Raw(
+        jarTestClasses(programClasses),
+        proguardedJar,
+        ToolHelper.getAndroidJar(AndroidApiLevel.N),
+        proguardConfigFile,
+        proguardMap);
+    if (result.exitCode != 0) {
+      fail("Proguard failed, exit code " + result.exitCode + ", stderr:\n" + result.stderr);
+    }
+    return ToolHelper.runD8(readJar(proguardedJar));
+  }
+
+  protected CodeInspector inspectProguard6AndD8Result(
+      List<Class> programClasses, String proguardConfig) throws Exception {
+    proguardMap = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
+    return new CodeInspector(
+        runProguard6AndD8(programClasses, proguardConfig, proguardMap), proguardMap);
+  }
+
+  protected void verifyClassesPresent(
+      CodeInspector codeInspector, Class<?>... classesOfInterest) {
+    for (Class klass : classesOfInterest) {
+      ClassSubject c = codeInspector.clazz(klass);
+      assertThat(c, isPresent());
+    }
+  }
+
+  protected void verifyClassesAbsent(
+      CodeInspector codeInspector, Class<?>... classesOfInterest) {
+    for (Class klass : classesOfInterest) {
+      ClassSubject c = codeInspector.clazz(klass);
+      assertThat(c, not(isPresent()));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
index d8615e0..1f5ea77 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
@@ -4,19 +4,23 @@
 
 package com.android.tools.r8.shaking.forceproguardcompatibility.defaultctor;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.hasDefaultConstructor;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.hasDefaultConstructor;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.core.IsNot.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
-import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
 import com.android.tools.r8.smali.ConstantFoldingTest.TriConsumer;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 class SuperClass {
 
@@ -90,7 +94,19 @@
   }
 }
 
-public class ImplicitlyKeptDefaultConstructorTest extends ProguardCompatabilityTestBase {
+@RunWith(Parameterized.class)
+public class ImplicitlyKeptDefaultConstructorTest extends ProguardCompatibilityTestBase {
+
+  private Backend backend;
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public ImplicitlyKeptDefaultConstructorTest(Backend backend) {
+    this.backend = backend;
+  }
 
   private void checkPresentWithDefaultConstructor(ClassSubject clazz) {
     assertThat(clazz, isPresent());
@@ -103,14 +119,14 @@
   }
 
   private void checkAllClassesPresentWithDefaultConstructor(
-      Class mainClass, List<Class> programClasses, DexInspector inspector) {
+      Class mainClass, List<Class> programClasses, CodeInspector inspector) {
     assert programClasses.contains(mainClass);
     assertEquals(programClasses.size(), inspector.allClasses().size());
     inspector.forAllClasses(this::checkPresentWithDefaultConstructor);
   }
 
   private void checkAllClassesPresentOnlyMainWithDefaultConstructor(
-      Class mainClass, List<Class> programClasses, DexInspector inspector) {
+      Class mainClass, List<Class> programClasses, CodeInspector inspector) {
     assert programClasses.contains(mainClass);
     assertEquals(programClasses.size(), inspector.allClasses().size());
     checkPresentWithDefaultConstructor(inspector.clazz(mainClass));
@@ -121,7 +137,7 @@
   }
 
   private void checkOnlyMainPresent(
-      Class mainClass, List<Class> programClasses, DexInspector inspector) {
+      Class mainClass, List<Class> programClasses, CodeInspector inspector) {
     assert programClasses.contains(mainClass);
     assertEquals(1, inspector.allClasses().size());
     inspector.forAllClasses(this::checkPresentWithDefaultConstructor);
@@ -129,22 +145,22 @@
 
   private void runTest(
       Class mainClass, List<Class> programClasses, String proguardConfiguration,
-      TriConsumer<Class, List<Class>, DexInspector> r8Checker,
-      TriConsumer<Class, List<Class>, DexInspector> proguardChecker) throws Exception {
-    DexInspector inspector = runR8Compat(programClasses, proguardConfiguration);
+      TriConsumer<Class, List<Class>, CodeInspector> r8Checker,
+      TriConsumer<Class, List<Class>, CodeInspector> proguardChecker) throws Exception {
+    CodeInspector inspector = inspectR8CompatResult(programClasses, proguardConfiguration, backend);
     r8Checker.accept(mainClass, programClasses, inspector);
 
     if (isRunProguard()) {
-      inspector = runProguard6(programClasses, proguardConfiguration);
+      inspector = inspectProguard6Result(programClasses, proguardConfiguration);
       proguardChecker.accept(mainClass, programClasses, inspector);
-      inspector = runProguard6AndD8(programClasses, proguardConfiguration);
+      inspector = inspectProguard6AndD8Result(programClasses, proguardConfiguration);
       proguardChecker.accept(mainClass, programClasses, inspector);
     }
   }
 
   private void runTest(
       Class mainClass, List<Class> programClasses, String proguardConfiguration,
-      TriConsumer<Class, List<Class>, DexInspector> checker) throws Exception {
+      TriConsumer<Class, List<Class>, CodeInspector> checker) throws Exception {
     runTest(mainClass, programClasses, proguardConfiguration, checker, checker);
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
index 65d1763..23aad75 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -3,17 +3,17 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.ifrule;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.List;
@@ -23,7 +23,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class IfOnAccessModifierTest extends ProguardCompatabilityTestBase {
+public class IfOnAccessModifierTest extends ProguardCompatibilityTestBase {
     private final static List<Class> CLASSES = ImmutableList.of(
         ClassForIf.class, ClassForSubsequent.class,
         MainForAccessModifierTest.class);
@@ -59,8 +59,8 @@
         "  public <methods>;",
         "}"
     );
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
-    ClassSubject classSubject = dexInspector.clazz(ClassForIf.class);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(publicMethod);
     assertThat(methodSubject, not(isPresent()));
@@ -68,7 +68,7 @@
     assertThat(methodSubject, isPresent());
     assertTrue(methodSubject.getMethod().accessFlags.isPublic());
 
-    classSubject = dexInspector.clazz(ClassForSubsequent.class);
+    classSubject = codeInspector.clazz(ClassForSubsequent.class);
     if (isR8(shrinker)) {
       // TODO(b/72109068): ClassForIf#nonPublicMethod becomes public, and -if rule is not applied
       // at the 2nd tree shaking.
@@ -98,8 +98,8 @@
         "  !public <methods>;",
         "}"
     );
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
-    ClassSubject classSubject = dexInspector.clazz(ClassForIf.class);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(publicMethod);
     assertThat(methodSubject, not(isPresent()));
@@ -107,7 +107,7 @@
     assertThat(methodSubject, isPresent());
     assertTrue(methodSubject.getMethod().accessFlags.isPublic());
 
-    classSubject = dexInspector.clazz(ClassForSubsequent.class);
+    classSubject = codeInspector.clazz(ClassForSubsequent.class);
     if (isR8(shrinker)) {
       // TODO(b/72109068): ClassForIf#nonPublicMethod becomes public, and -if rule is not applied
       // at the 2nd tree shaking.
@@ -138,15 +138,15 @@
         "  public <methods>;",
         "}"
     );
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
-    ClassSubject classSubject = dexInspector.clazz(ClassForIf.class);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(publicMethod);
     assertThat(methodSubject, isPresent());
     methodSubject = classSubject.method(nonPublicMethod);
     assertThat(methodSubject, not(isPresent()));
 
-    classSubject = dexInspector.clazz(ClassForSubsequent.class);
+    classSubject = codeInspector.clazz(ClassForSubsequent.class);
     assertThat(classSubject, isPresent());
     methodSubject = classSubject.method(publicMethod);
     assertThat(methodSubject, isPresent());
@@ -170,15 +170,15 @@
         "  !public <methods>;",
         "}"
     );
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
-    ClassSubject classSubject = dexInspector.clazz(ClassForIf.class);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(publicMethod);
     assertThat(methodSubject, isPresent());
     methodSubject = classSubject.method(nonPublicMethod);
     assertThat(methodSubject, not(isPresent()));
 
-    classSubject = dexInspector.clazz(ClassForSubsequent.class);
+    classSubject = codeInspector.clazz(ClassForSubsequent.class);
     assertThat(classSubject, isPresent());
     methodSubject = classSubject.method(publicMethod);
     assertThat(methodSubject, not(isPresent()));
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
index 4d0281f..20363c5 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
@@ -3,8 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.ifrule;
 
-import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.List;
@@ -14,7 +14,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class IfOnAnnotationTest extends ProguardCompatabilityTestBase {
+public class IfOnAnnotationTest extends ProguardCompatibilityTestBase {
   private final static List<Class> CLASSES = ImmutableList.of(
       UsedAnnotation.class, UnusedAnnotation.class,
       UsedAnnotationDependent.class, UnusedAnnotationDependent.class,
@@ -50,10 +50,10 @@
         "-keep class **.UnusedAnnotation*"
     );
 
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
-    verifyClassesAbsent(dexInspector,
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    verifyClassesAbsent(codeInspector,
         UnusedAnnotation.class, UnusedAnnotationDependent.class);
-    verifyClassesPresent(dexInspector,
+    verifyClassesPresent(codeInspector,
         UsedAnnotation.class, UsedAnnotationDependent.class);
   }
 
@@ -76,10 +76,10 @@
         "-keep class <1>.Unused<2>*"
     );
 
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
-    verifyClassesAbsent(dexInspector,
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    verifyClassesAbsent(codeInspector,
         UnusedAnnotation.class, UnusedAnnotationDependent.class);
-    verifyClassesPresent(dexInspector,
+    verifyClassesPresent(codeInspector,
         UsedAnnotation.class, UsedAnnotationDependent.class);
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
index 76c1023..906788a 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
@@ -3,18 +3,18 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.ifrule;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isNotRenamed;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
-import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -25,7 +25,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class IfOnClassTest extends ProguardCompatabilityTestBase {
+public class IfOnClassTest extends ProguardCompatibilityTestBase {
   private final static List<Class> CLASSES = ImmutableList.of(
       EmptyMainClassForIfOnClassTests.class,
       Precondition.class, DependentUser.class, Dependent.class);
@@ -60,21 +60,21 @@
   }
 
   @Override
-  protected DexInspector runR8(
-      List<Class> programClasses, String proguardConfig) throws Exception {
-    return super.runR8(programClasses, adaptConfiguration(proguardConfig));
+  protected CodeInspector inspectR8Result(
+      List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
+    return super.inspectR8Result(programClasses, adaptConfiguration(proguardConfig), backend);
   }
 
   @Override
-  protected DexInspector runProguard5(
-      List<Class> programClasses, String proguardConfig) throws Exception {
-    return super.runProguard5(programClasses, adaptConfiguration(proguardConfig));
+  protected CodeInspector inspectProguard5Result(List<Class> programClasses, String proguardConfig)
+      throws Exception {
+    return super.inspectProguard5Result(programClasses, adaptConfiguration(proguardConfig));
   }
 
   @Override
-  protected DexInspector runProguard6(
-      List<Class> programClasses, String proguardConfig) throws Exception {
-    return super.runProguard6(programClasses, adaptConfiguration(proguardConfig));
+  protected CodeInspector inspectProguard6Result(List<Class> programClasses, String proguardConfig)
+      throws Exception {
+    return super.inspectProguard6Result(programClasses, adaptConfiguration(proguardConfig));
   }
 
   @Test
@@ -86,17 +86,17 @@
         "-keep,allowobfuscation class **.Dependent"
     );
 
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     if (!keepPrecondition) {
       // TODO(b/73708139): Proguard6 kept Dependent (w/o any members), which is not necessary.
       if (shrinker == Shrinker.PROGUARD6) {
         return;
       }
-      assertEquals(1, dexInspector.allClasses().size());
+      assertEquals(1, codeInspector.allClasses().size());
       return;
     }
 
-    ClassSubject clazz = dexInspector.clazz(DependentUser.class);
+    ClassSubject clazz = codeInspector.clazz(DependentUser.class);
     assertThat(clazz, isRenamed());
     // Members of DependentUser are not used anywhere.
     MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of());
@@ -105,7 +105,7 @@
     assertThat(f, not(isPresent()));
 
     // Although DependentUser#callFoo is shrinked, Dependent is kept via -if.
-    clazz = dexInspector.clazz(Dependent.class);
+    clazz = codeInspector.clazz(Dependent.class);
     assertThat(clazz, isRenamed());
     // But, its members are gone.
     m = clazz.method("java.lang.String", "foo", ImmutableList.of());
@@ -123,17 +123,17 @@
         "-keep,allowobfuscation class <1>.<2>"
     );
 
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     if (!keepPrecondition) {
       // TODO(b/73708139): Proguard6 kept Dependent (w/o any members), which is not necessary.
       if (shrinker == Shrinker.PROGUARD6) {
         return;
       }
-      assertEquals(1, dexInspector.allClasses().size());
+      assertEquals(1, codeInspector.allClasses().size());
       return;
     }
 
-    ClassSubject clazz = dexInspector.clazz(DependentUser.class);
+    ClassSubject clazz = codeInspector.clazz(DependentUser.class);
     assertThat(clazz, isRenamed());
     // Members of DependentUser are not used anywhere.
     MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of());
@@ -142,7 +142,7 @@
     assertThat(f, not(isPresent()));
 
     // Although DependentUser#callFoo is shrinked, Dependent is kept via -if.
-    clazz = dexInspector.clazz(Dependent.class);
+    clazz = codeInspector.clazz(Dependent.class);
     assertThat(clazz, isRenamed());
     // But, its members are gone.
     m = clazz.method("java.lang.String", "foo", ImmutableList.of());
@@ -160,13 +160,13 @@
         "}"
     );
 
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     if (!keepPrecondition) {
-      assertEquals(1, dexInspector.allClasses().size());
+      assertEquals(1, codeInspector.allClasses().size());
       return;
     }
 
-    ClassSubject clazz = dexInspector.clazz(DependentUser.class);
+    ClassSubject clazz = codeInspector.clazz(DependentUser.class);
     assertThat(clazz, isRenamed());
     MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of());
     assertThat(m, isRenamed());
@@ -174,7 +174,7 @@
     assertThat(f, not(isPresent()));
 
     // Dependent is kept due to DependentUser#callFoo, but renamed.
-    clazz = dexInspector.clazz(Dependent.class);
+    clazz = codeInspector.clazz(Dependent.class);
     assertThat(clazz, isRenamed());
     m = clazz.method("java.lang.String", "foo", ImmutableList.of());
     assertThat(m, isRenamed());
@@ -196,17 +196,17 @@
         "-keep,allowobfuscation class **.*User"
     );
 
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     if (!keepPrecondition) {
       // TODO(b/73708139): Proguard6 kept DependentUser (w/o any members), which is not necessary.
       if (shrinker == Shrinker.PROGUARD6) {
         return;
       }
-      assertEquals(1, dexInspector.allClasses().size());
+      assertEquals(1, codeInspector.allClasses().size());
       return;
     }
 
-    ClassSubject clazz = dexInspector.clazz(DependentUser.class);
+    ClassSubject clazz = codeInspector.clazz(DependentUser.class);
     assertThat(clazz, isRenamed());
     MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of());
     assertThat(m, isRenamed());
@@ -214,7 +214,7 @@
     assertThat(f, not(isPresent()));
 
     // Dependent is kept due to DependentUser#callFoo, but renamed.
-    clazz = dexInspector.clazz(Dependent.class);
+    clazz = codeInspector.clazz(Dependent.class);
     assertThat(clazz, isRenamed());
     m = clazz.method("java.lang.String", "foo", ImmutableList.of());
     assertThat(m, isRenamed());
@@ -236,17 +236,17 @@
         "-keep,allowobfuscation class <1>.<2>User"
     );
 
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     if (!keepPrecondition) {
       // TODO(b/73708139): Proguard6 kept DependentUser (w/o any members), which is not necessary.
       if (shrinker == Shrinker.PROGUARD6) {
         return;
       }
-      assertEquals(1, dexInspector.allClasses().size());
+      assertEquals(1, codeInspector.allClasses().size());
       return;
     }
 
-    ClassSubject clazz = dexInspector.clazz(DependentUser.class);
+    ClassSubject clazz = codeInspector.clazz(DependentUser.class);
     assertThat(clazz, isRenamed());
     MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of());
     assertThat(m, isRenamed());
@@ -254,7 +254,7 @@
     assertThat(f, not(isPresent()));
 
     // Dependent is kept due to DependentUser#callFoo, but renamed.
-    clazz = dexInspector.clazz(Dependent.class);
+    clazz = codeInspector.clazz(Dependent.class);
     assertThat(clazz, isRenamed());
     m = clazz.method("java.lang.String", "foo", ImmutableList.of());
     assertThat(m, isRenamed());
@@ -274,9 +274,9 @@
         "-keepnames class **.Dependent"
     );
 
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
 
-    ClassSubject clazz = dexInspector.clazz(Dependent.class);
+    ClassSubject clazz = codeInspector.clazz(Dependent.class);
     // Only class name is not renamed, if triggered.
     assertThat(clazz, keepPrecondition ? isNotRenamed() : isRenamed());
     MethodSubject m = clazz.method("java.lang.String", "foo", ImmutableList.of());
@@ -299,9 +299,9 @@
         "}"
     );
 
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
 
-    ClassSubject clazz = dexInspector.clazz(Dependent.class);
+    ClassSubject clazz = codeInspector.clazz(Dependent.class);
     // Class name is not renamed, if triggered.
     assertThat(clazz, keepPrecondition ? isNotRenamed() : isRenamed());
     MethodSubject m = clazz.method("java.lang.String", "foo", ImmutableList.of());
@@ -325,9 +325,9 @@
         "}"
     );
 
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
 
-    ClassSubject clazz = dexInspector.clazz(Dependent.class);
+    ClassSubject clazz = codeInspector.clazz(Dependent.class);
     assertThat(clazz, isRenamed());
     MethodSubject m = clazz.method("java.lang.String", "foo", ImmutableList.of());
     // Only method name is not renamed, if triggered.
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
index 7480e2e..79c132b 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
@@ -3,8 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.ifrule;
 
-import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -15,7 +15,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class IfOnFieldTest extends ProguardCompatabilityTestBase {
+public class IfOnFieldTest extends ProguardCompatibilityTestBase {
   private final static List<Class> CLASSES = ImmutableList.of(
       D.class, D1.class, D2.class,
       R.class, R1.class, R2.class,
@@ -42,15 +42,15 @@
   }
 
   @Override
-  protected DexInspector runR8(
-      List<Class> programClasses, String proguardConfig) throws Exception {
-    return super.runR8(programClasses, adaptConfiguration(proguardConfig));
+  protected CodeInspector inspectR8Result(
+      List<Class> programClasses, String proguardConfig, Backend backend) throws Exception {
+    return super.inspectR8Result(programClasses, adaptConfiguration(proguardConfig), backend);
   }
 
   @Override
-  protected DexInspector runProguard6(
-      List<Class> programClasses, String proguardConfig) throws Exception {
-    return super.runProguard6(programClasses, adaptConfiguration(proguardConfig));
+  protected CodeInspector inspectProguard6Result(List<Class> programClasses, String proguardConfig)
+      throws Exception {
+    return super.inspectProguard6Result(programClasses, adaptConfiguration(proguardConfig));
   }
 
   @Test
@@ -77,10 +77,10 @@
         "-keep class **.D"
     );
 
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
-    verifyClassesAbsent(dexInspector,
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    verifyClassesAbsent(codeInspector,
         R1.class, R2.class, D.class, D2.class);
-    verifyClassesPresent(dexInspector,
+    verifyClassesPresent(codeInspector,
         R.class, D1.class);
   }
 
@@ -96,10 +96,10 @@
         "-keep class **.D<2>"
     );
 
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
-    verifyClassesAbsent(dexInspector,
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    verifyClassesAbsent(codeInspector,
         R1.class, R2.class, D.class, D2.class);
-    verifyClassesPresent(dexInspector,
+    verifyClassesPresent(codeInspector,
         R.class, D1.class);
   }
 
@@ -119,10 +119,10 @@
         "-keep class **.D2"
     );
 
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
-    verifyClassesAbsent(dexInspector,
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    verifyClassesAbsent(codeInspector,
         R.class, D.class, R1.class, D1.class);
-    verifyClassesPresent(dexInspector,
+    verifyClassesPresent(codeInspector,
         R2.class, D2.class);
   }
 
@@ -138,10 +138,10 @@
         "-keep class **.D<2>"
     );
 
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
-    verifyClassesAbsent(dexInspector,
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    verifyClassesAbsent(codeInspector,
         R.class, D.class, R1.class, D1.class);
-    verifyClassesPresent(dexInspector,
+    verifyClassesPresent(codeInspector,
         R2.class, D2.class);
   }
 
@@ -158,10 +158,10 @@
         "-keep class **$*D"
     );
 
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
-    verifyClassesAbsent(dexInspector,
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    verifyClassesAbsent(codeInspector,
         R.class, D.class, R1.class, D1.class, R2.class, D2.class);
-    verifyClassesPresent(dexInspector,
+    verifyClassesPresent(codeInspector,
         MainWithInner.InnerR.class, MainWithInner.InnerD.class);
   }
 
@@ -178,10 +178,10 @@
         "-keep class <1>$<2>D"
     );
 
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
-    verifyClassesAbsent(dexInspector,
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    verifyClassesAbsent(codeInspector,
         R.class, D.class, R1.class, D1.class, R2.class, D2.class);
-    verifyClassesPresent(dexInspector,
+    verifyClassesPresent(codeInspector,
         MainWithInner.InnerR.class, MainWithInner.InnerD.class);
   }
 
@@ -202,9 +202,9 @@
             "}",
             "-keep class **.D2");
 
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
-    verifyClassesAbsent(dexInspector, D2.class);
-    verifyClassesPresent(dexInspector,
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    verifyClassesAbsent(codeInspector, D2.class);
+    verifyClassesPresent(codeInspector,
         I.class, Impl.class, D1.class);
   }
 
@@ -225,9 +225,9 @@
             "}",
             "-keep class <2>.D2");
 
-    DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
-    verifyClassesAbsent(dexInspector, D2.class);
-    verifyClassesPresent(dexInspector,
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
+    verifyClassesAbsent(codeInspector, D2.class);
+    verifyClassesPresent(codeInspector,
         I.class, Impl.class, D1.class);
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java b/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
index 22f9ef6..fb9ce61 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/inlining/IfRuleWithInlining.java
@@ -4,15 +4,15 @@
 
 package com.android.tools.r8.shaking.ifrule.inlining;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.List;
@@ -43,7 +43,7 @@
 }
 
 @RunWith(Parameterized.class)
-public class IfRuleWithInlining extends ProguardCompatabilityTestBase {
+public class IfRuleWithInlining extends ProguardCompatibilityTestBase {
   private final static List<Class> CLASSES = ImmutableList.of(
       A.class, D.class, Main.class);
 
@@ -65,7 +65,7 @@
   }
 
   private void check(AndroidApp app) throws Exception {
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     ClassSubject clazzA = inspector.clazz(A.class);
     assertThat(clazzA, isPresent());
     // A.a might be inlined.
@@ -90,6 +90,6 @@
         "-dontobfuscate"
     );
 
-    check(runShrinkerRaw(shrinker, CLASSES, config));
+    check(runShrinker(shrinker, CLASSES, config));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
index b484d73..1cec850 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/IfRuleWithVerticalClassMerging.java
@@ -4,16 +4,16 @@
 
 package com.android.tools.r8.shaking.ifrule.verticalclassmerging;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.List;
@@ -58,7 +58,7 @@
 }
 
 @RunWith(Parameterized.class)
-public class IfRuleWithVerticalClassMerging extends ProguardCompatabilityTestBase {
+public class IfRuleWithVerticalClassMerging extends ProguardCompatibilityTestBase {
   private final static List<Class> CLASSES = ImmutableList.of(
       A.class, B.class, C.class, D.class, Main.class);
 
@@ -84,13 +84,13 @@
   }
 
   @Override
-  protected AndroidApp runR8Raw(
-      List<Class> programClasses, String proguardConfig) throws Exception {
-    return super.runR8Raw(programClasses, proguardConfig, this::configure);
+  protected AndroidApp runR8(List<Class> programClasses, String proguardConfig, Backend backend)
+      throws Exception {
+    return super.runR8(programClasses, proguardConfig, this::configure, backend);
   }
 
   private void check(AndroidApp app) throws Exception {
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     ClassSubject clazzA = inspector.clazz(A.class);
     assertEquals(!enableClassMerging, clazzA.isPresent());
     ClassSubject clazzB = inspector.clazz(B.class);
@@ -119,7 +119,7 @@
         "-dontobfuscate"
     );
 
-    check(runShrinkerRaw(shrinker, CLASSES, config));
+    check(runShrinker(shrinker, CLASSES, config));
   }
 
   @Test
@@ -135,7 +135,7 @@
         "-dontobfuscate"
     );
 
-    check(runShrinkerRaw(shrinker, CLASSES, config));
+    check(runShrinker(shrinker, CLASSES, config));
   }
 
   @Test
@@ -151,6 +151,6 @@
         "-dontobfuscate"
     );
 
-    check(runShrinkerRaw(shrinker, CLASSES, config));
+    check(runShrinker(shrinker, CLASSES, config));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
index cae3b06..bc94fa6 100644
--- a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -18,10 +18,10 @@
 
 public class IncludeDescriptorClassesTest extends TestBase {
   private class Result {
-    final DexInspector inspector;
-    final DexInspector proguardedInspector;
+    final CodeInspector inspector;
+    final CodeInspector proguardedInspector;
 
-    Result(DexInspector inspector, DexInspector proguardedInspector) {
+    Result(CodeInspector inspector, CodeInspector proguardedInspector) {
       this.inspector = inspector;
       this.proguardedInspector = proguardedInspector;
     }
@@ -62,15 +62,15 @@
     List<Class> classes = new ArrayList<>(applicationClasses);
     classes.add(mainClass);
 
-    DexInspector inspector = new DexInspector(compileWithR8(classes, proguardConfig));
+    CodeInspector inspector = new CodeInspector(compileWithR8(classes, proguardConfig));
 
-    DexInspector proguardedInspector = null;
+    CodeInspector proguardedInspector = null;
     // Actually running Proguard should only be during development.
     if (isRunProguard()) {
       Path proguardedJar = temp.newFolder().toPath().resolve("proguarded.jar");
       Path proguardedMap = temp.newFolder().toPath().resolve("proguarded.map");
       ToolHelper.runProguard(jarTestClasses(classes), proguardedJar, proguardConfig, proguardedMap);
-      proguardedInspector = new DexInspector(readJar(proguardedJar), proguardedMap);
+      proguardedInspector = new CodeInspector(readJar(proguardedJar), proguardedMap);
     }
 
     return new Result(inspector, proguardedInspector);
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersTest.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersTest.java
index 12bcdfd..cff0675 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersTest.java
@@ -4,24 +4,24 @@
 
 package com.android.tools.r8.shaking.keepclassmembers;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isAbstract;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbstract;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.core.IsNot.not;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
-public class KeepClassMembersTest extends ProguardCompatabilityTestBase {
+public class KeepClassMembersTest extends ProguardCompatibilityTestBase {
 
-  private void check(DexInspector inspector, Class mainClass, Class<?> staticClass,
+  private void check(CodeInspector inspector, Class mainClass, Class<?> staticClass,
       boolean forceProguardCompatibility, boolean fromProguard) {
     assertTrue(inspector.clazz(mainClass).isPresent());
     ClassSubject staticClassSubject = inspector.clazz(staticClass);
@@ -70,14 +70,14 @@
         "}",
         "-dontoptimize", "-dontobfuscate"
     ));
-    DexInspector inspector;
-      inspector = new DexInspector(
+    CodeInspector inspector;
+      inspector = new CodeInspector(
           compileWithR8(ImmutableList.of(mainClass, staticClass), proguardConfig,
               options -> options.forceProguardCompatibility = forceProguardCompatibility));
     check(inspector, mainClass, staticClass, forceProguardCompatibility, false);
 
     if (isRunProguard()) {
-      inspector = runProguard6(ImmutableList.of(mainClass, staticClass), proguardConfig);
+      inspector = inspectProguard6Result(ImmutableList.of(mainClass, staticClass), proguardConfig);
       check(inspector, mainClass, staticClass, true, true);
     }
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
index 8e39b67..b3e6367 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/ProxiesTest.java
@@ -6,14 +6,11 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.ClassFileConsumer;
 import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.code.InvokeInterface;
-import com.android.tools.r8.code.InvokeVirtual;
-import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.invokesuper.Consumer;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.origin.Origin;
@@ -23,14 +20,35 @@
 import com.android.tools.r8.shaking.proxy.testclasses.SubInterface;
 import com.android.tools.r8.shaking.proxy.testclasses.TestClass;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Streams;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
+import java.util.function.Predicate;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class ProxiesTest extends TestBase {
+  private Backend backend;
 
-  private void runTest(List<String> additionalKeepRules, Consumer<DexInspector> inspection,
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public ProxiesTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  private void runTest(List<String> additionalKeepRules, Consumer<CodeInspector> inspection,
       String expectedResult)
       throws Exception {
     Class mainClass = Main.class;
@@ -49,52 +67,91 @@
         Origin.unknown()
     );
     builder.addProguardConfiguration(additionalKeepRules, Origin.unknown());
-    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+    if (backend == Backend.DEX) {
+      builder
+          .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+          .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
+    } else {
+      assert backend == Backend.CF;
+      builder
+          .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+          .addLibraryFiles(ToolHelper.getJava8RuntimeJar());
+    }
     AndroidApp app = ToolHelper.runR8(builder.build(), o -> o.enableDevirtualization = false);
-    inspection.accept(new DexInspector(app));
-    assertEquals(expectedResult, runOnArt(app, mainClass));
+    inspection.accept(new CodeInspector(app, o -> o.enableCfFrontend = true));
+    String result = backend == Backend.DEX ? runOnArt(app, mainClass) : runOnJava(app, mainClass);
+    if (ToolHelper.isWindows()) {
+      result = result.replace(System.lineSeparator(), "\n");
+    }
+    assertEquals(expectedResult, result);
   }
 
-  private int countInstructionInX(DexInspector inspector, Class<? extends Instruction> invoke) {
+  private int countInstructionInX(CodeInspector inspector, Predicate<InstructionSubject> invoke) {
     MethodSignature signatureForX =
         new MethodSignature("x", "void", ImmutableList.of(BaseInterface.class.getCanonicalName()));
-    DexCode x = inspector.clazz(Main.class).method(signatureForX).getMethod().getCode().asDexCode();
-    return (int) filterInstructionKind(x, invoke).count();
+    MethodSubject method = inspector.clazz(Main.class).method(signatureForX);
+    assert method instanceof FoundMethodSubject;
+    FoundMethodSubject foundMethod = (FoundMethodSubject) method;
+    return (int) Streams.stream(foundMethod.iterateInstructions(invoke)).count();
   }
 
-  private int countInstructionInY(DexInspector inspector, Class<? extends Instruction> invoke) {
+  private int countInstructionInY(CodeInspector inspector, Predicate<InstructionSubject> invoke) {
     MethodSignature signatureForY =
         new MethodSignature("y", "void", ImmutableList.of(SubInterface.class.getCanonicalName()));
-    DexCode y = inspector.clazz(Main.class).method(signatureForY).getMethod().getCode().asDexCode();
-    return (int) filterInstructionKind(y, invoke)
-        .filter(instruction -> instruction.getMethod().qualifiedName().endsWith("method"))
-        .count();
+    MethodSubject method = inspector.clazz(Main.class).method(signatureForY);
+    assert method instanceof FoundMethodSubject;
+    FoundMethodSubject foundMethod = (FoundMethodSubject) method;
+    return (int)
+        Streams.stream(foundMethod.iterateInstructions(invoke))
+            .filter(
+                instruction -> {
+                  InvokeInstructionSubject invokeInstruction =
+                      (InvokeInstructionSubject) instruction;
+                  return invokeInstruction.invokedMethod().qualifiedName().endsWith("method");
+                })
+            .count();
   }
 
-  private int countInstructionInZ(DexInspector inspector, Class<? extends Instruction> invoke) {
+  private int countInstructionInZ(CodeInspector inspector, Predicate<InstructionSubject> invoke) {
     MethodSignature signatureForZ =
         new MethodSignature("z", "void", ImmutableList.of(TestClass.class.getCanonicalName()));
-    DexCode z = inspector.clazz(Main.class).method(signatureForZ).getMethod().getCode().asDexCode();
-    return (int) filterInstructionKind(z, invoke)
-        .filter(instruction -> instruction.getMethod().qualifiedName().endsWith("method"))
-        .count();
+    MethodSubject method = inspector.clazz(Main.class).method(signatureForZ);
+    assert method instanceof FoundMethodSubject;
+    FoundMethodSubject foundMethod = (FoundMethodSubject) method;
+    return (int)
+        Streams.stream(foundMethod.iterateInstructions(invoke))
+            .filter(
+                instruction -> {
+                  InvokeInstructionSubject invokeInstruction =
+                      (InvokeInstructionSubject) instruction;
+                  return invokeInstruction.invokedMethod().qualifiedName().endsWith("method");
+                })
+            .count();
   }
 
   private int countInstructionInZSubClass(
-      DexInspector inspector, Class<? extends Instruction> invoke) {
+      CodeInspector inspector, Predicate<InstructionSubject> invoke) {
     MethodSignature signatureForZ =
         new MethodSignature("z", "void", ImmutableList.of(SubClass.class.getCanonicalName()));
-    DexCode z = inspector.clazz(Main.class).method(signatureForZ).getMethod().getCode().asDexCode();
-    return (int) filterInstructionKind(z, invoke)
-        .filter(instruction -> instruction.getMethod().qualifiedName().endsWith("method"))
-        .count();
+    MethodSubject method = inspector.clazz(Main.class).method(signatureForZ);
+    assert method instanceof FoundMethodSubject;
+    FoundMethodSubject foundMethod = (FoundMethodSubject) method;
+    return (int)
+        Streams.stream(foundMethod.iterateInstructions(invoke))
+            .filter(
+                instruction -> {
+                  InvokeInstructionSubject invokeInstruction =
+                      (InvokeInstructionSubject) instruction;
+                  return invokeInstruction.invokedMethod().qualifiedName().endsWith("method");
+                })
+            .count();
   }
 
-  private void noInterfaceKept(DexInspector inspector) {
+  private void noInterfaceKept(CodeInspector inspector) {
     // Indirectly assert that method is inlined into x, y and z.
-    assertEquals(1, countInstructionInX(inspector, InvokeInterface.class));
-    assertEquals(1, countInstructionInY(inspector, InvokeInterface.class));
-    assertEquals(1, countInstructionInZ(inspector, InvokeVirtual.class));
+    assertEquals(1, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
+    assertEquals(1, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
+    assertEquals(1, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
   }
 
   @Test
@@ -104,13 +161,13 @@
         "TestClass 1\nTestClass 1\nTestClass 1\nProxy\nEXCEPTION\n");
   }
 
-  private void baseInterfaceKept(DexInspector inspector) {
+  private void baseInterfaceKept(CodeInspector inspector) {
     // Indirectly assert that method is not inlined into x.
-    assertEquals(3, countInstructionInX(inspector, InvokeInterface.class));
+    assertEquals(3, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
     // Indirectly assert that method is inlined into y and z.
-    assertEquals(1, countInstructionInY(inspector, InvokeInterface.class));
-    assertEquals(1, countInstructionInZ(inspector, InvokeVirtual.class));
-    assertEquals(1, countInstructionInZSubClass(inspector, InvokeVirtual.class));
+    assertEquals(1, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
+    assertEquals(1, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
+    assertEquals(1, countInstructionInZSubClass(inspector, InstructionSubject::isInvokeVirtual));
   }
 
   @Test
@@ -124,13 +181,13 @@
         "TestClass 2\nTestClass 2\nTestClass 2\nProxy\nEXCEPTION\n");
   }
 
-  private void subInterfaceKept(DexInspector inspector) {
+  private void subInterfaceKept(CodeInspector inspector) {
     // Indirectly assert that method is not inlined into x or y.
-    assertEquals(3, countInstructionInX(inspector, InvokeInterface.class));
-    assertEquals(3, countInstructionInY(inspector, InvokeInterface.class));
+    assertEquals(3, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
+    assertEquals(3, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
     // Indirectly assert that method is inlined into z.
-    assertEquals(1, countInstructionInZ(inspector, InvokeVirtual.class));
-    assertEquals(1, countInstructionInZSubClass(inspector, InvokeVirtual.class));
+    assertEquals(1, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
+    assertEquals(1, countInstructionInZSubClass(inspector, InstructionSubject::isInvokeVirtual));
   }
 
   @Test
@@ -146,12 +203,12 @@
         "TestClass 4\nTestClass 4\nTestClass 4\nSUCCESS\n");
   }
 
-  private void classKept(DexInspector inspector) {
+  private void classKept(CodeInspector inspector) {
     // Indirectly assert that method is not inlined into x, y or z.
-    assertEquals(3, countInstructionInX(inspector, InvokeInterface.class));
-    assertEquals(3, countInstructionInY(inspector, InvokeInterface.class));
-    assertEquals(3, countInstructionInZ(inspector, InvokeVirtual.class));
-    assertEquals(3, countInstructionInZSubClass(inspector, InvokeVirtual.class));
+    assertEquals(3, countInstructionInX(inspector, InstructionSubject::isInvokeInterface));
+    assertEquals(3, countInstructionInY(inspector, InstructionSubject::isInvokeInterface));
+    assertEquals(3, countInstructionInZ(inspector, InstructionSubject::isInvokeVirtual));
+    assertEquals(3, countInstructionInZSubClass(inspector, InstructionSubject::isInvokeVirtual));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
index e4be101..d12f41a 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
@@ -7,34 +7,66 @@
 // BSD-style license that can be found in the LICENSE file.
 
 package com.android.tools.r8.shaking.testrules;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static org.junit.Assert.assertThat;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
 
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramConsumer;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
+@RunWith(Parameterized.class)
 public class ForceInlineTest extends TestBase {
+  private Backend backend;
 
-  private DexInspector runTest(List<String> proguardConfiguration) throws Exception {
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public ForceInlineTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  private CodeInspector runTest(List<String> proguardConfiguration) throws Exception {
+    ProgramConsumer programConsumer;
+    Path library;
+    if (backend == Backend.DEX) {
+      programConsumer = DexIndexedConsumer.emptyConsumer();
+      library = ToolHelper.getDefaultAndroidJar();
+    } else {
+      assert backend == Backend.CF;
+      programConsumer = ClassFileConsumer.emptyConsumer();
+      library = ToolHelper.getJava8RuntimeJar();
+    }
     R8Command.Builder builder =
-        ToolHelper.prepareR8CommandBuilder(readClasses(Main.class, A.class, B.class, C.class));
+        ToolHelper.prepareR8CommandBuilder(
+                readClasses(Main.class, A.class, B.class, C.class), programConsumer)
+            .addLibraryFiles(library);
     ToolHelper.allowTestProguardOptions(builder);
     builder.addProguardConfiguration(proguardConfiguration, Origin.unknown());
-    return new DexInspector(ToolHelper.runR8(builder.build()));
+    return new CodeInspector(ToolHelper.runR8(builder.build(), o -> o.enableCfFrontend = true));
   }
 
   @Test
   public void testDefaultInlining() throws Exception {
-    DexInspector inspector = runTest(ImmutableList.of(
+    CodeInspector inspector = runTest(ImmutableList.of(
         "-keep class **.Main { *; }",
         "-dontobfuscate"
     ));
@@ -60,7 +92,7 @@
 
   @Test
   public void testNeverInline() throws Exception {
-    DexInspector inspector = runTest(ImmutableList.of(
+    CodeInspector inspector = runTest(ImmutableList.of(
         "-neverinline class **.A { method(); }",
         "-neverinline class **.B { method(); }",
         "-keep class **.Main { *; }",
@@ -85,7 +117,7 @@
 
   @Test
   public void testForceInline() throws Exception {
-    DexInspector inspector = runTest(ImmutableList.of(
+    CodeInspector inspector = runTest(ImmutableList.of(
         "-forceinline class **.A { int m(int, int); }",
         "-forceinline class **.B { int m(int, int); }",
         "-keep class **.Main { *; }",
@@ -107,7 +139,7 @@
   @Test
   public void testForceInlineFails() throws Exception {
     try {
-      DexInspector inspector = runTest(ImmutableList.of(
+      CodeInspector inspector = runTest(ImmutableList.of(
           "-forceinline class **.A { int x(); }",
           "-keep class **.Main { *; }",
           "-dontobfuscate"
diff --git a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
index 6b90d88..00d385f 100644
--- a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
+++ b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Phi;
@@ -68,7 +69,9 @@
 
     DexEncodedMethod method = getMethod(originalApplication, methodSig);
     // Get the IR pre-optimization.
-    IRCode code = method.buildIR(null, new InternalOptions(), Origin.unknown());
+    IRCode code =
+        method.buildIR(
+            null, GraphLense.getIdentityLense(), new InternalOptions(), Origin.unknown());
 
     // Find the exit block and assert that the value is a phi merging the exceptional edge
     // with the normal edge.
diff --git a/src/test/java/com/android/tools/r8/smali/ConstantFoldingTest.java b/src/test/java/com/android/tools/r8/smali/ConstantFoldingTest.java
index 3381473..ce1fd33 100644
--- a/src/test/java/com/android/tools/r8/smali/ConstantFoldingTest.java
+++ b/src/test/java/com/android/tools/r8/smali/ConstantFoldingTest.java
@@ -20,8 +20,8 @@
 import com.android.tools.r8.ir.code.SingleConstant;
 import com.android.tools.r8.ir.code.WideConstant;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -54,7 +54,7 @@
     public void run() throws Exception {
       AndroidApp processdApplication = processApplication(buildApplication(builder));
       assertEquals(1, getNumberOfProgramClasses(processdApplication));
-      DexInspector inspector = new DexInspector(processdApplication);
+      CodeInspector inspector = new CodeInspector(processdApplication);
       ClassSubject clazz = inspector.clazz(DEFAULT_CLASS_NAME);
       clazz.forAllMethods(method -> {
         int index = Integer.parseInt(method.getMethod().method.name.toString().substring(1));
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 371c4d1..1479add 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -32,11 +32,11 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -59,7 +59,7 @@
   }
 
   DexEncodedMethod getInvokedMethod(DexApplication application, InvokeStatic invoke) {
-    DexInspector inspector = new DexInspector(application);
+    CodeInspector inspector = new CodeInspector(application);
     ClassSubject clazz = inspector.clazz(invoke.getMethod().holder.toSourceString());
     assertTrue(clazz.isPresent());
     DexMethod invokedMethod = invoke.getMethod();
@@ -843,7 +843,7 @@
     assertEquals(2, getNumberOfProgramClasses(processedApplication));
 
     // Check that three outlining methods was created.
-    DexInspector inspector = new DexInspector(processedApplication);
+    CodeInspector inspector = new CodeInspector(processedApplication);
     ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
     assertTrue(clazz.isPresent());
     assertEquals(3, clazz.getDexClass().directMethods().length);
diff --git a/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
index 86c4242..0261af6 100644
--- a/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
@@ -11,8 +11,8 @@
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import org.junit.Test;
@@ -74,7 +74,7 @@
             keepMainProguardConfiguration("Test"),
             options -> options.enableInlining = false);
 
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     MethodSubject method = inspector.clazz("Test").method("void", "test", ImmutableList.of());
     DexCode code = method.getMethod().getCode().asDexCode();
     assertTrue(code.isEmptyVoidMethod());
@@ -142,7 +142,7 @@
             keepMainProguardConfiguration("Test"),
             options -> options.enableInlining = false);
 
-    DexInspector inspector = new DexInspector(app);
+    CodeInspector inspector = new CodeInspector(app);
     MethodSubject method = inspector.clazz("Test").method("void", "test", ImmutableList.of());
     DexCode code = method.getMethod().getCode().asDexCode();
     assertTrue(code.isEmptyVoidMethod());
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
index b6db233..8d780b4 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
@@ -9,8 +9,8 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.origin.EmbeddedOrigin;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
 import org.junit.Test;
@@ -19,7 +19,7 @@
 
   private void checkJavaLangString(AndroidApp application, boolean present) {
     try {
-      DexInspector inspector = new DexInspector(application);
+      CodeInspector inspector = new CodeInspector(application);
       ClassSubject clazz = inspector.clazz("java.lang.String");
       assertEquals(present, clazz.isPresent());
     } catch (IOException | ExecutionException e) {
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 6c4b884..b12c0b9 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -23,10 +23,10 @@
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Collection;
@@ -112,7 +112,7 @@
   }
 
   protected DexClass getClass(DexApplication application, String className) {
-    DexInspector inspector = new DexInspector(application);
+    CodeInspector inspector = new CodeInspector(application);
     ClassSubject clazz = inspector.clazz(className);
     assertTrue(clazz.isPresent());
     return clazz.getDexClass();
@@ -124,7 +124,7 @@
 
   protected DexClass getClass(Path appPath, String className) {
     try {
-      DexInspector inspector = new DexInspector(appPath);
+      CodeInspector inspector = new CodeInspector(appPath);
       ClassSubject clazz = inspector.clazz(className);
       assertTrue(clazz.isPresent());
       return clazz.getDexClass();
@@ -135,14 +135,14 @@
 
   protected DexEncodedMethod getMethod(Path appPath, MethodSignature signature) {
     try {
-      DexInspector inspector = new DexInspector(appPath);
+      CodeInspector inspector = new CodeInspector(appPath);
       return getMethod(inspector, signature);
     } catch (IOException | ExecutionException e) {
       throw new RuntimeException(e);
     }
   }
 
-  protected DexEncodedMethod getMethod(DexInspector inspector, MethodSignature signature) {
+  protected DexEncodedMethod getMethod(CodeInspector inspector, MethodSignature signature) {
     return getMethod(
         inspector, signature.clazz, signature.returnType, signature.name, signature.parameterTypes);
   }
diff --git a/src/test/java/com/android/tools/r8/utils/ArtErrorParser.java b/src/test/java/com/android/tools/r8/utils/ArtErrorParser.java
index 406c63f..5f00fc9 100644
--- a/src/test/java/com/android/tools/r8/utils/ArtErrorParser.java
+++ b/src/test/java/com/android/tools/r8/utils/ArtErrorParser.java
@@ -4,8 +4,9 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -71,7 +72,7 @@
   public static abstract class ArtErrorInfo {
     public abstract int consumedLines();
     public abstract String getMessage();
-    public abstract String dump(DexInspector inspector, boolean markLocation);
+    public abstract String dump(CodeInspector inspector, boolean markLocation);
   }
 
   private static class ArtMethodError extends ArtErrorInfo {
@@ -116,7 +117,7 @@
     }
 
     @Override
-    public String dump(DexInspector inspector, boolean markLocation) {
+    public String dump(CodeInspector inspector, boolean markLocation) {
       ClassSubject clazz = inspector.clazz(className);
       MethodSubject method = clazz.method(methodReturn, methodName, methodFormals);
       DexEncodedMethod dex = method.getMethod();
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
deleted file mode 100644
index eba3be8..0000000
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ /dev/null
@@ -1,1664 +0,0 @@
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.utils;
-
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.StringResource;
-import com.android.tools.r8.code.Const4;
-import com.android.tools.r8.code.ConstString;
-import com.android.tools.r8.code.Goto;
-import com.android.tools.r8.code.IfEqz;
-import com.android.tools.r8.code.IfNez;
-import com.android.tools.r8.code.Iget;
-import com.android.tools.r8.code.IgetBoolean;
-import com.android.tools.r8.code.IgetByte;
-import com.android.tools.r8.code.IgetChar;
-import com.android.tools.r8.code.IgetObject;
-import com.android.tools.r8.code.IgetShort;
-import com.android.tools.r8.code.IgetWide;
-import com.android.tools.r8.code.Instruction;
-import com.android.tools.r8.code.InvokeDirect;
-import com.android.tools.r8.code.InvokeDirectRange;
-import com.android.tools.r8.code.InvokeInterface;
-import com.android.tools.r8.code.InvokeInterfaceRange;
-import com.android.tools.r8.code.InvokeStatic;
-import com.android.tools.r8.code.InvokeStaticRange;
-import com.android.tools.r8.code.InvokeSuper;
-import com.android.tools.r8.code.InvokeSuperRange;
-import com.android.tools.r8.code.InvokeVirtual;
-import com.android.tools.r8.code.InvokeVirtualRange;
-import com.android.tools.r8.code.Iput;
-import com.android.tools.r8.code.IputBoolean;
-import com.android.tools.r8.code.IputByte;
-import com.android.tools.r8.code.IputChar;
-import com.android.tools.r8.code.IputObject;
-import com.android.tools.r8.code.IputShort;
-import com.android.tools.r8.code.IputWide;
-import com.android.tools.r8.code.Nop;
-import com.android.tools.r8.code.ReturnVoid;
-import com.android.tools.r8.code.Sget;
-import com.android.tools.r8.code.SgetBoolean;
-import com.android.tools.r8.code.SgetByte;
-import com.android.tools.r8.code.SgetChar;
-import com.android.tools.r8.code.SgetObject;
-import com.android.tools.r8.code.SgetShort;
-import com.android.tools.r8.code.SgetWide;
-import com.android.tools.r8.code.Sput;
-import com.android.tools.r8.code.SputBoolean;
-import com.android.tools.r8.code.SputByte;
-import com.android.tools.r8.code.SputChar;
-import com.android.tools.r8.code.SputObject;
-import com.android.tools.r8.code.SputShort;
-import com.android.tools.r8.code.SputWide;
-import com.android.tools.r8.code.Throw;
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexAnnotationElement;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexCode;
-import com.android.tools.r8.graph.DexEncodedAnnotation;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.DexValue.DexValueArray;
-import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.naming.ClassNamingForNameMapper;
-import com.android.tools.r8.naming.MemberNaming;
-import com.android.tools.r8.naming.MemberNaming.FieldSignature;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.naming.MemberNaming.Signature;
-import com.android.tools.r8.naming.signature.GenericSignatureAction;
-import com.android.tools.r8.naming.signature.GenericSignatureParser;
-import com.android.tools.r8.smali.SmaliBuilder;
-import com.google.common.collect.BiMap;
-import com.google.common.collect.ImmutableList;
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.concurrent.ExecutionException;
-import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.function.Predicate;
-
-public class DexInspector {
-
-  private final DexApplication application;
-  private final DexItemFactory dexItemFactory;
-  private final ClassNameMapper mapping;
-  private final BiMap<String, String> originalToObfuscatedMapping;
-
-  private final InstructionSubjectFactory factory = new InstructionSubjectFactory();
-
-  public static MethodSignature MAIN =
-      new MethodSignature("main", "void", new String[]{"java.lang.String[]"});
-
-  public DexInspector(Path file, String mappingFile) throws IOException, ExecutionException {
-    this(Collections.singletonList(file), mappingFile);
-  }
-
-  public DexInspector(Path file) throws IOException, ExecutionException {
-    this(Collections.singletonList(file), null);
-  }
-
-  public DexInspector(List<Path> files) throws IOException, ExecutionException {
-    this(files, null);
-  }
-
-  public DexInspector(List<Path> files, String mappingFile)
-      throws IOException, ExecutionException {
-    if (mappingFile != null) {
-      this.mapping = ClassNameMapper.mapperFromFile(Paths.get(mappingFile));
-      originalToObfuscatedMapping = this.mapping.getObfuscatedToOriginalMapping().inverse();
-    } else {
-      this.mapping = null;
-      originalToObfuscatedMapping = null;
-    }
-    Timing timing = new Timing("DexInspector");
-    InternalOptions options = new InternalOptions();
-    dexItemFactory = options.itemFactory;
-    AndroidApp input = AndroidApp.builder().addProgramFiles(files).build();
-    application = new ApplicationReader(input, options, timing).read();
-  }
-
-  public DexInspector(AndroidApp app) throws IOException, ExecutionException {
-    this(
-        new ApplicationReader(app, new InternalOptions(), new Timing("DexInspector"))
-            .read(app.getProguardMapOutputData()));
-  }
-
-  public DexInspector(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
-      throws IOException, ExecutionException {
-    this(
-        new ApplicationReader(app, runOptionsConsumer(optionsConsumer), new Timing("DexInspector"))
-            .read(app.getProguardMapOutputData()));
-  }
-
-  private static InternalOptions runOptionsConsumer(Consumer<InternalOptions> optionsConsumer) {
-    InternalOptions internalOptions = new InternalOptions();
-    optionsConsumer.accept(internalOptions);
-    return internalOptions;
-  }
-
-  public DexInspector(AndroidApp app, Path proguardMap) throws IOException, ExecutionException {
-    this(
-        new ApplicationReader(app, new InternalOptions(), new Timing("DexInspector"))
-            .read(StringResource.fromFile(proguardMap)));
-  }
-
-  public DexInspector(DexApplication application) {
-    dexItemFactory = application.dexItemFactory;
-    this.application = application;
-    this.mapping = application.getProguardMap();
-    originalToObfuscatedMapping =
-        mapping == null ? null : mapping.getObfuscatedToOriginalMapping().inverse();
-  }
-
-  public DexItemFactory getFactory() {
-    return dexItemFactory;
-  }
-
-  private DexType toDexType(String string) {
-    return dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(string));
-  }
-
-  private DexType toDexTypeIgnorePrimitives(String string) {
-    return dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptorIgnorePrimitives(string));
-  }
-
-  private static <S, T extends Subject> void forAll(S[] items,
-      BiFunction<S, FoundClassSubject, ? extends T> constructor,
-      FoundClassSubject clazz,
-      Consumer<T> consumer) {
-    for (S item : items) {
-      consumer.accept(constructor.apply(item, clazz));
-    }
-  }
-
-  private static <S, T extends Subject> void forAll(Iterable<S> items, Function<S, T> constructor,
-      Consumer<T> consumer) {
-    for (S item : items) {
-      consumer.accept(constructor.apply(item));
-    }
-  }
-
-  DexAnnotation findAnnotation(String name, DexAnnotationSet annotations) {
-    for (DexAnnotation annotation : annotations.annotations) {
-      DexType type = annotation.annotation.type;
-      String original = mapping == null ? type.toSourceString() : mapping.originalNameOf(type);
-      if (original.equals(name)) {
-        return annotation;
-      }
-    }
-    return null;
-  }
-
-  public String getFinalSignatureAttribute(DexAnnotationSet annotations) {
-    DexAnnotation annotation =
-        findAnnotation("dalvik.annotation.Signature", annotations);
-    if (annotation == null) {
-      return null;
-    }
-    assert annotation.annotation.elements.length == 1;
-    DexAnnotationElement element = annotation.annotation.elements[0];
-    assert element.value instanceof DexValueArray;
-    StringBuilder builder = new StringBuilder();
-    DexValueArray valueArray = (DexValueArray) element.value;
-    for (DexValue value : valueArray.getValues()) {
-      assertTrue(value instanceof DexValueString);
-      DexValueString s = (DexValueString) value;
-      builder.append(s.getValue());
-    }
-    return builder.toString();
-  }
-
-  public String getOriginalSignatureAttribute(
-      DexAnnotationSet annotations, BiConsumer<GenericSignatureParser, String> parse) {
-    String finalSignature = getFinalSignatureAttribute(annotations);
-    if (finalSignature == null || mapping == null) {
-      return finalSignature;
-    }
-
-    GenericSignatureGenerater rewriter = new GenericSignatureGenerater();
-    GenericSignatureParser<String> parser = new GenericSignatureParser<>(rewriter);
-    parse.accept(parser, finalSignature);
-    return rewriter.getSignature();
-  }
-
-
-  public ClassSubject clazz(Class clazz) {
-    return clazz(clazz.getTypeName());
-  }
-
-  /**
-   * Lookup a class by name. This allows both original and obfuscated names.
-   */
-  public ClassSubject clazz(String name) {
-    ClassNamingForNameMapper naming = null;
-    if (mapping != null) {
-      String obfuscated = originalToObfuscatedMapping.get(name);
-      if (obfuscated != null) {
-        naming = mapping.getClassNaming(obfuscated);
-        name = obfuscated;
-      } else {
-        // Figure out if the name is an already obfuscated name.
-        String original = originalToObfuscatedMapping.inverse().get(name);
-        if (original != null) {
-          naming = mapping.getClassNaming(name);
-        }
-      }
-    }
-    DexClass clazz = application.definitionFor(toDexTypeIgnorePrimitives(name));
-    if (clazz == null) {
-      return new AbsentClassSubject();
-    }
-    return new FoundClassSubject(clazz, naming);
-  }
-
-  public void forAllClasses(Consumer<FoundClassSubject> inspection) {
-    forAll(application.classes(), cls -> {
-      ClassSubject subject = clazz(cls.type.toSourceString());
-      assert subject.isPresent();
-      return (FoundClassSubject) subject;
-    }, inspection);
-  }
-
-  public List<FoundClassSubject> allClasses() {
-    ImmutableList.Builder<FoundClassSubject> builder = ImmutableList.builder();
-    forAllClasses(builder::add);
-    return builder.build();
-  }
-
-  public MethodSubject method(Method method) {
-    ClassSubject clazz = clazz(method.getDeclaringClass());
-    if (!clazz.isPresent()) {
-      return new AbsentMethodSubject();
-    }
-    return clazz.method(method);
-  }
-
-  private String getObfuscatedTypeName(String originalTypeName) {
-    String obfuscatedType = null;
-    if (mapping != null) {
-      obfuscatedType = originalToObfuscatedMapping.get(originalTypeName);
-    }
-    obfuscatedType = obfuscatedType == null ? originalTypeName : obfuscatedType;
-    return obfuscatedType;
-  }
-
-  public abstract class Subject {
-
-    public abstract boolean isPresent();
-    public abstract boolean isRenamed();
-  }
-
-  public abstract class AnnotationSubject extends Subject {
-
-    public abstract DexEncodedAnnotation getAnnotation();
-  }
-
-  public class FoundAnnotationSubject extends AnnotationSubject {
-
-    private final DexAnnotation annotation;
-
-    private FoundAnnotationSubject(DexAnnotation annotation) {
-      this.annotation = annotation;
-    }
-
-    @Override
-    public boolean isPresent() {
-      return true;
-    }
-
-    @Override
-    public boolean isRenamed() {
-      return false;
-    }
-
-    @Override
-    public DexEncodedAnnotation getAnnotation() {
-      return annotation.annotation;
-    }
-  }
-
-  public class AbsentAnnotationSubject extends AnnotationSubject {
-
-    @Override
-    public boolean isPresent() {
-      return false;
-    }
-
-    @Override
-    public boolean isRenamed() {
-      return false;
-    }
-
-    @Override
-    public DexEncodedAnnotation getAnnotation() {
-      throw new UnsupportedOperationException();
-    }
-  }
-
-
-  public abstract class ClassSubject extends Subject {
-
-    public abstract void forAllMethods(Consumer<FoundMethodSubject> inspection);
-
-    public MethodSubject method(Method method) {
-      List<String> parameters = new ArrayList<>();
-      for (Class<?> parameterType : method.getParameterTypes()) {
-        parameters.add(parameterType.getTypeName());
-      }
-      return method(method.getReturnType().getTypeName(), method.getName(), parameters);
-    }
-
-    public abstract MethodSubject method(String returnType, String name, List<String> parameters);
-
-    public MethodSubject clinit() {
-      return method("void", "<clinit>", ImmutableList.of());
-    }
-
-    public MethodSubject init(List<String> parameters) {
-      return method("void", "<init>", parameters);
-    }
-
-    public MethodSubject method(MethodSignature signature) {
-      return method(signature.type, signature.name, ImmutableList.copyOf(signature.parameters));
-    }
-
-    public MethodSubject method(SmaliBuilder.MethodSignature signature) {
-      return method(
-          signature.returnType, signature.name, ImmutableList.copyOf(signature.parameterTypes));
-    }
-
-    public abstract void forAllFields(Consumer<FoundFieldSubject> inspection);
-
-    public abstract FieldSubject field(String type, String name);
-
-    public abstract boolean isAbstract();
-
-    public abstract boolean isAnnotation();
-
-    public String dumpMethods() {
-      StringBuilder dump = new StringBuilder();
-      forAllMethods((FoundMethodSubject method) ->
-          dump.append(method.getMethod().toString())
-              .append(method.getMethod().codeToString()));
-      return dump.toString();
-    }
-
-    public abstract DexClass getDexClass();
-
-    public abstract AnnotationSubject annotation(String name);
-
-    public abstract String getOriginalName();
-
-    public abstract String getOriginalDescriptor();
-
-    public abstract String getFinalName();
-
-    public abstract String getFinalDescriptor();
-
-    public abstract boolean isMemberClass();
-
-    public abstract boolean isLocalClass();
-
-    public abstract boolean isAnonymousClass();
-
-    public abstract String getOriginalSignatureAttribute();
-
-    public abstract String getFinalSignatureAttribute();
-  }
-
-  private class AbsentClassSubject extends ClassSubject {
-
-    @Override
-    public boolean isPresent() {
-      return false;
-    }
-
-    @Override
-    public void forAllMethods(Consumer<FoundMethodSubject> inspection) {
-    }
-
-    @Override
-    public MethodSubject method(String returnType, String name, List<String> parameters) {
-      return new AbsentMethodSubject();
-    }
-
-    @Override
-    public void forAllFields(Consumer<FoundFieldSubject> inspection) {
-    }
-
-    @Override
-    public FieldSubject field(String type, String name) {
-      return new AbsentFieldSubject();
-    }
-
-    @Override
-    public boolean isAbstract() {
-      return false;
-    }
-
-    @Override
-    public boolean isAnnotation() {
-      return false;
-    }
-
-    @Override
-    public DexClass getDexClass() {
-      return null;
-    }
-
-    @Override
-    public AnnotationSubject annotation(String name) {
-      return new AbsentAnnotationSubject();
-    }
-
-    @Override
-    public String getOriginalName() {
-      return null;
-    }
-
-    @Override
-    public String getOriginalDescriptor() {
-      return null;
-    }
-
-    @Override
-    public String getFinalName() {
-      return null;
-    }
-
-    @Override
-    public String getFinalDescriptor() {
-      return null;
-    }
-
-    @Override
-    public boolean isRenamed() {
-      return false;
-    }
-
-    @Override
-    public boolean isMemberClass() {
-      return false;
-    }
-
-    @Override
-    public boolean isLocalClass() {
-      return false;
-    }
-
-    @Override
-    public boolean isAnonymousClass() {
-      return false;
-    }
-
-    @Override
-    public String getOriginalSignatureAttribute() {
-      return null;
-    }
-
-    @Override
-    public String getFinalSignatureAttribute() {
-      return null;
-    }
-  }
-
-  public class FoundClassSubject extends ClassSubject {
-
-    private final DexClass dexClass;
-    private final ClassNamingForNameMapper naming;
-
-    private FoundClassSubject(DexClass dexClass, ClassNamingForNameMapper naming) {
-      this.dexClass = dexClass;
-      this.naming = naming;
-    }
-
-    @Override
-    public boolean isPresent() {
-      return true;
-    }
-
-    @Override
-    public void forAllMethods(Consumer<FoundMethodSubject> inspection) {
-      forAll(dexClass.directMethods(), FoundMethodSubject::new, this, inspection);
-      forAll(dexClass.virtualMethods(), FoundMethodSubject::new, this, inspection);
-    }
-
-    @Override
-    public MethodSubject method(String returnType, String name, List<String> parameters) {
-      DexType[] parameterTypes = new DexType[parameters.size()];
-      for (int i = 0; i < parameters.size(); i++) {
-        parameterTypes[i] = toDexType(getObfuscatedTypeName(parameters.get(i)));
-      }
-      DexProto proto = dexItemFactory.createProto(toDexType(getObfuscatedTypeName(returnType)),
-          parameterTypes);
-      if (naming != null) {
-        String[] parameterStrings = new String[parameterTypes.length];
-        Signature signature = new MethodSignature(name, returnType,
-            parameters.toArray(parameterStrings));
-        MemberNaming methodNaming = naming.lookupByOriginalSignature(signature);
-        if (methodNaming != null) {
-          name = methodNaming.getRenamedName();
-        }
-      }
-      DexMethod dexMethod =
-          dexItemFactory.createMethod(dexClass.type, proto, dexItemFactory.createString(name));
-      DexEncodedMethod encoded = findMethod(dexClass.directMethods(), dexMethod);
-      if (encoded == null) {
-        encoded = findMethod(dexClass.virtualMethods(), dexMethod);
-      }
-      return encoded == null ? new AbsentMethodSubject() : new FoundMethodSubject(encoded, this);
-    }
-
-    private DexEncodedMethod findMethod(DexEncodedMethod[] methods, DexMethod dexMethod) {
-      for (DexEncodedMethod method : methods) {
-        if (method.method.equals(dexMethod)) {
-          return method;
-        }
-      }
-      return null;
-    }
-
-    @Override
-    public void forAllFields(Consumer<FoundFieldSubject> inspection) {
-      forAll(dexClass.staticFields(), FoundFieldSubject::new, this, inspection);
-      forAll(dexClass.instanceFields(), FoundFieldSubject::new, this, inspection);
-    }
-
-    @Override
-    public FieldSubject field(String type, String name) {
-      String obfuscatedType = getObfuscatedTypeName(type);
-      MemberNaming fieldNaming = null;
-      if (naming != null) {
-        fieldNaming = naming.lookupByOriginalSignature(
-            new FieldSignature(name, type));
-      }
-      String obfuscatedName = fieldNaming == null ? name : fieldNaming.getRenamedName();
-
-      DexField field = dexItemFactory.createField(dexClass.type,
-          toDexType(obfuscatedType), dexItemFactory.createString(obfuscatedName));
-      DexEncodedField encoded = findField(dexClass.staticFields(), field);
-      if (encoded == null) {
-        encoded = findField(dexClass.instanceFields(), field);
-      }
-      return encoded == null ? new AbsentFieldSubject() : new FoundFieldSubject(encoded, this);
-    }
-
-    @Override
-    public boolean isAbstract() {
-      return dexClass.accessFlags.isAbstract();
-    }
-
-    @Override
-    public boolean isAnnotation() {
-      return dexClass.accessFlags.isAnnotation();
-    }
-
-    private DexEncodedField findField(DexEncodedField[] fields, DexField dexField) {
-      for (DexEncodedField field : fields) {
-        if (field.field.equals(dexField)) {
-          return field;
-        }
-      }
-      return null;
-    }
-
-    @Override
-    public DexClass getDexClass() {
-      return dexClass;
-    }
-
-    @Override
-    public AnnotationSubject annotation(String name) {
-      // Ensure we don't check for annotations represented as attributes.
-      assert !name.endsWith("EnclosingClass")
-          && !name.endsWith("EnclosingMethod")
-          && !name.endsWith("InnerClass");
-      DexAnnotation annotation = findAnnotation(name, dexClass.annotations);
-      return annotation == null
-          ? new AbsentAnnotationSubject()
-          : new FoundAnnotationSubject(annotation);
-    }
-
-    @Override
-    public String getOriginalName() {
-      if (naming != null) {
-        return naming.originalName;
-      } else {
-        return getFinalName();
-      }
-    }
-
-    @Override
-    public String getOriginalDescriptor() {
-      if (naming != null) {
-        return DescriptorUtils.javaTypeToDescriptor(naming.originalName);
-      } else {
-        return getFinalDescriptor();
-      }
-    }
-
-    @Override
-    public String getFinalName() {
-      return DescriptorUtils.descriptorToJavaType(getFinalDescriptor());
-    }
-
-    @Override
-    public String getFinalDescriptor() {
-      return dexClass.type.descriptor.toString();
-    }
-
-    @Override
-    public boolean isRenamed() {
-      return naming != null && !getFinalDescriptor().equals(getOriginalDescriptor());
-    }
-
-    private InnerClassAttribute getInnerClassAttribute() {
-      for (InnerClassAttribute innerClassAttribute : dexClass.getInnerClasses()) {
-        if (dexClass.type == innerClassAttribute.getInner()) {
-          return innerClassAttribute;
-        }
-      }
-      return null;
-    }
-
-    @Override
-    public boolean isLocalClass() {
-      InnerClassAttribute innerClass = getInnerClassAttribute();
-      return innerClass != null
-          && innerClass.isNamed()
-          && dexClass.getEnclosingMethod() != null;
-    }
-
-    @Override
-    public boolean isMemberClass() {
-      InnerClassAttribute innerClass = getInnerClassAttribute();
-      return innerClass != null
-          && innerClass.getOuter() != null
-          && innerClass.isNamed()
-          && dexClass.getEnclosingMethod() == null;
-    }
-
-    @Override
-    public boolean isAnonymousClass() {
-      InnerClassAttribute innerClass = getInnerClassAttribute();
-      return innerClass != null
-          && innerClass.isAnonymous()
-          && dexClass.getEnclosingMethod() != null;
-    }
-
-    @Override
-    public String getOriginalSignatureAttribute() {
-      return DexInspector.this.getOriginalSignatureAttribute(
-          dexClass.annotations, GenericSignatureParser::parseClassSignature);
-    }
-
-    @Override
-    public String getFinalSignatureAttribute() {
-      return DexInspector.this.getFinalSignatureAttribute(dexClass.annotations);
-    }
-
-    @Override
-    public String toString() {
-      return dexClass.toSourceString();
-    }
-  }
-
-  public abstract class MemberSubject extends Subject {
-
-    public abstract boolean isPublic();
-
-    public abstract boolean isStatic();
-
-    public abstract boolean isFinal();
-
-    public abstract Signature getOriginalSignature();
-
-    public abstract Signature getFinalSignature();
-
-    public String getOriginalName() {
-      Signature originalSignature = getOriginalSignature();
-      return originalSignature == null ? null : originalSignature.name;
-    }
-
-    public String getFinalName() {
-      Signature finalSignature = getFinalSignature();
-      return finalSignature == null ? null : finalSignature.name;
-    }
-  }
-
-  public abstract class MethodSubject extends MemberSubject {
-
-    public abstract boolean isAbstract();
-
-    public abstract boolean isBridge();
-
-    public abstract boolean isInstanceInitializer();
-
-    public abstract boolean isClassInitializer();
-
-    public abstract String getOriginalSignatureAttribute();
-
-    public abstract String getFinalSignatureAttribute();
-
-    public abstract DexEncodedMethod getMethod();
-
-    public Iterator<InstructionSubject> iterateInstructions() {
-      return null;
-    }
-
-    public <T extends InstructionSubject> Iterator<T> iterateInstructions(
-        Predicate<InstructionSubject> filter) {
-      return null;
-    }
-  }
-
-  public class AbsentMethodSubject extends MethodSubject {
-
-    @Override
-    public boolean isPresent() {
-      return false;
-    }
-
-    @Override
-    public boolean isRenamed() {
-      return false;
-    }
-
-    @Override
-    public boolean isPublic() {
-      return false;
-    }
-
-    @Override
-    public boolean isStatic() {
-      return false;
-    }
-
-    @Override
-    public boolean isFinal() {
-      return false;
-    }
-
-    @Override
-    public boolean isAbstract() {
-      return false;
-    }
-
-    @Override
-    public boolean isBridge() {
-      return false;
-    }
-
-    @Override
-    public boolean isInstanceInitializer() {
-      return false;
-    }
-
-    @Override
-    public boolean isClassInitializer() {
-      return false;
-    }
-
-    @Override
-    public DexEncodedMethod getMethod() {
-      return null;
-    }
-
-    @Override
-    public Signature getOriginalSignature() {
-      return null;
-    }
-
-    @Override
-    public Signature getFinalSignature() {
-      return null;
-    }
-
-    @Override
-    public String getOriginalSignatureAttribute() {
-      return null;
-    }
-
-    @Override
-    public String getFinalSignatureAttribute() {
-      return null;
-    }
-  }
-
-  public class FoundMethodSubject extends MethodSubject {
-
-    private final FoundClassSubject clazz;
-    private final DexEncodedMethod dexMethod;
-
-    public FoundMethodSubject(DexEncodedMethod encoded, FoundClassSubject clazz) {
-      this.clazz = clazz;
-      this.dexMethod = encoded;
-    }
-
-    @Override
-    public boolean isPresent() {
-      return true;
-    }
-
-    @Override
-    public boolean isRenamed() {
-      return clazz.naming != null && !getFinalSignature().name.equals(getOriginalSignature().name);
-    }
-
-    @Override
-    public boolean isPublic() {
-      return dexMethod.accessFlags.isPublic();
-    }
-
-    @Override
-    public boolean isStatic() {
-      return dexMethod.accessFlags.isStatic();
-    }
-
-    @Override
-    public boolean isFinal() {
-      return dexMethod.accessFlags.isFinal();
-    }
-
-    @Override
-    public boolean isAbstract() {
-      return dexMethod.accessFlags.isAbstract();
-    }
-
-    @Override
-    public boolean isBridge() {
-      return dexMethod.accessFlags.isBridge();
-    }
-
-    @Override
-    public boolean isInstanceInitializer() {
-      return dexMethod.isInstanceInitializer();
-    }
-
-    @Override
-    public boolean isClassInitializer() {
-      return dexMethod.isClassInitializer();
-    }
-
-    @Override
-    public DexEncodedMethod getMethod() {
-      return dexMethod;
-    }
-
-    @Override
-    public MethodSignature getOriginalSignature() {
-      MethodSignature signature = getFinalSignature();
-      if (clazz.naming == null) {
-        return signature;
-      }
-
-      // Map the parameters and return type to original names. This is needed as the in the
-      // Proguard map the names on the left side are the original names. E.g.
-      //
-      //   X -> a
-      //     X method(X) -> a
-      //
-      // whereas the final signature is for X.a is "a (a)"
-      String[] OriginalParameters = new String[signature.parameters.length];
-      for (int i = 0; i < OriginalParameters.length; i++) {
-        String obfuscated = signature.parameters[i];
-        String original = originalToObfuscatedMapping.inverse().get(obfuscated);
-        OriginalParameters[i] = original != null ? original : obfuscated;
-      }
-      String obfuscatedReturnType = signature.type;
-      String originalReturnType = originalToObfuscatedMapping.inverse().get(obfuscatedReturnType);
-      String returnType = originalReturnType != null ? originalReturnType : obfuscatedReturnType;
-
-      MethodSignature lookupSignature =
-          new MethodSignature(signature.name, returnType, OriginalParameters);
-
-      MemberNaming memberNaming = clazz.naming.lookup(lookupSignature);
-      return memberNaming != null
-          ? (MethodSignature) memberNaming.getOriginalSignature()
-          : signature;
-    }
-
-    @Override
-    public MethodSignature getFinalSignature() {
-      return MemberNaming.MethodSignature.fromDexMethod(dexMethod.method);
-    }
-
-    @Override
-    public String getOriginalSignatureAttribute() {
-      return DexInspector.this.getOriginalSignatureAttribute(
-          dexMethod.annotations, GenericSignatureParser::parseMethodSignature);
-    }
-
-    @Override
-    public String getFinalSignatureAttribute() {
-      return DexInspector.this.getFinalSignatureAttribute(dexMethod.annotations);
-    }
-
-    @Override
-    public Iterator<InstructionSubject> iterateInstructions() {
-      return new InstructionIterator(this);
-    }
-
-    @Override
-    public <T extends InstructionSubject> Iterator<T> iterateInstructions(
-        Predicate<InstructionSubject> filter) {
-      return new FilteredInstructionIterator<>(this, filter);
-    }
-
-    @Override
-    public String toString() {
-      return dexMethod.toSourceString();
-    }
-  }
-
-  public abstract class FieldSubject extends MemberSubject {
-    public abstract boolean hasExplicitStaticValue();
-
-    public abstract DexEncodedField getField();
-
-    public abstract DexValue getStaticValue();
-
-    public abstract boolean isRenamed();
-
-    public abstract String getOriginalSignatureAttribute();
-
-    public abstract String getFinalSignatureAttribute();
-  }
-
-  public class AbsentFieldSubject extends FieldSubject {
-
-    @Override
-    public boolean isPublic() {
-      return false;
-    }
-
-    @Override
-    public boolean isStatic() {
-      return false;
-    }
-
-    @Override
-    public boolean isFinal() {
-      return false;
-    }
-
-    @Override
-    public boolean isPresent() {
-      return false;
-    }
-
-    @Override
-    public boolean isRenamed() {
-      return false;
-    }
-
-    @Override
-    public Signature getOriginalSignature() {
-      return null;
-    }
-
-    @Override
-    public Signature getFinalSignature() {
-      return null;
-    }
-
-    @Override
-    public boolean hasExplicitStaticValue() {
-      return false;
-    }
-
-    @Override
-    public DexValue getStaticValue() {
-      return null;
-    }
-
-    @Override
-    public DexEncodedField getField() {
-      return null;
-    }
-
-    @Override
-    public String getOriginalSignatureAttribute() {
-      return null;
-    }
-
-    @Override
-    public String getFinalSignatureAttribute() {
-      return null;
-    }
-  }
-
-  public class FoundFieldSubject extends FieldSubject {
-
-    private final FoundClassSubject clazz;
-    private final DexEncodedField dexField;
-
-    public FoundFieldSubject(DexEncodedField dexField, FoundClassSubject clazz) {
-      this.clazz = clazz;
-      this.dexField = dexField;
-    }
-
-    @Override
-    public boolean isPublic() {
-      return dexField.accessFlags.isPublic();
-    }
-
-    @Override
-    public boolean isStatic() {
-      return dexField.accessFlags.isStatic();
-    }
-
-    @Override
-    public boolean isFinal() {
-      return dexField.accessFlags.isFinal();
-    }
-
-    @Override
-    public boolean isPresent() {
-      return true;
-    }
-
-    @Override
-    public boolean isRenamed() {
-      return clazz.naming != null && !getFinalSignature().name.equals(getOriginalSignature().name);
-    }
-
-
-    public TypeSubject type() {
-      return new TypeSubject(dexField.field.type);
-    }
-
-    @Override
-    public FieldSignature getOriginalSignature() {
-      FieldSignature signature = getFinalSignature();
-      if (clazz.naming == null) {
-        return signature;
-      }
-
-      // Map the type to the original name. This is needed as the in the Proguard map the
-      // names on the left side are the original names. E.g.
-      //
-      //   X -> a
-      //     X field -> a
-      //
-      // whereas the final signature is for X.a is "a a"
-      String obfuscatedType = signature.type;
-      String originalType = originalToObfuscatedMapping.inverse().get(obfuscatedType);
-      String fieldType = originalType != null ? originalType : obfuscatedType;
-
-      FieldSignature lookupSignature = new FieldSignature(signature.name, fieldType);
-
-      MemberNaming memberNaming = clazz.naming.lookup(lookupSignature);
-      return memberNaming != null
-          ? (FieldSignature) memberNaming.getOriginalSignature()
-          : signature;
-    }
-
-    @Override
-    public FieldSignature getFinalSignature() {
-      return MemberNaming.FieldSignature.fromDexField(dexField.field);
-    }
-
-    @Override
-    public boolean hasExplicitStaticValue() {
-      return isStatic() && dexField.hasExplicitStaticValue();
-    }
-
-    @Override
-    public DexValue getStaticValue() {
-      return dexField.getStaticValue();
-    }
-
-    @Override
-    public DexEncodedField getField() {
-      return dexField;
-    }
-
-    @Override
-    public String getOriginalSignatureAttribute() {
-      return DexInspector.this.getOriginalSignatureAttribute(
-          dexField.annotations, GenericSignatureParser::parseFieldSignature);
-    }
-
-    @Override
-    public String getFinalSignatureAttribute() {
-      return DexInspector.this.getFinalSignatureAttribute(dexField.annotations);
-    }
-
-    @Override
-    public String toString() {
-      return dexField.toSourceString();
-    }
-  }
-
-  public class TypeSubject extends Subject {
-
-    private final DexType dexType;
-
-    TypeSubject(DexType dexType) {
-      this.dexType = dexType;
-    }
-
-    @Override
-    public boolean isPresent() {
-      return true;
-    }
-
-    @Override
-    public boolean isRenamed() {
-      return false;
-    }
-
-    public boolean is(String type) {
-      return dexType.equals(toDexType(type));
-    }
-
-    public String toString() {
-      return dexType.toSourceString();
-    }
-  }
-
-  private class InstructionSubjectFactory {
-
-    InstructionSubject create(Instruction instruction) {
-      if (isInvoke(instruction)) {
-        return new InvokeInstructionSubject(this, instruction);
-      } else if (isFieldAccess(instruction)) {
-        return new FieldAccessInstructionSubject(this, instruction);
-      } else {
-        return new InstructionSubject(this, instruction);
-      }
-    }
-
-    boolean isInvoke(Instruction instruction) {
-      return isInvokeVirtual(instruction)
-          || isInvokeInterface(instruction)
-          || isInvokeDirect(instruction)
-          || isInvokeSuper(instruction)
-          || isInvokeStatic(instruction);
-    }
-
-    boolean isInvokeVirtual(Instruction instruction) {
-      return instruction instanceof InvokeVirtual || instruction instanceof InvokeVirtualRange;
-    }
-
-    boolean isInvokeInterface(Instruction instruction) {
-      return instruction instanceof InvokeInterface || instruction instanceof InvokeInterfaceRange;
-    }
-
-    boolean isInvokeDirect(Instruction instruction) {
-      return instruction instanceof InvokeDirect || instruction instanceof InvokeDirectRange;
-    }
-
-    boolean isInvokeSuper(Instruction instruction) {
-      return instruction instanceof InvokeSuper || instruction instanceof InvokeSuperRange;
-    }
-
-    boolean isInvokeStatic(Instruction instruction) {
-      return instruction instanceof InvokeStatic || instruction instanceof InvokeStaticRange;
-    }
-
-    boolean isNop(Instruction instruction) {
-      return instruction instanceof Nop;
-    }
-
-    boolean isGoto(Instruction instruction) {
-      return instruction instanceof Goto;
-    }
-
-    boolean isReturnVoid(Instruction instruction) {
-      return instruction instanceof ReturnVoid;
-    }
-
-    boolean isConst4(Instruction instruction) {
-      return instruction instanceof Const4;
-    }
-
-    boolean isThrow(Instruction instruction) {
-      return instruction instanceof Throw;
-    }
-
-    boolean isConstString(Instruction instruction) {
-      return instruction instanceof ConstString;
-    }
-
-    boolean isConstString(Instruction instruction, String value) {
-      return instruction instanceof ConstString
-          && ((ConstString) instruction).BBBB.toSourceString().equals(value);
-    }
-
-    boolean isIfNez(Instruction instruction) {
-      return instruction instanceof IfNez;
-    }
-
-    boolean isIfEqz(Instruction instruction) {
-      return instruction instanceof IfEqz;
-    }
-
-    boolean isFieldAccess(Instruction instruction) {
-      return isInstanceGet(instruction)
-          || isInstancePut(instruction)
-          || isStaticGet(instruction)
-          || isStaticSet(instruction);
-    }
-
-    boolean isInstanceGet(Instruction instruction) {
-      return instruction instanceof Iget
-          || instruction instanceof IgetBoolean
-          || instruction instanceof IgetByte
-          || instruction instanceof IgetShort
-          || instruction instanceof IgetChar
-          || instruction instanceof IgetWide
-          || instruction instanceof IgetObject;
-    }
-
-    boolean isInstancePut(Instruction instruction) {
-      return instruction instanceof Iput
-          || instruction instanceof IputBoolean
-          || instruction instanceof IputByte
-          || instruction instanceof IputShort
-          || instruction instanceof IputChar
-          || instruction instanceof IputWide
-          || instruction instanceof IputObject;
-    }
-
-    boolean isStaticGet(Instruction instruction) {
-      return instruction instanceof Sget
-          || instruction instanceof SgetBoolean
-          || instruction instanceof SgetByte
-          || instruction instanceof SgetShort
-          || instruction instanceof SgetChar
-          || instruction instanceof SgetWide
-          || instruction instanceof SgetObject;
-    }
-
-    boolean isStaticSet(Instruction instruction) {
-      return instruction instanceof Sput
-          || instruction instanceof SputBoolean
-          || instruction instanceof SputByte
-          || instruction instanceof SputShort
-          || instruction instanceof SputChar
-          || instruction instanceof SputWide
-          || instruction instanceof SputObject;
-    }
-  }
-
-  public class InstructionSubject {
-
-    protected final InstructionSubjectFactory factory;
-    protected final Instruction instruction;
-
-    protected InstructionSubject(InstructionSubjectFactory factory, Instruction instruction) {
-      this.factory = factory;
-      this.instruction = instruction;
-    }
-
-    public boolean isInvoke() {
-      return factory.isInvoke(instruction);
-    }
-
-    public boolean isFieldAccess() {
-      return factory.isFieldAccess(instruction);
-    }
-
-    public boolean isInvokeVirtual() {
-      return factory.isInvokeVirtual(instruction);
-    }
-
-    public boolean isInvokeInterface() {
-      return factory.isInvokeInterface(instruction);
-    }
-
-    public boolean isInvokeDirect() {
-      return factory.isInvokeDirect(instruction);
-    }
-
-    public boolean isInvokeSuper() {
-      return factory.isInvokeSuper(instruction);
-    }
-
-    public boolean isInvokeStatic() {
-      return factory.isInvokeStatic(instruction);
-    }
-
-    boolean isFieldAccess(Instruction instruction) {
-      return factory.isFieldAccess(instruction);
-    }
-
-    public boolean isNop() {
-      return factory.isNop(instruction);
-    }
-
-    public boolean isConstString() {
-      return factory.isConstString(instruction);
-    }
-
-    public boolean isConstString(String value) {
-      return factory.isConstString(instruction, value);
-    }
-
-    public boolean isGoto() {
-      return factory.isGoto(instruction);
-    }
-
-    public boolean isIfNez() {
-      return factory.isIfNez(instruction);
-    }
-
-    public boolean isIfEqz() {
-      return factory.isIfEqz(instruction);
-    }
-
-    public boolean isReturnVoid() {
-      return factory.isReturnVoid(instruction);
-    }
-
-    public boolean isConst4() {
-      return factory.isConst4(instruction);
-    }
-
-    public boolean isThrow() {
-      return factory.isThrow(instruction);
-    }
-  }
-
-  public class InvokeInstructionSubject extends InstructionSubject {
-
-    InvokeInstructionSubject(InstructionSubjectFactory factory, Instruction instruction) {
-      super(factory, instruction);
-      assert isInvoke();
-    }
-
-    public TypeSubject holder() {
-      return new TypeSubject(invokedMethod().getHolder());
-    }
-
-    public DexMethod invokedMethod() {
-      if (instruction instanceof InvokeVirtual) {
-        return ((InvokeVirtual) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeVirtualRange) {
-        return ((InvokeVirtualRange) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeInterface) {
-        return ((InvokeInterface) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeInterfaceRange) {
-        return ((InvokeInterfaceRange) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeDirect) {
-        return ((InvokeDirect) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeDirectRange) {
-        return ((InvokeDirectRange) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeSuper) {
-        return ((InvokeSuper) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeSuperRange) {
-        return ((InvokeSuperRange) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeDirect) {
-        return ((InvokeDirect) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeDirectRange) {
-        return ((InvokeDirectRange) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeStatic) {
-        return ((InvokeStatic) instruction).getMethod();
-      }
-      if (instruction instanceof InvokeStaticRange) {
-        return ((InvokeStaticRange) instruction).getMethod();
-      }
-      assert false;
-      return null;
-    }
-  }
-
-  public class FieldAccessInstructionSubject extends InstructionSubject {
-
-    FieldAccessInstructionSubject(InstructionSubjectFactory factory, Instruction instruction) {
-      super(factory, instruction);
-      assert isFieldAccess();
-    }
-
-    public TypeSubject holder() {
-      return new TypeSubject(accessedField().getHolder());
-    }
-
-    public DexField accessedField() {
-      if (instruction instanceof Iget) {
-        return ((Iget) instruction).getField();
-      }
-      if (instruction instanceof IgetBoolean) {
-        return ((IgetBoolean) instruction).getField();
-      }
-      if (instruction instanceof IgetByte) {
-        return ((IgetByte) instruction).getField();
-      }
-      if (instruction instanceof IgetShort) {
-        return ((IgetShort) instruction).getField();
-      }
-      if (instruction instanceof IgetChar) {
-        return ((IgetChar) instruction).getField();
-      }
-      if (instruction instanceof IgetWide) {
-        return ((IgetWide) instruction).getField();
-      }
-      if (instruction instanceof IgetObject) {
-        return ((IgetObject) instruction).getField();
-      }
-      if (instruction instanceof Iput) {
-        return ((Iput) instruction).getField();
-      }
-      if (instruction instanceof IputBoolean) {
-        return ((IputBoolean) instruction).getField();
-      }
-      if (instruction instanceof IputByte) {
-        return ((IputByte) instruction).getField();
-      }
-      if (instruction instanceof IputShort) {
-        return ((IputShort) instruction).getField();
-      }
-      if (instruction instanceof IputChar) {
-        return ((IputChar) instruction).getField();
-      }
-      if (instruction instanceof IputWide) {
-        return ((IputWide) instruction).getField();
-      }
-      if (instruction instanceof IputObject) {
-        return ((IputObject) instruction).getField();
-      }
-      if (instruction instanceof Sget) {
-        return ((Sget) instruction).getField();
-      }
-      if (instruction instanceof SgetBoolean) {
-        return ((SgetBoolean) instruction).getField();
-      }
-      if (instruction instanceof SgetByte) {
-        return ((SgetByte) instruction).getField();
-      }
-      if (instruction instanceof SgetShort) {
-        return ((SgetShort) instruction).getField();
-      }
-      if (instruction instanceof SgetChar) {
-        return ((SgetChar) instruction).getField();
-      }
-      if (instruction instanceof SgetWide) {
-        return ((SgetWide) instruction).getField();
-      }
-      if (instruction instanceof SgetObject) {
-        return ((SgetObject) instruction).getField();
-      }
-      if (instruction instanceof Sput) {
-        return ((Sput) instruction).getField();
-      }
-      if (instruction instanceof SputBoolean) {
-        return ((SputBoolean) instruction).getField();
-      }
-      if (instruction instanceof SputByte) {
-        return ((SputByte) instruction).getField();
-      }
-      if (instruction instanceof SputShort) {
-        return ((SputShort) instruction).getField();
-      }
-      if (instruction instanceof SputChar) {
-        return ((SputChar) instruction).getField();
-      }
-      if (instruction instanceof SputWide) {
-        return ((SputWide) instruction).getField();
-      }
-      if (instruction instanceof SputObject) {
-        return ((SputObject) instruction).getField();
-      }
-      assert false;
-      return null;
-    }
-  }
-
-  private class InstructionIterator implements Iterator<InstructionSubject> {
-
-    private final DexCode code;
-    private int index;
-
-    InstructionIterator(MethodSubject method) {
-      assert method.isPresent();
-      this.code = method.getMethod().getCode().asDexCode();
-      this.index = 0;
-    }
-
-    @Override
-    public boolean hasNext() {
-      return index < code.instructions.length;
-    }
-
-    @Override
-    public InstructionSubject next() {
-      if (index == code.instructions.length) {
-        throw new NoSuchElementException();
-      }
-      return factory.create(code.instructions[index++]);
-    }
-  }
-
-  private class FilteredInstructionIterator<T extends InstructionSubject> implements Iterator<T> {
-
-    private final InstructionIterator iterator;
-    private final Predicate<InstructionSubject> predicate;
-    private InstructionSubject pendingNext = null;
-
-    FilteredInstructionIterator(MethodSubject method, Predicate<InstructionSubject> predicate) {
-      this.iterator = new InstructionIterator(method);
-      this.predicate = predicate;
-      hasNext();
-    }
-
-    @Override
-    public boolean hasNext() {
-      if (pendingNext == null) {
-        while (iterator.hasNext()) {
-          pendingNext = iterator.next();
-          if (predicate.test(pendingNext)) {
-            break;
-          }
-          pendingNext = null;
-        }
-      }
-      return pendingNext != null;
-    }
-
-    @Override
-    public T next() {
-      hasNext();
-      if (pendingNext == null) {
-        throw new NoSuchElementException();
-      }
-      // We cannot tell if the provided predicate will only match instruction subjects of type T.
-      @SuppressWarnings("unchecked")
-      T result = (T) pendingNext;
-      pendingNext = null;
-      return result;
-    }
-  }
-
-  // Build the generic signature using the current mapping if any.
-  class GenericSignatureGenerater implements GenericSignatureAction<String> {
-
-    private StringBuilder signature;
-
-    public String getSignature() {
-      return signature.toString();
-    }
-
-    @Override
-    public void parsedSymbol(char symbol) {
-      signature.append(symbol);
-    }
-
-    @Override
-    public void parsedIdentifier(String identifier) {
-      signature.append(identifier);
-    }
-
-    @Override
-    public String parsedTypeName(String name) {
-      String type = name;
-      if (originalToObfuscatedMapping != null) {
-        String original = originalToObfuscatedMapping.inverse().get(name);
-        type = original != null ? original : name;
-      }
-      signature.append(type);
-      return type;
-    }
-
-    @Override
-    public String parsedInnerTypeName(String enclosingType, String name) {
-      String type;
-      if (originalToObfuscatedMapping != null) {
-        // The enclosingType has already been mapped if a mapping is present.
-        String minifiedEnclosing = originalToObfuscatedMapping.get(enclosingType);
-        type = originalToObfuscatedMapping.inverse().get(minifiedEnclosing + "$" + name);
-        if (type != null) {
-          assert type.startsWith(enclosingType + "$");
-          name = type.substring(enclosingType.length() + 1);
-        }
-      } else {
-        type = enclosingType + "$" + name;
-      }
-      signature.append(name);
-      return type;
-    }
-
-    @Override
-    public void start() {
-      signature = new StringBuilder();
-    }
-
-    @Override
-    public void stop() {
-      // nothing to do
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/utils/Smali.java b/src/test/java/com/android/tools/r8/utils/Smali.java
index 68de752..ac46dfc 100644
--- a/src/test/java/com/android/tools/r8/utils/Smali.java
+++ b/src/test/java/com/android/tools/r8/utils/Smali.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.Origin;
 import com.google.common.collect.ImmutableList;
@@ -110,7 +111,14 @@
           app, options, new Timing("smali")).read(executor);
       ApplicationWriter writer =
           new ApplicationWriter(
-              dexApp, options, null, null, NamingLens.getIdentityLens(), null, null);
+              dexApp,
+              options,
+              null,
+              null,
+              GraphLense.getIdentityLense(),
+              NamingLens.getIdentityLens(),
+              null,
+              null);
       writer.write(executor);
       return consumer.contents;
     } finally {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentAnnotationSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentAnnotationSubject.java
new file mode 100644
index 0000000..a82d9b0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentAnnotationSubject.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+
+public class AbsentAnnotationSubject extends AnnotationSubject {
+
+  @Override
+  public boolean isPresent() {
+    return false;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    return false;
+  }
+
+  @Override
+  public DexEncodedAnnotation getAnnotation() {
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
new file mode 100644
index 0000000..167bc8d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexClass;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class AbsentClassSubject extends ClassSubject {
+
+  @Override
+  public boolean isPresent() {
+    return false;
+  }
+
+  @Override
+  public void forAllMethods(Consumer<FoundMethodSubject> inspection) {}
+
+  @Override
+  public MethodSubject method(String returnType, String name, List<String> parameters) {
+    return new AbsentMethodSubject();
+  }
+
+  @Override
+  public void forAllFields(Consumer<FoundFieldSubject> inspection) {}
+
+  @Override
+  public FieldSubject field(String type, String name) {
+    return new AbsentFieldSubject();
+  }
+
+  @Override
+  public boolean isAbstract() {
+    return false;
+  }
+
+  @Override
+  public boolean isAnnotation() {
+    return false;
+  }
+
+  @Override
+  public DexClass getDexClass() {
+    return null;
+  }
+
+  @Override
+  public AnnotationSubject annotation(String name) {
+    return new AbsentAnnotationSubject();
+  }
+
+  @Override
+  public String getOriginalName() {
+    return null;
+  }
+
+  @Override
+  public String getOriginalDescriptor() {
+    return null;
+  }
+
+  @Override
+  public String getFinalName() {
+    return null;
+  }
+
+  @Override
+  public String getFinalDescriptor() {
+    return null;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    return false;
+  }
+
+  @Override
+  public boolean isMemberClass() {
+    return false;
+  }
+
+  @Override
+  public boolean isLocalClass() {
+    return false;
+  }
+
+  @Override
+  public boolean isAnonymousClass() {
+    return false;
+  }
+
+  @Override
+  public boolean isSynthesizedJavaLambdaClass() {
+    return false;
+  }
+
+  @Override
+  public String getOriginalSignatureAttribute() {
+    return null;
+  }
+
+  @Override
+  public String getFinalSignatureAttribute() {
+    return null;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
new file mode 100644
index 0000000..d4409d1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentFieldSubject.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.naming.MemberNaming.Signature;
+
+public class AbsentFieldSubject extends FieldSubject {
+
+  @Override
+  public boolean isPublic() {
+    return false;
+  }
+
+  @Override
+  public boolean isStatic() {
+    return false;
+  }
+
+  @Override
+  public boolean isFinal() {
+    return false;
+  }
+
+  @Override
+  public boolean isPresent() {
+    return false;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    return false;
+  }
+
+  @Override
+  public Signature getOriginalSignature() {
+    return null;
+  }
+
+  @Override
+  public Signature getFinalSignature() {
+    return null;
+  }
+
+  @Override
+  public boolean hasExplicitStaticValue() {
+    return false;
+  }
+
+  @Override
+  public DexValue getStaticValue() {
+    return null;
+  }
+
+  @Override
+  public DexEncodedField getField() {
+    return null;
+  }
+
+  @Override
+  public String getOriginalSignatureAttribute() {
+    return null;
+  }
+
+  @Override
+  public String getFinalSignatureAttribute() {
+    return null;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
new file mode 100644
index 0000000..1396be9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentMethodSubject.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.naming.MemberNaming.Signature;
+
+public class AbsentMethodSubject extends MethodSubject {
+
+  @Override
+  public boolean isPresent() {
+    return false;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    return false;
+  }
+
+  @Override
+  public boolean isPublic() {
+    return false;
+  }
+
+  @Override
+  public boolean isStatic() {
+    return false;
+  }
+
+  @Override
+  public boolean isFinal() {
+    return false;
+  }
+
+  @Override
+  public boolean isAbstract() {
+    return false;
+  }
+
+  @Override
+  public boolean isBridge() {
+    return false;
+  }
+
+  @Override
+  public boolean isInstanceInitializer() {
+    return false;
+  }
+
+  @Override
+  public boolean isClassInitializer() {
+    return false;
+  }
+
+  @Override
+  public DexEncodedMethod getMethod() {
+    return null;
+  }
+
+  @Override
+  public Signature getOriginalSignature() {
+    return null;
+  }
+
+  @Override
+  public Signature getFinalSignature() {
+    return null;
+  }
+
+  @Override
+  public String getOriginalSignatureAttribute() {
+    return null;
+  }
+
+  @Override
+  public String getFinalSignatureAttribute() {
+    return null;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AnnotationSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AnnotationSubject.java
new file mode 100644
index 0000000..4bffcca
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AnnotationSubject.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+
+public abstract class AnnotationSubject extends Subject {
+
+  public abstract DexEncodedAnnotation getAnnotation();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionIterator.java
new file mode 100644
index 0000000..0a3263a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionIterator.java
@@ -0,0 +1,33 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.graph.Code;
+import java.util.Iterator;
+
+class CfInstructionIterator implements InstructionIterator {
+
+  private final CodeInspector codeInspector;
+  private final Iterator<CfInstruction> iterator;
+
+  CfInstructionIterator(CodeInspector codeInspector, MethodSubject method) {
+    this.codeInspector = codeInspector;
+    assert method.isPresent();
+    Code code = method.getMethod().getCode();
+    assert code != null && code.isCfCode();
+    iterator = code.asCfCode().getInstructions().iterator();
+  }
+
+  @Override
+  public boolean hasNext() {
+    return iterator.hasNext();
+  }
+
+  @Override
+  public InstructionSubject next() {
+    return codeInspector.createInstructionSubject(iterator.next());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
new file mode 100644
index 0000000..fa00432
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -0,0 +1,134 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfGoto;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfInvokeDynamic;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfNop;
+import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.cf.code.CfReturnVoid;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfThrow;
+import org.objectweb.asm.Opcodes;
+
+public class CfInstructionSubject implements InstructionSubject {
+  protected final CfInstruction instruction;
+
+  public CfInstructionSubject(CfInstruction instruction) {
+    this.instruction = instruction;
+  }
+
+  @Override
+  public boolean isFieldAccess() {
+    return instruction instanceof CfFieldInstruction;
+  }
+
+  @Override
+  public boolean isInvokeVirtual() {
+    return instruction instanceof CfInvoke
+        && ((CfInvoke) instruction).getOpcode() == Opcodes.INVOKEVIRTUAL;
+  }
+
+  @Override
+  public boolean isInvokeInterface() {
+    return instruction instanceof CfInvoke
+        && ((CfInvoke) instruction).getOpcode() == Opcodes.INVOKEINTERFACE;
+  }
+
+  @Override
+  public boolean isInvokeStatic() {
+    return instruction instanceof CfInvoke
+        && ((CfInvoke) instruction).getOpcode() == Opcodes.INVOKESTATIC;
+  }
+
+  @Override
+  public boolean isNop() {
+    return instruction instanceof CfNop;
+  }
+
+  @Override
+  public boolean isConstString(JumboStringMode jumboStringMode) {
+    return instruction instanceof CfConstString;
+  }
+
+  @Override
+  public boolean isConstString(String value, JumboStringMode jumboStringMode) {
+    return isConstString(jumboStringMode)
+        && ((CfConstString) instruction).getString().toSourceString().equals(value);
+  }
+
+  @Override
+  public boolean isGoto() {
+    return instruction instanceof CfGoto;
+  }
+
+  @Override
+  public boolean isIfNez() {
+    return instruction instanceof CfIf && ((CfIf) instruction).getOpcode() == Opcodes.IFNE;
+  }
+
+  @Override
+  public boolean isIfEqz() {
+    return instruction instanceof CfIf && ((CfIf) instruction).getOpcode() == Opcodes.IFEQ;
+  }
+
+  @Override
+  public boolean isReturnVoid() {
+    return instruction instanceof CfReturnVoid;
+  }
+
+  @Override
+  public boolean isThrow() {
+    return instruction instanceof CfThrow;
+  }
+
+  @Override
+  public boolean isInvoke() {
+    return instruction instanceof CfInvoke || instruction instanceof CfInvokeDynamic;
+  }
+
+  @Override
+  public boolean isNewInstance() {
+    return instruction instanceof CfNew;
+  }
+
+  public boolean isInvokeSpecial() {
+    return instruction instanceof CfInvoke
+        && ((CfInvoke) instruction).getOpcode() == Opcodes.INVOKESPECIAL;
+  }
+
+  public boolean isInvokeDynamic() {
+    return instruction instanceof CfInvokeDynamic;
+  }
+
+  public boolean isLabel() {
+    return instruction instanceof CfLabel;
+  }
+
+  public boolean isPosition() {
+    return instruction instanceof CfPosition;
+  }
+
+  public boolean isStackInstruction(CfStackInstruction.Opcode opcode) {
+    return instruction instanceof CfStackInstruction
+        && ((CfStackInstruction) instruction).getOpcode() == opcode;
+  }
+
+  public boolean isConstNull() {
+    return instruction instanceof CfConstNull;
+  }
+
+  public boolean isIfNull() {
+    return instruction instanceof CfIf && ((CfIf) instruction).getOpcode() == Opcodes.IFNULL;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
new file mode 100644
index 0000000..bbb6213
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+public abstract class ClassSubject extends Subject {
+
+  public abstract void forAllMethods(Consumer<FoundMethodSubject> inspection);
+
+  public MethodSubject method(Method method) {
+    List<String> parameters = new ArrayList<>();
+    for (Class<?> parameterType : method.getParameterTypes()) {
+      parameters.add(parameterType.getTypeName());
+    }
+    return method(method.getReturnType().getTypeName(), method.getName(), parameters);
+  }
+
+  public abstract MethodSubject method(String returnType, String name, List<String> parameters);
+
+  public MethodSubject clinit() {
+    return method("void", "<clinit>", ImmutableList.of());
+  }
+
+  public MethodSubject init(List<String> parameters) {
+    return method("void", "<init>", parameters);
+  }
+
+  public MethodSubject method(MethodSignature signature) {
+    return method(signature.type, signature.name, ImmutableList.copyOf(signature.parameters));
+  }
+
+  public MethodSubject method(SmaliBuilder.MethodSignature signature) {
+    return method(
+        signature.returnType, signature.name, ImmutableList.copyOf(signature.parameterTypes));
+  }
+
+  public abstract void forAllFields(Consumer<FoundFieldSubject> inspection);
+
+  public abstract FieldSubject field(String type, String name);
+
+  public abstract boolean isAbstract();
+
+  public abstract boolean isAnnotation();
+
+  public String dumpMethods() {
+    StringBuilder dump = new StringBuilder();
+    forAllMethods(
+        (FoundMethodSubject method) ->
+            dump.append(method.getMethod().toString()).append(method.getMethod().codeToString()));
+    return dump.toString();
+  }
+
+  public abstract DexClass getDexClass();
+
+  public abstract AnnotationSubject annotation(String name);
+
+  public abstract String getOriginalName();
+
+  public abstract String getOriginalDescriptor();
+
+  public abstract String getFinalName();
+
+  public abstract String getFinalDescriptor();
+
+  public abstract boolean isMemberClass();
+
+  public abstract boolean isLocalClass();
+
+  public abstract boolean isAnonymousClass();
+
+  public abstract boolean isSynthesizedJavaLambdaClass();
+
+  public abstract String getOriginalSignatureAttribute();
+
+  public abstract String getFinalSignatureAttribute();
+}
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
new file mode 100644
index 0000000..ecc78c6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -0,0 +1,350 @@
+// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils.codeinspector;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.naming.signature.GenericSignatureAction;
+import com.android.tools.r8.naming.signature.GenericSignatureParser;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class CodeInspector {
+
+  private final DexApplication application;
+  final DexItemFactory dexItemFactory;
+  private final ClassNameMapper mapping;
+  final BiMap<String, String> originalToObfuscatedMapping;
+
+  public static MethodSignature MAIN =
+      new MethodSignature("main", "void", new String[] {"java.lang.String[]"});
+
+  public CodeInspector(Path file, String mappingFile) throws IOException, ExecutionException {
+    this(Collections.singletonList(file), mappingFile);
+  }
+
+  public CodeInspector(Path file) throws IOException, ExecutionException {
+    this(Collections.singletonList(file), null);
+  }
+
+  public CodeInspector(List<Path> files) throws IOException, ExecutionException {
+    this(files, null);
+  }
+
+  public CodeInspector(List<Path> files, String mappingFile) throws IOException, ExecutionException {
+    if (mappingFile != null) {
+      this.mapping = ClassNameMapper.mapperFromFile(Paths.get(mappingFile));
+      originalToObfuscatedMapping = this.mapping.getObfuscatedToOriginalMapping().inverse();
+    } else {
+      this.mapping = null;
+      originalToObfuscatedMapping = null;
+    }
+    Timing timing = new Timing("CodeInspector");
+    InternalOptions options = new InternalOptions();
+    dexItemFactory = options.itemFactory;
+    AndroidApp input = AndroidApp.builder().addProgramFiles(files).build();
+    application = new ApplicationReader(input, options, timing).read();
+  }
+
+  public CodeInspector(AndroidApp app) throws IOException, ExecutionException {
+    this(
+        new ApplicationReader(app, new InternalOptions(), new Timing("CodeInspector"))
+            .read(app.getProguardMapOutputData()));
+  }
+
+  public CodeInspector(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
+      throws IOException, ExecutionException {
+    this(
+        new ApplicationReader(app, runOptionsConsumer(optionsConsumer), new Timing("CodeInspector"))
+            .read(app.getProguardMapOutputData()));
+  }
+
+  private static InternalOptions runOptionsConsumer(Consumer<InternalOptions> optionsConsumer) {
+    InternalOptions internalOptions = new InternalOptions();
+    optionsConsumer.accept(internalOptions);
+    return internalOptions;
+  }
+
+  public CodeInspector(AndroidApp app, Path proguardMap) throws IOException, ExecutionException {
+    this(
+        new ApplicationReader(app, new InternalOptions(), new Timing("CodeInspector"))
+            .read(StringResource.fromFile(proguardMap)));
+  }
+
+  public CodeInspector(DexApplication application) {
+    dexItemFactory = application.dexItemFactory;
+    this.application = application;
+    this.mapping = application.getProguardMap();
+    originalToObfuscatedMapping =
+        mapping == null ? null : mapping.getObfuscatedToOriginalMapping().inverse();
+  }
+
+  public DexItemFactory getFactory() {
+    return dexItemFactory;
+  }
+
+  DexType toDexType(String string) {
+    return dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(string));
+  }
+
+  private DexType toDexTypeIgnorePrimitives(String string) {
+    return dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptorIgnorePrimitives(string));
+  }
+
+  static <S, T extends Subject> void forAll(
+      S[] items,
+      BiFunction<S, FoundClassSubject, ? extends T> constructor,
+      FoundClassSubject clazz,
+      Consumer<T> consumer) {
+    for (S item : items) {
+      consumer.accept(constructor.apply(item, clazz));
+    }
+  }
+
+  private static <S, T extends Subject> void forAll(
+      Iterable<S> items, Function<S, T> constructor, Consumer<T> consumer) {
+    for (S item : items) {
+      consumer.accept(constructor.apply(item));
+    }
+  }
+
+  DexAnnotation findAnnotation(String name, DexAnnotationSet annotations) {
+    for (DexAnnotation annotation : annotations.annotations) {
+      DexType type = annotation.annotation.type;
+      String original = mapping == null ? type.toSourceString() : mapping.originalNameOf(type);
+      if (original.equals(name)) {
+        return annotation;
+      }
+    }
+    return null;
+  }
+
+  public String getFinalSignatureAttribute(DexAnnotationSet annotations) {
+    DexAnnotation annotation = findAnnotation("dalvik.annotation.Signature", annotations);
+    if (annotation == null) {
+      return null;
+    }
+    assert annotation.annotation.elements.length == 1;
+    DexAnnotationElement element = annotation.annotation.elements[0];
+    assert element.value instanceof DexValueArray;
+    StringBuilder builder = new StringBuilder();
+    DexValueArray valueArray = (DexValueArray) element.value;
+    for (DexValue value : valueArray.getValues()) {
+      assertTrue(value instanceof DexValueString);
+      DexValueString s = (DexValueString) value;
+      builder.append(s.getValue());
+    }
+    return builder.toString();
+  }
+
+  public String getOriginalSignatureAttribute(
+      DexAnnotationSet annotations, BiConsumer<GenericSignatureParser, String> parse) {
+    String finalSignature = getFinalSignatureAttribute(annotations);
+    if (finalSignature == null || mapping == null) {
+      return finalSignature;
+    }
+
+    GenericSignatureGenerator rewriter = new GenericSignatureGenerator();
+    GenericSignatureParser<String> parser = new GenericSignatureParser<>(rewriter);
+    parse.accept(parser, finalSignature);
+    return rewriter.getSignature();
+  }
+
+  public ClassSubject clazz(Class clazz) {
+    return clazz(clazz.getTypeName());
+  }
+
+  /** Lookup a class by name. This allows both original and obfuscated names. */
+  public ClassSubject clazz(String name) {
+    ClassNamingForNameMapper naming = null;
+    if (mapping != null) {
+      String obfuscated = originalToObfuscatedMapping.get(name);
+      if (obfuscated != null) {
+        naming = mapping.getClassNaming(obfuscated);
+        name = obfuscated;
+      } else {
+        // Figure out if the name is an already obfuscated name.
+        String original = originalToObfuscatedMapping.inverse().get(name);
+        if (original != null) {
+          naming = mapping.getClassNaming(name);
+        }
+      }
+    }
+    DexClass clazz = application.definitionFor(toDexTypeIgnorePrimitives(name));
+    if (clazz == null) {
+      return new AbsentClassSubject();
+    }
+    return new FoundClassSubject(this, clazz, naming);
+  }
+
+  public void forAllClasses(Consumer<FoundClassSubject> inspection) {
+    forAll(
+        application.classes(),
+        cls -> {
+          ClassSubject subject = clazz(cls.type.toSourceString());
+          assert subject.isPresent();
+          return (FoundClassSubject) subject;
+        },
+        inspection);
+  }
+
+  public List<FoundClassSubject> allClasses() {
+    ImmutableList.Builder<FoundClassSubject> builder = ImmutableList.builder();
+    forAllClasses(builder::add);
+    return builder.build();
+  }
+
+  public MethodSubject method(Method method) {
+    ClassSubject clazz = clazz(method.getDeclaringClass());
+    if (!clazz.isPresent()) {
+      return new AbsentMethodSubject();
+    }
+    return clazz.method(method);
+  }
+
+  String getObfuscatedTypeName(String originalTypeName) {
+    String obfuscatedType = null;
+    if (mapping != null) {
+      obfuscatedType = originalToObfuscatedMapping.get(originalTypeName);
+    }
+    obfuscatedType = obfuscatedType == null ? originalTypeName : obfuscatedType;
+    return obfuscatedType;
+  }
+
+  InstructionSubject createInstructionSubject(Instruction instruction) {
+    DexInstructionSubject dexInst = new DexInstructionSubject(instruction);
+    if (dexInst.isInvoke()) {
+      return new InvokeDexInstructionSubject(this, instruction);
+    } else if (dexInst.isFieldAccess()) {
+      return new FieldAccessDexInstructionSubject(this, instruction);
+    } else if (dexInst.isNewInstance()) {
+      return new NewInstanceDexInstructionSubject(instruction);
+    } else if (dexInst.isConstString(JumboStringMode.ALLOW)) {
+      return new ConstStringDexInstructionSubject(instruction);
+    } else {
+      return dexInst;
+    }
+  }
+
+  InstructionSubject createInstructionSubject(CfInstruction instruction) {
+    CfInstructionSubject cfInst = new CfInstructionSubject(instruction);
+    if (cfInst.isInvoke()) {
+      return new InvokeCfInstructionSubject(this, instruction);
+    } else if (cfInst.isFieldAccess()) {
+      return new FieldAccessCfInstructionSubject(this, instruction);
+    } else if (cfInst.isNewInstance()) {
+      return new NewInstanceCfInstructionSubject(instruction);
+    } else if (cfInst.isConstString(JumboStringMode.ALLOW)) {
+      return new ConstStringCfInstructionSubject(instruction);
+    } else {
+      return cfInst;
+    }
+  }
+
+  InstructionIterator createInstructionIterator(MethodSubject method) {
+    Code code = method.getMethod().getCode();
+    assert code != null;
+    if (code.isDexCode()) {
+      return new DexInstructionIterator(this, method);
+    } else if (code.isCfCode()) {
+      return new CfInstructionIterator(this, method);
+    } else {
+      throw new Unimplemented("InstructionIterator is implemented for DexCode and CfCode only.");
+    }
+  }
+
+  // Build the generic signature using the current mapping if any.
+  class GenericSignatureGenerator implements GenericSignatureAction<String> {
+
+    private StringBuilder signature;
+
+    public String getSignature() {
+      return signature.toString();
+    }
+
+    @Override
+    public void parsedSymbol(char symbol) {
+      signature.append(symbol);
+    }
+
+    @Override
+    public void parsedIdentifier(String identifier) {
+      signature.append(identifier);
+    }
+
+    @Override
+    public String parsedTypeName(String name) {
+      String type = name;
+      if (originalToObfuscatedMapping != null) {
+        String original = originalToObfuscatedMapping.inverse().get(name);
+        type = original != null ? original : name;
+      }
+      signature.append(type);
+      return type;
+    }
+
+    @Override
+    public String parsedInnerTypeName(String enclosingType, String name) {
+      String type;
+      if (originalToObfuscatedMapping != null) {
+        // The enclosingType has already been mapped if a mapping is present.
+        String minifiedEnclosing = originalToObfuscatedMapping.get(enclosingType);
+        type = originalToObfuscatedMapping.inverse().get(minifiedEnclosing + "$" + name);
+        if (type != null) {
+          assert type.startsWith(enclosingType + "$");
+          name = type.substring(enclosingType.length() + 1);
+        }
+      } else {
+        type = enclosingType + "$" + name;
+      }
+      signature.append(name);
+      return type;
+    }
+
+    @Override
+    public void start() {
+      signature = new StringBuilder();
+    }
+
+    @Override
+    public void stop() {
+      // nothing to do
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringCfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringCfInstructionSubject.java
new file mode 100644
index 0000000..da545e4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringCfInstructionSubject.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.graph.DexString;
+
+public class ConstStringCfInstructionSubject extends CfInstructionSubject
+    implements ConstStringInstructionSubject {
+  public ConstStringCfInstructionSubject(CfInstruction instruction) {
+    super(instruction);
+    assert isConstString(JumboStringMode.ALLOW);
+  }
+
+  @Override
+  public DexString getString() {
+    return ((CfConstString) instruction).getString();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringDexInstructionSubject.java
new file mode 100644
index 0000000..cb69144
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringDexInstructionSubject.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.ConstStringJumbo;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.graph.DexString;
+
+public class ConstStringDexInstructionSubject extends DexInstructionSubject
+    implements ConstStringInstructionSubject {
+  public ConstStringDexInstructionSubject(Instruction instruction) {
+    super(instruction);
+    assert isConstString(JumboStringMode.ALLOW);
+  }
+
+  @Override
+  public DexString getString() {
+    if (instruction instanceof ConstString) {
+      return ((ConstString) instruction).getString();
+    } else {
+      assert (instruction instanceof ConstStringJumbo);
+      return ((ConstStringJumbo) instruction).getString();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringInstructionSubject.java
new file mode 100644
index 0000000..5914be7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ConstStringInstructionSubject.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexString;
+
+public interface ConstStringInstructionSubject extends InstructionSubject {
+  DexString getString();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java
new file mode 100644
index 0000000..8ae5609
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionIterator.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexCode;
+import java.util.NoSuchElementException;
+
+class DexInstructionIterator implements InstructionIterator {
+
+  private final CodeInspector codeInspector;
+  private final DexCode code;
+  private int index;
+
+  DexInstructionIterator(CodeInspector codeInspector, MethodSubject method) {
+    this.codeInspector = codeInspector;
+    assert method.isPresent();
+    Code code = method.getMethod().getCode();
+    assert code != null && code.isDexCode();
+    this.code = code.asDexCode();
+    this.index = 0;
+  }
+
+  @Override
+  public boolean hasNext() {
+    return index < code.instructions.length;
+  }
+
+  @Override
+  public InstructionSubject next() {
+    if (index == code.instructions.length) {
+      throw new NoSuchElementException();
+    }
+    return codeInspector.createInstructionSubject(code.instructions[index++]);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
new file mode 100644
index 0000000..9c6d21c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -0,0 +1,195 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.ConstStringJumbo;
+import com.android.tools.r8.code.Goto;
+import com.android.tools.r8.code.IfEqz;
+import com.android.tools.r8.code.IfNez;
+import com.android.tools.r8.code.Iget;
+import com.android.tools.r8.code.IgetBoolean;
+import com.android.tools.r8.code.IgetByte;
+import com.android.tools.r8.code.IgetChar;
+import com.android.tools.r8.code.IgetObject;
+import com.android.tools.r8.code.IgetShort;
+import com.android.tools.r8.code.IgetWide;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.InvokeDirect;
+import com.android.tools.r8.code.InvokeDirectRange;
+import com.android.tools.r8.code.InvokeInterface;
+import com.android.tools.r8.code.InvokeInterfaceRange;
+import com.android.tools.r8.code.InvokeStatic;
+import com.android.tools.r8.code.InvokeStaticRange;
+import com.android.tools.r8.code.InvokeSuper;
+import com.android.tools.r8.code.InvokeSuperRange;
+import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.code.InvokeVirtualRange;
+import com.android.tools.r8.code.Iput;
+import com.android.tools.r8.code.IputBoolean;
+import com.android.tools.r8.code.IputByte;
+import com.android.tools.r8.code.IputChar;
+import com.android.tools.r8.code.IputObject;
+import com.android.tools.r8.code.IputShort;
+import com.android.tools.r8.code.IputWide;
+import com.android.tools.r8.code.NewInstance;
+import com.android.tools.r8.code.Nop;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.code.Sget;
+import com.android.tools.r8.code.SgetBoolean;
+import com.android.tools.r8.code.SgetByte;
+import com.android.tools.r8.code.SgetChar;
+import com.android.tools.r8.code.SgetObject;
+import com.android.tools.r8.code.SgetShort;
+import com.android.tools.r8.code.SgetWide;
+import com.android.tools.r8.code.Sput;
+import com.android.tools.r8.code.SputBoolean;
+import com.android.tools.r8.code.SputByte;
+import com.android.tools.r8.code.SputChar;
+import com.android.tools.r8.code.SputObject;
+import com.android.tools.r8.code.SputShort;
+import com.android.tools.r8.code.SputWide;
+import com.android.tools.r8.code.Throw;
+
+public class DexInstructionSubject implements InstructionSubject {
+  protected final Instruction instruction;
+
+  public DexInstructionSubject(Instruction instruction) {
+    this.instruction = instruction;
+  }
+
+  @Override
+  public boolean isFieldAccess() {
+    return isInstanceGet() || isInstancePut() || isStaticGet() || isStaticSet();
+  }
+
+  @Override
+  public boolean isInvokeVirtual() {
+    return instruction instanceof InvokeVirtual || instruction instanceof InvokeVirtualRange;
+  }
+
+  @Override
+  public boolean isInvokeInterface() {
+    return instruction instanceof InvokeInterface || instruction instanceof InvokeInterfaceRange;
+  }
+
+  @Override
+  public boolean isInvokeStatic() {
+    return instruction instanceof InvokeStatic || instruction instanceof InvokeStaticRange;
+  }
+
+  @Override
+  public boolean isNop() {
+    return instruction instanceof Nop;
+  }
+
+  @Override
+  public boolean isConstString(JumboStringMode jumboStringMode) {
+    return instruction instanceof ConstString
+        || (jumboStringMode == JumboStringMode.ALLOW && instruction instanceof ConstStringJumbo);
+  }
+
+  @Override
+  public boolean isConstString(String value, JumboStringMode jumboStringMode) {
+    return (instruction instanceof ConstString
+            && ((ConstString) instruction).BBBB.toSourceString().equals(value))
+        || (jumboStringMode == JumboStringMode.ALLOW
+            && instruction instanceof ConstStringJumbo
+            && ((ConstStringJumbo) instruction).BBBBBBBB.toSourceString().equals(value));
+  }
+
+  @Override
+  public boolean isGoto() {
+
+    return instruction instanceof Goto;
+  }
+
+  @Override
+  public boolean isIfNez() {
+    return instruction instanceof IfNez;
+  }
+
+  @Override
+  public boolean isIfEqz() {
+    return instruction instanceof IfEqz;
+  }
+
+  @Override
+  public boolean isReturnVoid() {
+    return instruction instanceof ReturnVoid;
+  }
+
+  @Override
+  public boolean isThrow() {
+    return instruction instanceof Throw;
+  }
+
+  @Override
+  public boolean isInvoke() {
+    return isInvokeVirtual()
+        || isInvokeInterface()
+        || isInvokeDirect()
+        || isInvokeSuper()
+        || isInvokeStatic();
+  }
+
+  @Override
+  public boolean isNewInstance() {
+    return instruction instanceof NewInstance;
+  }
+
+  public boolean isInvokeSuper() {
+    return instruction instanceof InvokeSuper || instruction instanceof InvokeSuperRange;
+  }
+
+  public boolean isInvokeDirect() {
+    return instruction instanceof InvokeDirect || instruction instanceof InvokeDirectRange;
+  }
+
+  public boolean isConst4() {
+    return instruction instanceof Const4;
+  }
+
+  public boolean isInstanceGet() {
+    return instruction instanceof Iget
+        || instruction instanceof IgetBoolean
+        || instruction instanceof IgetByte
+        || instruction instanceof IgetShort
+        || instruction instanceof IgetChar
+        || instruction instanceof IgetWide
+        || instruction instanceof IgetObject;
+  }
+
+  public boolean isInstancePut() {
+    return instruction instanceof Iput
+        || instruction instanceof IputBoolean
+        || instruction instanceof IputByte
+        || instruction instanceof IputShort
+        || instruction instanceof IputChar
+        || instruction instanceof IputWide
+        || instruction instanceof IputObject;
+  }
+
+  public boolean isStaticGet() {
+    return instruction instanceof Sget
+        || instruction instanceof SgetBoolean
+        || instruction instanceof SgetByte
+        || instruction instanceof SgetShort
+        || instruction instanceof SgetChar
+        || instruction instanceof SgetWide
+        || instruction instanceof SgetObject;
+  }
+
+  public boolean isStaticSet() {
+    return instruction instanceof Sput
+        || instruction instanceof SputBoolean
+        || instruction instanceof SputByte
+        || instruction instanceof SputShort
+        || instruction instanceof SputChar
+        || instruction instanceof SputWide
+        || instruction instanceof SputObject;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessCfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessCfInstructionSubject.java
new file mode 100644
index 0000000..e9ccfd3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessCfInstructionSubject.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstruction;
+
+public class FieldAccessCfInstructionSubject extends CfInstructionSubject
+    implements FieldAccessInstructionSubject {
+  private final CodeInspector codeInspector;
+
+  public FieldAccessCfInstructionSubject(CodeInspector codeInspector, CfInstruction instruction) {
+    super(instruction);
+    this.codeInspector = codeInspector;
+    assert isFieldAccess();
+  }
+
+  @Override
+  public TypeSubject holder() {
+    return new TypeSubject(codeInspector, ((CfFieldInstruction) instruction).getField().getHolder());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessDexInstructionSubject.java
new file mode 100644
index 0000000..b9e7fd2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessDexInstructionSubject.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.code.Instruction;
+
+public class FieldAccessDexInstructionSubject extends DexInstructionSubject
+    implements FieldAccessInstructionSubject {
+
+  private final CodeInspector codeInspector;
+
+  public FieldAccessDexInstructionSubject(CodeInspector codeInspector, Instruction instruction) {
+    super(instruction);
+    this.codeInspector = codeInspector;
+    assert isFieldAccess();
+  }
+
+  @Override
+  public TypeSubject holder() {
+    return new TypeSubject(codeInspector, instruction.getField().getHolder());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessInstructionSubject.java
new file mode 100644
index 0000000..355f3a4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldAccessInstructionSubject.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+public interface FieldAccessInstructionSubject extends InstructionSubject {
+  TypeSubject holder();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
new file mode 100644
index 0000000..f9a669a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FieldSubject.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexValue;
+
+public abstract class FieldSubject extends MemberSubject {
+  public abstract boolean hasExplicitStaticValue();
+
+  public abstract DexEncodedField getField();
+
+  public abstract DexValue getStaticValue();
+
+  public abstract boolean isRenamed();
+
+  public abstract String getOriginalSignatureAttribute();
+
+  public abstract String getFinalSignatureAttribute();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredInstructionIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredInstructionIterator.java
new file mode 100644
index 0000000..870953e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FilteredInstructionIterator.java
@@ -0,0 +1,50 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.function.Predicate;
+
+class FilteredInstructionIterator<T extends InstructionSubject> implements Iterator<T> {
+
+  private final InstructionIterator iterator;
+  private final Predicate<InstructionSubject> predicate;
+  private InstructionSubject pendingNext = null;
+
+  FilteredInstructionIterator(
+      CodeInspector codeInspector, MethodSubject method, Predicate<InstructionSubject> predicate) {
+    this.iterator = codeInspector.createInstructionIterator(method);
+    this.predicate = predicate;
+    hasNext();
+  }
+
+  @Override
+  public boolean hasNext() {
+    if (pendingNext == null) {
+      while (iterator.hasNext()) {
+        pendingNext = iterator.next();
+        if (predicate.test(pendingNext)) {
+          break;
+        }
+        pendingNext = null;
+      }
+    }
+    return pendingNext != null;
+  }
+
+  @Override
+  public T next() {
+    hasNext();
+    if (pendingNext == null) {
+      throw new NoSuchElementException();
+    }
+    // We cannot tell if the provided predicate will only match instruction subjects of type T.
+    @SuppressWarnings("unchecked")
+    T result = (T) pendingNext;
+    pendingNext = null;
+    return result;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java
new file mode 100644
index 0000000..546ef59
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundAnnotationSubject.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
+
+public class FoundAnnotationSubject extends AnnotationSubject {
+
+  private final DexAnnotation annotation;
+
+  public FoundAnnotationSubject(DexAnnotation annotation) {
+    this.annotation = annotation;
+  }
+
+  @Override
+  public boolean isPresent() {
+    return true;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    return false;
+  }
+
+  @Override
+  public DexEncodedAnnotation getAnnotation() {
+    return annotation.annotation;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
new file mode 100644
index 0000000..9a0661f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -0,0 +1,253 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexAnnotation;
+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;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
+import com.android.tools.r8.naming.MemberNaming;
+import com.android.tools.r8.naming.MemberNaming.FieldSignature;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.naming.signature.GenericSignatureParser;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class FoundClassSubject extends ClassSubject {
+
+  private final CodeInspector codeInspector;
+  private final DexClass dexClass;
+  final ClassNamingForNameMapper naming;
+
+  FoundClassSubject(CodeInspector codeInspector, DexClass dexClass, ClassNamingForNameMapper naming) {
+    this.codeInspector = codeInspector;
+    this.dexClass = dexClass;
+    this.naming = naming;
+  }
+
+  @Override
+  public boolean isPresent() {
+    return true;
+  }
+
+  @Override
+  public void forAllMethods(Consumer<FoundMethodSubject> inspection) {
+    CodeInspector.forAll(
+        dexClass.directMethods(),
+        (encoded, clazz) -> new FoundMethodSubject(codeInspector, encoded, clazz),
+        this,
+        inspection);
+    CodeInspector.forAll(
+        dexClass.virtualMethods(),
+        (encoded, clazz) -> new FoundMethodSubject(codeInspector, encoded, clazz),
+        this,
+        inspection);
+  }
+
+  @Override
+  public MethodSubject method(String returnType, String name, List<String> parameters) {
+    DexType[] parameterTypes = new DexType[parameters.size()];
+    for (int i = 0; i < parameters.size(); i++) {
+      parameterTypes[i] =
+          codeInspector.toDexType(codeInspector.getObfuscatedTypeName(parameters.get(i)));
+    }
+    DexProto proto =
+        codeInspector.dexItemFactory.createProto(
+            codeInspector.toDexType(codeInspector.getObfuscatedTypeName(returnType)), parameterTypes);
+    if (naming != null) {
+      String[] parameterStrings = new String[parameterTypes.length];
+      Signature signature =
+          new MethodSignature(name, returnType, parameters.toArray(parameterStrings));
+      MemberNaming methodNaming = naming.lookupByOriginalSignature(signature);
+      if (methodNaming != null) {
+        name = methodNaming.getRenamedName();
+      }
+    }
+    DexMethod dexMethod =
+        codeInspector.dexItemFactory.createMethod(
+            dexClass.type, proto, codeInspector.dexItemFactory.createString(name));
+    DexEncodedMethod encoded = findMethod(dexClass.directMethods(), dexMethod);
+    if (encoded == null) {
+      encoded = findMethod(dexClass.virtualMethods(), dexMethod);
+    }
+    return encoded == null
+        ? new AbsentMethodSubject()
+        : new FoundMethodSubject(codeInspector, encoded, this);
+  }
+
+  private DexEncodedMethod findMethod(DexEncodedMethod[] methods, DexMethod dexMethod) {
+    for (DexEncodedMethod method : methods) {
+      if (method.method.equals(dexMethod)) {
+        return method;
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public void forAllFields(Consumer<FoundFieldSubject> inspection) {
+    CodeInspector.forAll(
+        dexClass.staticFields(),
+        (dexField, clazz) -> new FoundFieldSubject(codeInspector, dexField, clazz),
+        this,
+        inspection);
+    CodeInspector.forAll(
+        dexClass.instanceFields(),
+        (dexField, clazz) -> new FoundFieldSubject(codeInspector, dexField, clazz),
+        this,
+        inspection);
+  }
+
+  @Override
+  public FieldSubject field(String type, String name) {
+    String obfuscatedType = codeInspector.getObfuscatedTypeName(type);
+    MemberNaming fieldNaming = null;
+    if (naming != null) {
+      fieldNaming = naming.lookupByOriginalSignature(new FieldSignature(name, type));
+    }
+    String obfuscatedName = fieldNaming == null ? name : fieldNaming.getRenamedName();
+
+    DexField field =
+        codeInspector.dexItemFactory.createField(
+            dexClass.type,
+            codeInspector.toDexType(obfuscatedType),
+            codeInspector.dexItemFactory.createString(obfuscatedName));
+    DexEncodedField encoded = findField(dexClass.staticFields(), field);
+    if (encoded == null) {
+      encoded = findField(dexClass.instanceFields(), field);
+    }
+    return encoded == null
+        ? new AbsentFieldSubject()
+        : new FoundFieldSubject(codeInspector, encoded, this);
+  }
+
+  @Override
+  public boolean isAbstract() {
+    return dexClass.accessFlags.isAbstract();
+  }
+
+  @Override
+  public boolean isAnnotation() {
+    return dexClass.accessFlags.isAnnotation();
+  }
+
+  private DexEncodedField findField(DexEncodedField[] fields, DexField dexField) {
+    for (DexEncodedField field : fields) {
+      if (field.field.equals(dexField)) {
+        return field;
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public DexClass getDexClass() {
+    return dexClass;
+  }
+
+  @Override
+  public AnnotationSubject annotation(String name) {
+    // Ensure we don't check for annotations represented as attributes.
+    assert !name.endsWith("EnclosingClass")
+        && !name.endsWith("EnclosingMethod")
+        && !name.endsWith("InnerClass");
+    DexAnnotation annotation = codeInspector.findAnnotation(name, dexClass.annotations);
+    return annotation == null
+        ? new AbsentAnnotationSubject()
+        : new FoundAnnotationSubject(annotation);
+  }
+
+  @Override
+  public String getOriginalName() {
+    if (naming != null) {
+      return naming.originalName;
+    } else {
+      return getFinalName();
+    }
+  }
+
+  @Override
+  public String getOriginalDescriptor() {
+    if (naming != null) {
+      return DescriptorUtils.javaTypeToDescriptor(naming.originalName);
+    } else {
+      return getFinalDescriptor();
+    }
+  }
+
+  @Override
+  public String getFinalName() {
+    return DescriptorUtils.descriptorToJavaType(getFinalDescriptor());
+  }
+
+  @Override
+  public String getFinalDescriptor() {
+    return dexClass.type.descriptor.toString();
+  }
+
+  @Override
+  public boolean isRenamed() {
+    return naming != null && !getFinalDescriptor().equals(getOriginalDescriptor());
+  }
+
+  private InnerClassAttribute getInnerClassAttribute() {
+    for (InnerClassAttribute innerClassAttribute : dexClass.getInnerClasses()) {
+      if (dexClass.type == innerClassAttribute.getInner()) {
+        return innerClassAttribute;
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public boolean isLocalClass() {
+    InnerClassAttribute innerClass = getInnerClassAttribute();
+    return innerClass != null && innerClass.isNamed() && dexClass.getEnclosingMethod() != null;
+  }
+
+  @Override
+  public boolean isMemberClass() {
+    InnerClassAttribute innerClass = getInnerClassAttribute();
+    return innerClass != null
+        && innerClass.getOuter() != null
+        && innerClass.isNamed()
+        && dexClass.getEnclosingMethod() == null;
+  }
+
+  @Override
+  public boolean isAnonymousClass() {
+    InnerClassAttribute innerClass = getInnerClassAttribute();
+    return innerClass != null && innerClass.isAnonymous() && dexClass.getEnclosingMethod() != null;
+  }
+
+  @Override
+  public boolean isSynthesizedJavaLambdaClass() {
+    return dexClass.type.getName().contains("$Lambda$");
+  }
+
+  @Override
+  public String getOriginalSignatureAttribute() {
+    return codeInspector.getOriginalSignatureAttribute(
+        dexClass.annotations, GenericSignatureParser::parseClassSignature);
+  }
+
+  @Override
+  public String getFinalSignatureAttribute() {
+    return codeInspector.getFinalSignatureAttribute(dexClass.annotations);
+  }
+
+  @Override
+  public String toString() {
+    return dexClass.toSourceString();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
new file mode 100644
index 0000000..ae42b14
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundFieldSubject.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.naming.MemberNaming;
+import com.android.tools.r8.naming.MemberNaming.FieldSignature;
+import com.android.tools.r8.naming.signature.GenericSignatureParser;
+
+public class FoundFieldSubject extends FieldSubject {
+
+  private final CodeInspector codeInspector;
+  private final FoundClassSubject clazz;
+  private final DexEncodedField dexField;
+
+  public FoundFieldSubject(
+      CodeInspector codeInspector, DexEncodedField dexField, FoundClassSubject clazz) {
+    this.codeInspector = codeInspector;
+    this.clazz = clazz;
+    this.dexField = dexField;
+  }
+
+  @Override
+  public boolean isPublic() {
+    return dexField.accessFlags.isPublic();
+  }
+
+  @Override
+  public boolean isStatic() {
+    return dexField.accessFlags.isStatic();
+  }
+
+  @Override
+  public boolean isFinal() {
+    return dexField.accessFlags.isFinal();
+  }
+
+  @Override
+  public boolean isPresent() {
+    return true;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    return clazz.naming != null && !getFinalSignature().name.equals(getOriginalSignature().name);
+  }
+
+  public TypeSubject type() {
+    return new TypeSubject(codeInspector, dexField.field.type);
+  }
+
+  @Override
+  public FieldSignature getOriginalSignature() {
+    FieldSignature signature = getFinalSignature();
+    if (clazz.naming == null) {
+      return signature;
+    }
+
+    // Map the type to the original name. This is needed as the in the Proguard map the
+    // names on the left side are the original names. E.g.
+    //
+    //   X -> a
+    //     X field -> a
+    //
+    // whereas the final signature is for X.a is "a a"
+    String obfuscatedType = signature.type;
+    String originalType = codeInspector.originalToObfuscatedMapping.inverse().get(obfuscatedType);
+    String fieldType = originalType != null ? originalType : obfuscatedType;
+
+    FieldSignature lookupSignature = new FieldSignature(signature.name, fieldType);
+
+    MemberNaming memberNaming = clazz.naming.lookup(lookupSignature);
+    return memberNaming != null ? (FieldSignature) memberNaming.getOriginalSignature() : signature;
+  }
+
+  @Override
+  public FieldSignature getFinalSignature() {
+    return FieldSignature.fromDexField(dexField.field);
+  }
+
+  @Override
+  public boolean hasExplicitStaticValue() {
+    return isStatic() && dexField.hasExplicitStaticValue();
+  }
+
+  @Override
+  public DexValue getStaticValue() {
+    return dexField.getStaticValue();
+  }
+
+  @Override
+  public DexEncodedField getField() {
+    return dexField;
+  }
+
+  @Override
+  public String getOriginalSignatureAttribute() {
+    return codeInspector.getOriginalSignatureAttribute(
+        dexField.annotations, GenericSignatureParser::parseFieldSignature);
+  }
+
+  @Override
+  public String getFinalSignatureAttribute() {
+    return codeInspector.getFinalSignatureAttribute(dexField.annotations);
+  }
+
+  @Override
+  public String toString() {
+    return dexField.toSourceString();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
new file mode 100644
index 0000000..3fe8186
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundMethodSubject.java
@@ -0,0 +1,140 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.naming.MemberNaming;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.naming.signature.GenericSignatureParser;
+import java.util.Iterator;
+import java.util.function.Predicate;
+
+public class FoundMethodSubject extends MethodSubject {
+
+  private final CodeInspector codeInspector;
+  private final FoundClassSubject clazz;
+  private final DexEncodedMethod dexMethod;
+
+  public FoundMethodSubject(
+      CodeInspector codeInspector, DexEncodedMethod encoded, FoundClassSubject clazz) {
+    this.codeInspector = codeInspector;
+    this.clazz = clazz;
+    this.dexMethod = encoded;
+  }
+
+  @Override
+  public boolean isPresent() {
+    return true;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    return clazz.naming != null && !getFinalSignature().name.equals(getOriginalSignature().name);
+  }
+
+  @Override
+  public boolean isPublic() {
+    return dexMethod.accessFlags.isPublic();
+  }
+
+  @Override
+  public boolean isStatic() {
+    return dexMethod.accessFlags.isStatic();
+  }
+
+  @Override
+  public boolean isFinal() {
+    return dexMethod.accessFlags.isFinal();
+  }
+
+  @Override
+  public boolean isAbstract() {
+    return dexMethod.accessFlags.isAbstract();
+  }
+
+  @Override
+  public boolean isBridge() {
+    return dexMethod.accessFlags.isBridge();
+  }
+
+  @Override
+  public boolean isInstanceInitializer() {
+    return dexMethod.isInstanceInitializer();
+  }
+
+  @Override
+  public boolean isClassInitializer() {
+    return dexMethod.isClassInitializer();
+  }
+
+  @Override
+  public DexEncodedMethod getMethod() {
+    return dexMethod;
+  }
+
+  @Override
+  public MethodSignature getOriginalSignature() {
+    MethodSignature signature = getFinalSignature();
+    if (clazz.naming == null) {
+      return signature;
+    }
+
+    // Map the parameters and return type to original names. This is needed as the in the
+    // Proguard map the names on the left side are the original names. E.g.
+    //
+    //   X -> a
+    //     X method(X) -> a
+    //
+    // whereas the final signature is for X.a is "a (a)"
+    String[] OriginalParameters = new String[signature.parameters.length];
+    for (int i = 0; i < OriginalParameters.length; i++) {
+      String obfuscated = signature.parameters[i];
+      String original = codeInspector.originalToObfuscatedMapping.inverse().get(obfuscated);
+      OriginalParameters[i] = original != null ? original : obfuscated;
+    }
+    String obfuscatedReturnType = signature.type;
+    String originalReturnType =
+        codeInspector.originalToObfuscatedMapping.inverse().get(obfuscatedReturnType);
+    String returnType = originalReturnType != null ? originalReturnType : obfuscatedReturnType;
+
+    MethodSignature lookupSignature =
+        new MethodSignature(signature.name, returnType, OriginalParameters);
+
+    MemberNaming memberNaming = clazz.naming.lookup(lookupSignature);
+    return memberNaming != null ? (MethodSignature) memberNaming.getOriginalSignature() : signature;
+  }
+
+  @Override
+  public MethodSignature getFinalSignature() {
+    return MethodSignature.fromDexMethod(dexMethod.method);
+  }
+
+  @Override
+  public String getOriginalSignatureAttribute() {
+    return codeInspector.getOriginalSignatureAttribute(
+        dexMethod.annotations, GenericSignatureParser::parseMethodSignature);
+  }
+
+  @Override
+  public String getFinalSignatureAttribute() {
+    return codeInspector.getFinalSignatureAttribute(dexMethod.annotations);
+  }
+
+  @Override
+  public Iterator<InstructionSubject> iterateInstructions() {
+    return codeInspector.createInstructionIterator(this);
+  }
+
+  @Override
+  public <T extends InstructionSubject> Iterator<T> iterateInstructions(
+      Predicate<InstructionSubject> filter) {
+    return new FilteredInstructionIterator<>(codeInspector, this, filter);
+  }
+
+  @Override
+  public String toString() {
+    return dexMethod.toSourceString();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionIterator.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionIterator.java
new file mode 100644
index 0000000..4685730
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionIterator.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import java.util.Iterator;
+
+interface InstructionIterator extends Iterator<InstructionSubject> {}
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
new file mode 100644
index 0000000..6c1f4be
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+public interface InstructionSubject {
+
+  enum JumboStringMode {
+    ALLOW,
+    DISALLOW
+  };
+
+  boolean isFieldAccess();
+
+  boolean isInvokeVirtual();
+
+  boolean isInvokeInterface();
+
+  boolean isInvokeStatic();
+
+  boolean isNop();
+
+  boolean isConstString(JumboStringMode jumboStringMode);
+
+  boolean isConstString(String value, JumboStringMode jumboStringMode);
+
+  boolean isGoto();
+
+  boolean isIfNez();
+
+  boolean isIfEqz();
+
+  boolean isReturnVoid();
+
+  boolean isThrow();
+
+  boolean isInvoke();
+
+  boolean isNewInstance();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeCfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeCfInstructionSubject.java
new file mode 100644
index 0000000..b307112
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeCfInstructionSubject.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.DexMethod;
+
+public class InvokeCfInstructionSubject extends CfInstructionSubject
+    implements InvokeInstructionSubject {
+  private final CodeInspector codeInspector;
+
+  public InvokeCfInstructionSubject(CodeInspector codeInspector, CfInstruction instruction) {
+    super(instruction);
+    assert isInvoke();
+    this.codeInspector = codeInspector;
+  }
+
+  @Override
+  public TypeSubject holder() {
+    return new TypeSubject(codeInspector, invokedMethod().getHolder());
+  }
+
+  @Override
+  public DexMethod invokedMethod() {
+    if (isInvokeDynamic()) {
+      throw new Unimplemented(
+          "invokeMethod is not implemented for the INVOKEDYNAMIC CF instruction.");
+    }
+    return ((CfInvoke) instruction).getMethod();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeDexInstructionSubject.java
new file mode 100644
index 0000000..7b5d41c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeDexInstructionSubject.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.graph.DexMethod;
+
+public class InvokeDexInstructionSubject extends DexInstructionSubject
+    implements InvokeInstructionSubject {
+
+  private final CodeInspector codeInspector;
+
+  public InvokeDexInstructionSubject(CodeInspector codeInspector, Instruction instruction) {
+    super(instruction);
+    this.codeInspector = codeInspector;
+    assert isInvoke();
+  }
+
+  @Override
+  public TypeSubject holder() {
+    return new TypeSubject(codeInspector, invokedMethod().getHolder());
+  }
+
+  @Override
+  public DexMethod invokedMethod() {
+    return instruction.getMethod();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeInstructionSubject.java
new file mode 100644
index 0000000..b2f99dc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InvokeInstructionSubject.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexMethod;
+
+public interface InvokeInstructionSubject extends InstructionSubject {
+  TypeSubject holder();
+
+  DexMethod invokedMethod();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
similarity index 93%
rename from src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java
rename to src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 1986bbc..3b718a4 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -2,18 +2,14 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-package com.android.tools.r8.utils;
+package com.android.tools.r8.utils.codeinspector;
 
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
-import com.android.tools.r8.utils.DexInspector.FieldSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
-import com.android.tools.r8.utils.DexInspector.Subject;
 import com.google.common.collect.ImmutableList;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
 
-public class DexInspectorMatchers {
+public class Matchers {
 
   private static String type(Subject subject) {
     String type = "<unknown subject type>";
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
new file mode 100644
index 0000000..9e90fde
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.naming.MemberNaming.Signature;
+
+public abstract class MemberSubject extends Subject {
+
+  public abstract boolean isPublic();
+
+  public abstract boolean isStatic();
+
+  public abstract boolean isFinal();
+
+  public abstract Signature getOriginalSignature();
+
+  public abstract Signature getFinalSignature();
+
+  public String getOriginalName() {
+    Signature originalSignature = getOriginalSignature();
+    return originalSignature == null ? null : originalSignature.name;
+  }
+
+  public String getFinalName() {
+    Signature finalSignature = getFinalSignature();
+    return finalSignature == null ? null : finalSignature.name;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
new file mode 100644
index 0000000..6eaaf5f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MethodSubject.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import java.util.Iterator;
+import java.util.function.Predicate;
+
+public abstract class MethodSubject extends MemberSubject {
+
+  public abstract boolean isAbstract();
+
+  public abstract boolean isBridge();
+
+  public abstract boolean isInstanceInitializer();
+
+  public abstract boolean isClassInitializer();
+
+  public abstract String getOriginalSignatureAttribute();
+
+  public abstract String getFinalSignatureAttribute();
+
+  public abstract DexEncodedMethod getMethod();
+
+  public Iterator<InstructionSubject> iterateInstructions() {
+    return null;
+  }
+
+  public <T extends InstructionSubject> Iterator<T> iterateInstructions(
+      Predicate<InstructionSubject> filter) {
+    return null;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceCfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceCfInstructionSubject.java
new file mode 100644
index 0000000..d3b0a93
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceCfInstructionSubject.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.graph.DexType;
+
+public class NewInstanceCfInstructionSubject extends CfInstructionSubject
+    implements NewInstanceInstructionSubject {
+  public NewInstanceCfInstructionSubject(CfInstruction instruction) {
+    super(instruction);
+  }
+
+  @Override
+  public DexType getType() {
+    return ((CfNew) instruction).getType();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceDexInstructionSubject.java
new file mode 100644
index 0000000..a396468
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceDexInstructionSubject.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.NewInstance;
+import com.android.tools.r8.graph.DexType;
+
+public class NewInstanceDexInstructionSubject extends DexInstructionSubject
+    implements NewInstanceInstructionSubject {
+  public NewInstanceDexInstructionSubject(Instruction instruction) {
+    super(instruction);
+  }
+
+  @Override
+  public DexType getType() {
+    return ((NewInstance) instruction).getType();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceInstructionSubject.java
new file mode 100644
index 0000000..2a0f192
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/NewInstanceInstructionSubject.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexType;
+
+public interface NewInstanceInstructionSubject extends InstructionSubject {
+  DexType getType();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Subject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Subject.java
new file mode 100644
index 0000000..d93e19a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Subject.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+public abstract class Subject {
+
+  public abstract boolean isPresent();
+
+  public abstract boolean isRenamed();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/TypeSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/TypeSubject.java
new file mode 100644
index 0000000..ba2230a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/TypeSubject.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils.codeinspector;
+
+import com.android.tools.r8.graph.DexType;
+
+public class TypeSubject extends Subject {
+
+  private final CodeInspector codeInspector;
+  private final DexType dexType;
+
+  TypeSubject(CodeInspector codeInspector, DexType dexType) {
+    this.codeInspector = codeInspector;
+    this.dexType = dexType;
+  }
+
+  @Override
+  public boolean isPresent() {
+    return true;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    return false;
+  }
+
+  public boolean is(String type) {
+    return dexType.equals(codeInspector.toDexType(type));
+  }
+
+  public String toString() {
+    return dexType.toSourceString();
+  }
+}
diff --git a/src/test/kotlinR8TestResources/class_staticizer/main.kt b/src/test/kotlinR8TestResources/class_staticizer/main.kt
new file mode 100644
index 0000000..01915c4
--- /dev/null
+++ b/src/test/kotlinR8TestResources/class_staticizer/main.kt
@@ -0,0 +1,43 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package class_staticizer
+
+private var COUNT = 0
+
+fun next() = "${COUNT++}".padStart(3, '0')
+
+fun main(args: Array<String>) {
+    println(Regular.foo)
+    println(Regular.bar)
+    println(Regular.blah(next()))
+    println(Derived.foo)
+    println(Derived.bar)
+    println(Derived.blah(next()))
+    println(Util.foo)
+    println(Util.bar)
+    println(Util.blah(next()))
+}
+
+open class Regular {
+    companion object {
+        var foo: String = "Regular::CC::foo[${next()}]"
+        var bar: String = blah(next())
+        fun blah(p: String) = "Regular::CC::blah($p)[${next()}]"
+    }
+}
+
+open class Derived : Regular() {
+    companion object {
+        var foo: String = "Derived::CC::foo[${next()}]"
+        var bar: String = blah(next())
+        fun blah(p: String) = "Derived::CC::blah($p)[${next()}]"
+    }
+}
+
+object Util {
+    var foo: String = "Util::foo[${next()}]"
+    var bar: String = Regular.blah(next()) + Derived.blah(next())
+    fun blah(p: String) = "Util::blah($p)[${next()}]"
+}
diff --git a/third_party/gmail/gmail_android_170604.16.tar.gz.sha1 b/third_party/gmail/gmail_android_170604.16.tar.gz.sha1
index 9d9985c..f57ba90 100644
--- a/third_party/gmail/gmail_android_170604.16.tar.gz.sha1
+++ b/third_party/gmail/gmail_android_170604.16.tar.gz.sha1
@@ -1 +1 @@
-a6d49ef4fb2094672a6f6be039c971727cc9fd34
\ No newline at end of file
+161c569821a5c9b4cb8e99de764f3449191af084
\ No newline at end of file
diff --git a/third_party/gmscore/gmscore_v10.tar.gz.sha1 b/third_party/gmscore/gmscore_v10.tar.gz.sha1
index 535f285..15cab81 100644
--- a/third_party/gmscore/gmscore_v10.tar.gz.sha1
+++ b/third_party/gmscore/gmscore_v10.tar.gz.sha1
@@ -1 +1 @@
-fd2bc157ba2d61a19804107df47e0f87926b210d
\ No newline at end of file
+43838ee1687ff48e866396dfd4b99415662fbea6
\ No newline at end of file
diff --git a/third_party/gmscore/gmscore_v9.tar.gz.sha1 b/third_party/gmscore/gmscore_v9.tar.gz.sha1
index 0ff5779..5983b5b 100644
--- a/third_party/gmscore/gmscore_v9.tar.gz.sha1
+++ b/third_party/gmscore/gmscore_v9.tar.gz.sha1
@@ -1 +1 @@
-4bfdee0d2287b061164f984dfa4ad6ec8617effa
\ No newline at end of file
+0066065faeb293c5a850d3319f2cb8a48d1e760d
\ No newline at end of file
diff --git a/third_party/youtube/youtube.android_12.22.tar.gz.sha1 b/third_party/youtube/youtube.android_12.22.tar.gz.sha1
index 056ff59..8f6813c 100644
--- a/third_party/youtube/youtube.android_12.22.tar.gz.sha1
+++ b/third_party/youtube/youtube.android_12.22.tar.gz.sha1
@@ -1 +1 @@
-57b5c53a80ba010d1faef7da1b643f8c72b3e4e8
\ No newline at end of file
+73c4880898d734064815d0426d8fe84ee6d075b4
\ No newline at end of file