Merge "Revert "The meet of precise types is only defined for identical types.""
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 805bdf7..fd547cf 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -235,7 +235,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."));
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 584b06d..f0ae9e0 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -618,6 +618,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/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 55c4134..0ef8a34 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.
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/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 3586d6d..9a44dde 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -634,6 +634,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;
 
@@ -705,6 +706,14 @@
       return this.trivialInitializerInfo;
     }
 
+    private void setInitializerEnablingJavaAssertions() {
+      this.initializerEnablingJavaAssertions = true;
+    }
+
+    public boolean isInitializerEnablingJavaAssertions() {
+      return initializerEnablingJavaAssertions;
+    }
+
     public long getReturnedConstant() {
       assert returnsConstant();
       return returnedConstant;
@@ -754,6 +763,10 @@
       forceInline = true;
     }
 
+    private void unsetForceInline() {
+      forceInline = false;
+    }
+
     private void markPublicized() {
       publicized = true;
     }
@@ -831,10 +844,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();
   }
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/ir/code/Monitor.java b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
index 6b3c89c..3b1b2d1 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
@@ -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
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/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index d5f923e..33531da 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
@@ -108,6 +108,7 @@
   private final boolean enableWholeProgramOptimizations;
 
   private final OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
+  private final OptimizationFeedback simpleOptimizationFeedback = new OptimizationFeedbackSimple();
   private DexString highestSortingString;
 
   private IRConverter(
@@ -176,7 +177,8 @@
     }
     this.classInliner =
         (options.enableClassInlining && options.enableInlining && inliner != null)
-            ? new ClassInliner(appInfo.dexItemFactory, options.classInliningInstructionLimit)
+            ? new ClassInliner(
+            appInfo.dexItemFactory, lambdaRewriter, options.classInliningInstructionLimit)
             : null;
   }
 
@@ -356,24 +358,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);
@@ -405,8 +417,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 =
@@ -565,7 +577,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);
     }
@@ -672,7 +684,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 +745,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 +762,11 @@
       assert code.isConsistentSSA();
     }
 
+    if (interfaceMethodRewriter != null) {
+      interfaceMethodRewriter.rewriteMethodReferences(method, code);
+      assert code.isConsistentSSA();
+    }
+
     if (lambdaMerger != null) {
       lambdaMerger.processMethodCode(method, code);
       assert code.isConsistentSSA();
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..49bfac8 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
@@ -21,6 +21,7 @@
   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..1d7a2d6 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
@@ -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..2fb2969 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
@@ -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..b31f28a
--- /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.Constraint;
+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, Constraint state) {
+    // Just as processed, don't provide any inlining constraints.
+    method.markProcessed(Constraint.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/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 81407bd..86f3f17 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) {
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..51b8b8a 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
@@ -141,7 +141,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];
@@ -170,10 +169,19 @@
     }
   }
 
+  /**
+   * 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/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 94e8147..d87a508 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
@@ -1265,7 +1265,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 +1310,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) {
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/MethodPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
new file mode 100644
index 0000000..9af3967
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
@@ -0,0 +1,147 @@
+// 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.DexProgramClass;
+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.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;
+
+// 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<>();
+      submitAll(application.classes(), futures, executorService);
+      ThreadUtils.awaitFutures(futures);
+    } finally {
+      timing.end();
+    }
+  }
+
+  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<DexProgramClass> classes, List<Future<?>> futures, ExecutorService executorService) {
+    for (DexProgramClass 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);
+              }
+            });
+      }
+    };
+  }
+
+  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 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));
+    }
+  }
+}
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/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/synthetic/SynthesizedCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SynthesizedCode.java
index a5807c3..7835da2 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
@@ -10,25 +10,33 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 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;
   }
 
@@ -40,7 +48,15 @@
   @Override
   public final IRCode buildIR(
       DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options, Origin origin) {
-    return new IRBuilder(encodedMethod, appInfo, sourceCode, options).build();
+    return new IRBuilder(encodedMethod, appInfo, sourceCodeProvider.get(), options).build();
+  }
+
+  @Override
+  public IRCode buildInliningIR(
+      DexEncodedMethod encodedMethod, AppInfo appInfo, InternalOptions options,
+      ValueNumberGenerator valueNumberGenerator, Position callerPosition, Origin origin) {
+    return new IRBuilder(encodedMethod, appInfo,
+        sourceCodeProvider.get(), options, valueNumberGenerator).build();
   }
 
   @Override
@@ -59,17 +75,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/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/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/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/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index eb8ee0e..93a80b6 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -447,7 +447,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() {
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/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 4ba014e..37a3010 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(...);
 }
diff --git a/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
index b3950b4..c773989 100644
--- a/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
@@ -9,7 +9,7 @@
 
 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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 7328c6e..801abae 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.dexinspector.DexInspector;
 import com.google.common.base.Charsets;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableListMultimap;
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index f20335b..36a5fb3 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.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.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(DexInspector 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/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index b0876c9..2a17917 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.dexinspector.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.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>> {
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
index 4701ac3..aa17280 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.dexinspector.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.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.io.ByteStreams;
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
index 60ebf39..d1894fb 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.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.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.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import java.io.IOException;
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 8adddfd..2d951fa 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -20,14 +20,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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -54,6 +54,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.
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 2667ce2..2767df7 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -474,8 +474,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));
   }
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..706b50b
--- /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.dexinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.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(DexInspector dexInspector, Class clazz, MethodSignature signature) {
+    ClassSubject classSubject = dexInspector.clazz(clazz);
+    assertThat(classSubject, isPresent());
+    MethodSubject methodSubject = classSubject.method(signature);
+    assertThat(methodSubject, isPublic());
+  }
+
+  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()));
+  }
+
+}
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..713c467
--- /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.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+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);
+
+    DexInspector dexInspector = new DexInspector(app);
+    for (Class clazz : CLASSES) {
+      ClassSubject classSubject = dexInspector.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..4459699 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.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.function.Consumer;
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 69%
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..9bfd4e2 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.dexinspector.DexInspector;
 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;
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..a692b9e 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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
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..77b251d 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.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import org.junit.Test;
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..8fcf7a3 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.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
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..272406f 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.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.lang.reflect.Method;
 import java.nio.file.Path;
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..f1eddcd 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.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.lang.reflect.Method;
 import java.util.List;
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..d285dc6 100644
--- a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
@@ -19,7 +19,7 @@
 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.dexinspector.DexInspector;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
 import it.unimi.dsi.fastutil.ints.IntSet;
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..e1d110c 100644
--- a/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
@@ -18,7 +18,7 @@
 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.dexinspector.DexInspector;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collections;
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..6196081 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.dexinspector.DexInspector;
 import java.util.ArrayList;
 import java.util.Collections;
 import org.junit.Test;
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..91e7b5d 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.classmerging;
 
 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.dexinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -29,11 +29,11 @@
 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.FoundClassSubject;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
@@ -70,6 +70,7 @@
     options.enableClassMerging = true;
     options.enableClassInlining = false;
     options.enableMinification = false;
+    options.testing.nondeterministicCycleElimination = true;
   }
 
   private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
@@ -115,6 +116,29 @@
     }
   }
 
+  // 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 testCallGraphCycle() throws Exception {
+    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 Exception {
     String main = "classmerging.ConflictInGeneratedNameTest";
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..d524d7c 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.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
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..7d27c5e 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
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..0d5849b 100644
--- a/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/compatproguard/CompatProguardSmaliTestBase.java
@@ -10,7 +10,7 @@
 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.dexinspector.DexInspector;
 import java.nio.file.Path;
 import java.util.List;
 
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..3d9c912 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
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..e478115 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
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..d0f9d20 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.Collections;
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..1bb357f 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.Collection;
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..aa8bc0c 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.dexinspector.DexInspector;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.HashSet;
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..f37b5ed 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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.Files;
 import java.nio.file.Path;
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..7c4c250 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import java.io.FileNotFoundException;
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..8cba9a0 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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.Collections;
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..3987e0a 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.dexinspector.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.google.common.io.ByteStreams;
 import com.google.common.io.Closer;
 import java.io.File;
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..c011f6f
--- /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.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+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"));
+
+    DexInspector inspector = new DexInspector(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"));
+
+    DexInspector inspector = new DexInspector(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..f02098a 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.dexinspector.DexInspector;
 import java.io.IOException;
 import java.util.List;
 import java.util.ListIterator;
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..de97052 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
@@ -25,18 +25,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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableMap;
 import java.util.Map;
 import java.util.function.BiConsumer;
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..286fb56 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
@@ -30,10 +30,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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import java.nio.charset.StandardCharsets;
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..6aecc60 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.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import java.util.Collections;
 import org.junit.Assert;
 import org.junit.Test;
@@ -156,44 +158,44 @@
     DexInspector inspector = new DexInspector(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/optimize/B87341268.java b/src/test/java/com/android/tools/r8/ir/optimize/B87341268.java
index 3f7bcfb..11c7c0f 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,13 +3,13 @@
 // 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.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import org.junit.Test;
 
 public class B87341268 extends TestBase {
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..5a2bc22 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
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..2ef43fd 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
@@ -26,9 +26,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.dexinspector.DexInspector;
 import java.util.function.Consumer;
 import org.junit.Test;
 
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..8e78784 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
@@ -28,10 +28,10 @@
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Files;
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..43b728e 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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
 import java.util.List;
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..d40e202 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.Sets;
 import java.nio.file.Path;
 import java.util.Collections;
@@ -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);
+
+    DexInspector inspector = new DexInspector(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..0ae7971 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import org.junit.Test;
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..4b603952 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import java.nio.file.Path;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
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..c79e50e 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.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import java.util.List;
 import java.util.Map;
 import org.junit.Test;
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..36156eb 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,12 +4,13 @@
 
 package com.android.tools.r8.ir.regalloc;
 
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.dexinspector.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.dexinspector.DexInspector;
 import org.junit.Test;
 
 public class B79405526 extends TestBase {
@@ -17,7 +18,7 @@
   public void test() throws Exception {
     AndroidApp app = compileWithD8(readClasses(TestClass.class));
     DexInspector inspector = new DexInspector(app);
-    DexInspector.ClassSubject clazz = inspector.clazz(TestClass.class);
+    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..d8861b6 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.dexinspector.DexInspector;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
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..ed9194e 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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
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..b56824d 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
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..c7a32b3 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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
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..c2dd7b1 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.dexinspector.AnnotationSubject;
+import com.android.tools.r8.utils.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.nio.file.Files;
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..4fd1d8d 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.FieldSubject;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.nio.file.Paths;
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..a28d0c5 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
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..658e89a 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.dexinspector.DexInspector;
 import com.google.common.collect.Lists;
 import java.io.IOException;
 import java.util.ArrayList;
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..233bb24 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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Files;
 import java.nio.file.Path;
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..53b7925 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -16,9 +16,9 @@
 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.FieldSubject;
 import java.nio.file.Path;
 import java.util.Collections;
 import org.junit.Assert;
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..45f74af 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import java.util.Collections;
 import java.util.function.Consumer;
 import org.junit.Test;
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..c764974 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import java.util.Collections;
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..03c2873 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.FieldSubject;
 import java.util.Map.Entry;
 import java.util.function.Consumer;
 import org.junit.Test;
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..fd41344 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.Arrays;
 import org.junit.Test;
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..dc5347e 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -59,8 +59,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 +66,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.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.FoundClassSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import java.io.IOException;
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..fea8160 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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Paths;
 import java.util.ArrayList;
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..d3805d9 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.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.List;
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..c2ec4b7 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.dexinspector.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.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
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..9b120e6 100644
--- a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
+++ b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
@@ -13,10 +13,10 @@
 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.InstructionSubject;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.Iterator;
 import java.util.function.Consumer;
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..876a758 100644
--- a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
@@ -21,11 +21,11 @@
 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.dexinspector.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 java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
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..58e17d1 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -20,10 +20,10 @@
 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.nio.file.Path;
@@ -37,9 +37,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;
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..41e514a 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.FieldSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
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..e081f18 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.dexinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 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 {
 
@@ -290,26 +306,36 @@
       Consumer<DexInspector> 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);
+    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(
+                        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());
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..02ec163 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.dexinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.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 {
 
@@ -164,17 +180,27 @@
       Consumer<DexInspector> 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);
+    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(
+                        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());
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..efc9855 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.dexinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.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 {
 
@@ -185,17 +202,27 @@
       Consumer<DexInspector> 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);
+    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(
+                        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());
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..25e2358 100644
--- a/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
+++ b/src/test/java/com/android/tools/r8/naming/WarnReflectiveAccessTest.java
@@ -3,7 +3,7 @@
 // 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.dexinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -19,10 +19,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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.lang.reflect.Method;
 import java.nio.file.Path;
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..4e46637 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.dexinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -17,9 +17,9 @@
 import com.android.tools.r8.naming.b72391662.subpackage.OtherPackageTestClass;
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import java.util.List;
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..1b7d842 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.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
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..0feddfd 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import org.junit.Test;
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..6e3e30a 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.FieldSubject;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import java.util.List;
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..8ae31f7 100644
--- a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
+++ b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
@@ -14,11 +14,12 @@
 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.InvokeInstructionSubject;
-import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.DexInstructionSubject;
+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.google.common.collect.ImmutableList;
 import java.util.Iterator;
 import java.util.function.BiConsumer;
@@ -82,7 +83,8 @@
     assertTrue(insn.isInvoke());
     assertTrue(((InvokeInstructionSubject) insn)
         .invokedMethod().name.toString().equals("throwNpe"));
-    assertTrue(nextInstruction(instructions).isConst4());
+    insn = nextInstruction(instructions);
+    assertTrue(insn instanceof DexInstructionSubject && ((DexInstructionSubject) insn).isConst4());
     assertTrue(nextInstruction(instructions).isThrow());
     assertFalse(instructions.hasNext());
 
@@ -105,7 +107,8 @@
       assertTrue(((InvokeInstructionSubject) insn)
           .invokedMethod().name.toString().equals("throwNpe"));
     }
-    assertTrue(nextInstruction(instructions).isConst4());
+    insn = nextInstruction(instructions);
+    assertTrue(insn instanceof DexInstructionSubject && ((DexInstructionSubject) insn).isConst4());
     assertTrue(nextInstruction(instructions).isThrow());
     assertFalse(instructions.hasNext());
 
@@ -119,7 +122,8 @@
     assertTrue(insn.isInvoke());
     assertTrue(((InvokeInstructionSubject) insn)
         .invokedMethod().name.toString().equals("innerNotReachable"));
-    assertTrue(nextInstruction(instructions).isConst4());
+    insn = nextInstruction(instructions);
+    assertTrue(insn instanceof DexInstructionSubject && ((DexInstructionSubject) insn).isConst4());
     assertTrue(nextInstruction(instructions).isThrow());
     assertFalse(instructions.hasNext());
   }
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..68f6fe0 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.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.io.File;
 import java.nio.file.Files;
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..1d2c6fe 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import java.util.Base64;
 import java.util.Set;
 import org.junit.Test;
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..abf43b2 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.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.FieldSubject;
+import com.android.tools.r8.utils.dexinspector.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;
@@ -231,6 +241,8 @@
     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,
@@ -283,9 +302,7 @@
   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);
+    check(inspector, 1, 1);
   }
 
   private void checkMixed(AndroidApp app) throws Exception{
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..9cc17af 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
@@ -13,8 +13,8 @@
 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.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.FoundClassSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
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..448e472 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
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..a35adbc 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.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.nio.file.Paths;
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..ca62dee 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.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 {
 
@@ -38,4 +160,75 @@
         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)
+            .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..f92b1dc 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.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 java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
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..c3037f8 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.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 java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
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..5fe2abd 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.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import org.junit.Test;
 
 public class StaticValuesTest extends SmaliTestBase {
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..7ea9cdd 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.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.stream.Collectors;
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..7eae052 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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Paths;
 import java.util.List;
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..9242842 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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.Collection;
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..5fa8edc 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.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import org.junit.Test;
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..c6ccc61 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.dexinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -31,7 +31,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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.ByteStreams;
 import com.google.common.io.CharSource;
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..22b6790 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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.io.File;
 import java.io.IOException;
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..24d4ba5 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.FoundFieldSubject;
+import com.android.tools.r8.utils.dexinspector.FoundMethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Files;
 import java.nio.file.Path;
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..d739348 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
@@ -15,9 +15,9 @@
 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
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..f7889ee 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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..d58d975 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..6cfcd87 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+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.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..8ea939a 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..1651fca 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.FieldSubject;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..ba01d2f 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..26ea045 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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..0c662bb 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.dexinspector.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..6b90ec9 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..4c2cfe1 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..3996622 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..ad13070 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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..9fb2029 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..b44f3aa 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..02cda2a 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..18a1141 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..dfe1806 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..e544edf 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..6650860 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..d7a8af5 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..18f4648 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..e55d822 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..2b49803 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..ce9659b 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..31c97ca 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
@@ -36,12 +36,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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.FieldSubject;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
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
index e3bffc6..f76d402 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
@@ -3,7 +3,7 @@
 // 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 com.android.tools.r8.utils.dexinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
@@ -17,10 +17,10 @@
 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.android.tools.r8.utils.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.io.File;
 import java.nio.file.Path;
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..d752e39 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,16 +4,16 @@
 
 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.dexinspector.Matchers.hasDefaultConstructor;
+import static com.android.tools.r8.utils.dexinspector.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.smali.ConstantFoldingTest.TriConsumer;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
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..e64983d 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,7 +3,7 @@
 // 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.dexinspector.Matchers.isPresent;
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
@@ -11,9 +11,9 @@
 
 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.utils.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.List;
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..ac92146 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
@@ -4,7 +4,7 @@
 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.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.List;
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..1f5ba35 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.dexinspector.Matchers.isNotRenamed;
+import static com.android.tools.r8.utils.dexinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.dexinspector.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.utils.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.FieldSubject;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..0292344 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
@@ -4,7 +4,7 @@
 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.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collection;
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..cd2ce6a 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.dexinspector.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.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.List;
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..aed9596 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.dexinspector.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.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
 import java.util.List;
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..c841996 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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.ArrayList;
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..b1ae8bb 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,18 +4,18 @@
 
 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.dexinspector.Matchers.isAbstract;
+import static com.android.tools.r8.utils.dexinspector.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.utils.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.FieldSubject;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
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..8c71ff7 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
@@ -23,7 +23,7 @@
 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.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.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..365d039 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,17 +7,18 @@
 // 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.dexinspector.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.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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
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..5f9a6fd 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collections;
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..41e894e 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Arrays;
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 5270cd4..c8713e3 100644
--- a/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
@@ -12,8 +12,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.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import org.junit.Test;
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..7287cc3 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
 import org.junit.Test;
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..7aabadb 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Collection;
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..51c6ca5 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.dexinspector.ClassSubject;
+import com.android.tools.r8.utils.dexinspector.DexInspector;
+import com.android.tools.r8.utils.dexinspector.MethodSubject;
 import java.util.ArrayList;
 import java.util.List;
 
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/dexinspector/AbsentAnnotationSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/AbsentAnnotationSubject.java
new file mode 100644
index 0000000..e6d9306
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/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.dexinspector;
+
+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/dexinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/AbsentClassSubject.java
new file mode 100644
index 0000000..7213a54
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/AbsentClassSubject.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.dexinspector;
+
+import com.android.tools.r8.graph.DexClass;
+import java.util.List;
+import java.util.function.Consumer;
+
+public class AbsentClassSubject extends ClassSubject {
+
+  private DexInspector dexInspector;
+
+  public AbsentClassSubject(DexInspector dexInspector) {
+    this.dexInspector = dexInspector;
+  }
+
+  @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/dexinspector/AbsentFieldSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/AbsentFieldSubject.java
new file mode 100644
index 0000000..f79fc00
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/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.dexinspector;
+
+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/dexinspector/AbsentMethodSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/AbsentMethodSubject.java
new file mode 100644
index 0000000..5f10dd7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/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.dexinspector;
+
+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/dexinspector/AnnotationSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/AnnotationSubject.java
new file mode 100644
index 0000000..24682be
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/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.dexinspector;
+
+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/dexinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/ClassSubject.java
new file mode 100644
index 0000000..afb4928
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/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.dexinspector;
+
+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/dexinspector/DexInspector.java b/src/test/java/com/android/tools/r8/utils/dexinspector/DexInspector.java
new file mode 100644
index 0000000..51e4ce2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/DexInspector.java
@@ -0,0 +1,322 @@
+// 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.dexinspector;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.dex.ApplicationReader;
+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.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 DexInspector {
+
+  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 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;
+  }
+
+  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(this);
+    }
+    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 {
+      return dexInst;
+    }
+  }
+
+  InstructionIterator createInstructionIterator(MethodSubject method) {
+    Code code = method.getMethod().getCode();
+    assert code != null && code.isDexCode();
+    return new DexInstructionIterator(this, method);
+  }
+
+  // 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/dexinspector/DexInstructionIterator.java b/src/test/java/com/android/tools/r8/utils/dexinspector/DexInstructionIterator.java
new file mode 100644
index 0000000..eaff010
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/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.dexinspector;
+
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexCode;
+import java.util.NoSuchElementException;
+
+class DexInstructionIterator implements InstructionIterator {
+
+  private DexInspector dexInspector;
+  private final DexCode code;
+  private int index;
+
+  DexInstructionIterator(DexInspector dexInspector, MethodSubject method) {
+    this.dexInspector = dexInspector;
+    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 dexInspector.createInstructionSubject(code.instructions[index++]);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/dexinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/DexInstructionSubject.java
new file mode 100644
index 0000000..b2320d3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/DexInstructionSubject.java
@@ -0,0 +1,177 @@
+// 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.dexinspector;
+
+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;
+
+public class DexInstructionSubject implements InstructionSubject {
+  protected 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 isInvokeDirect() {
+    return instruction instanceof InvokeDirect || instruction instanceof InvokeDirectRange;
+  }
+
+  @Override
+  public boolean isInvokeSuper() {
+    return instruction instanceof InvokeSuper || instruction instanceof InvokeSuperRange;
+  }
+
+  @Override
+  public boolean isInvokeStatic() {
+    return instruction instanceof InvokeStatic || instruction instanceof InvokeStaticRange;
+  }
+
+  @Override
+  public boolean isNop() {
+    return instruction instanceof Nop;
+  }
+
+  @Override
+  public boolean isConstString() {
+    return instruction instanceof ConstString;
+  }
+
+  @Override
+  public boolean isConstString(String value) {
+    return instruction instanceof ConstString
+        && ((ConstString) instruction).BBBB.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;
+  }
+
+  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/dexinspector/FieldAccessDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/FieldAccessDexInstructionSubject.java
new file mode 100644
index 0000000..c3ea3fc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/FieldAccessDexInstructionSubject.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 com.android.tools.r8.utils.dexinspector;
+
+import com.android.tools.r8.code.Instruction;
+
+public class FieldAccessDexInstructionSubject extends DexInstructionSubject
+    implements FieldAccessInstructionSubject {
+
+  private DexInspector dexInspector;
+
+  public FieldAccessDexInstructionSubject(DexInspector dexInspector, Instruction instruction) {
+    super(instruction);
+    this.dexInspector = dexInspector;
+    assert isFieldAccess();
+  }
+
+  public TypeSubject holder() {
+    return new TypeSubject(dexInspector, instruction.getField().getHolder());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/dexinspector/FieldAccessInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/FieldAccessInstructionSubject.java
new file mode 100644
index 0000000..54b27c7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/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.dexinspector;
+
+public interface FieldAccessInstructionSubject extends InstructionSubject {
+  TypeSubject holder();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/dexinspector/FieldSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/FieldSubject.java
new file mode 100644
index 0000000..1e9471b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/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.dexinspector;
+
+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/dexinspector/FilteredInstructionIterator.java b/src/test/java/com/android/tools/r8/utils/dexinspector/FilteredInstructionIterator.java
new file mode 100644
index 0000000..33ac77f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/FilteredInstructionIterator.java
@@ -0,0 +1,52 @@
+// 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.dexinspector;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.function.Predicate;
+
+class FilteredInstructionIterator<T extends InstructionSubject> implements Iterator<T> {
+
+  private DexInspector dexInspector;
+  private final InstructionIterator iterator;
+  private final Predicate<InstructionSubject> predicate;
+  private InstructionSubject pendingNext = null;
+
+  FilteredInstructionIterator(
+      DexInspector dexInspector, MethodSubject method, Predicate<InstructionSubject> predicate) {
+    this.dexInspector = dexInspector;
+    this.iterator = dexInspector.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/dexinspector/FoundAnnotationSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/FoundAnnotationSubject.java
new file mode 100644
index 0000000..ed85b24
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/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.dexinspector;
+
+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/dexinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/FoundClassSubject.java
new file mode 100644
index 0000000..12dbaeb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/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.dexinspector;
+
+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 DexInspector dexInspector;
+  private final DexClass dexClass;
+  final ClassNamingForNameMapper naming;
+
+  FoundClassSubject(DexInspector dexInspector, DexClass dexClass, ClassNamingForNameMapper naming) {
+    this.dexInspector = dexInspector;
+    this.dexClass = dexClass;
+    this.naming = naming;
+  }
+
+  @Override
+  public boolean isPresent() {
+    return true;
+  }
+
+  @Override
+  public void forAllMethods(Consumer<FoundMethodSubject> inspection) {
+    DexInspector.forAll(
+        dexClass.directMethods(),
+        (encoded, clazz) -> new FoundMethodSubject(dexInspector, encoded, clazz),
+        this,
+        inspection);
+    DexInspector.forAll(
+        dexClass.virtualMethods(),
+        (encoded, clazz) -> new FoundMethodSubject(dexInspector, 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] =
+          dexInspector.toDexType(dexInspector.getObfuscatedTypeName(parameters.get(i)));
+    }
+    DexProto proto =
+        dexInspector.dexItemFactory.createProto(
+            dexInspector.toDexType(dexInspector.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 =
+        dexInspector.dexItemFactory.createMethod(
+            dexClass.type, proto, dexInspector.dexItemFactory.createString(name));
+    DexEncodedMethod encoded = findMethod(dexClass.directMethods(), dexMethod);
+    if (encoded == null) {
+      encoded = findMethod(dexClass.virtualMethods(), dexMethod);
+    }
+    return encoded == null
+        ? new AbsentMethodSubject()
+        : new FoundMethodSubject(dexInspector, 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) {
+    DexInspector.forAll(
+        dexClass.staticFields(),
+        (dexField, clazz) -> new FoundFieldSubject(dexInspector, dexField, clazz),
+        this,
+        inspection);
+    DexInspector.forAll(
+        dexClass.instanceFields(),
+        (dexField, clazz) -> new FoundFieldSubject(dexInspector, dexField, clazz),
+        this,
+        inspection);
+  }
+
+  @Override
+  public FieldSubject field(String type, String name) {
+    String obfuscatedType = dexInspector.getObfuscatedTypeName(type);
+    MemberNaming fieldNaming = null;
+    if (naming != null) {
+      fieldNaming = naming.lookupByOriginalSignature(new FieldSignature(name, type));
+    }
+    String obfuscatedName = fieldNaming == null ? name : fieldNaming.getRenamedName();
+
+    DexField field =
+        dexInspector.dexItemFactory.createField(
+            dexClass.type,
+            dexInspector.toDexType(obfuscatedType),
+            dexInspector.dexItemFactory.createString(obfuscatedName));
+    DexEncodedField encoded = findField(dexClass.staticFields(), field);
+    if (encoded == null) {
+      encoded = findField(dexClass.instanceFields(), field);
+    }
+    return encoded == null
+        ? new AbsentFieldSubject()
+        : new FoundFieldSubject(dexInspector, 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 = dexInspector.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 dexInspector.getOriginalSignatureAttribute(
+        dexClass.annotations, GenericSignatureParser::parseClassSignature);
+  }
+
+  @Override
+  public String getFinalSignatureAttribute() {
+    return dexInspector.getFinalSignatureAttribute(dexClass.annotations);
+  }
+
+  @Override
+  public String toString() {
+    return dexClass.toSourceString();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/dexinspector/FoundFieldSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/FoundFieldSubject.java
new file mode 100644
index 0000000..52aa0ce
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/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.dexinspector;
+
+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 DexInspector dexInspector;
+  private final FoundClassSubject clazz;
+  private final DexEncodedField dexField;
+
+  public FoundFieldSubject(
+      DexInspector dexInspector, DexEncodedField dexField, FoundClassSubject clazz) {
+    this.dexInspector = dexInspector;
+    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(dexInspector, 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 = dexInspector.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 dexInspector.getOriginalSignatureAttribute(
+        dexField.annotations, GenericSignatureParser::parseFieldSignature);
+  }
+
+  @Override
+  public String getFinalSignatureAttribute() {
+    return dexInspector.getFinalSignatureAttribute(dexField.annotations);
+  }
+
+  @Override
+  public String toString() {
+    return dexField.toSourceString();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/dexinspector/FoundMethodSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/FoundMethodSubject.java
new file mode 100644
index 0000000..e245258
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/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.dexinspector;
+
+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 DexInspector dexInspector;
+  private final FoundClassSubject clazz;
+  private final DexEncodedMethod dexMethod;
+
+  public FoundMethodSubject(
+      DexInspector dexInspector, DexEncodedMethod encoded, FoundClassSubject clazz) {
+    this.dexInspector = dexInspector;
+    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 = dexInspector.originalToObfuscatedMapping.inverse().get(obfuscated);
+      OriginalParameters[i] = original != null ? original : obfuscated;
+    }
+    String obfuscatedReturnType = signature.type;
+    String originalReturnType =
+        dexInspector.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 dexInspector.getOriginalSignatureAttribute(
+        dexMethod.annotations, GenericSignatureParser::parseMethodSignature);
+  }
+
+  @Override
+  public String getFinalSignatureAttribute() {
+    return dexInspector.getFinalSignatureAttribute(dexMethod.annotations);
+  }
+
+  @Override
+  public Iterator<InstructionSubject> iterateInstructions() {
+    return dexInspector.createInstructionIterator(this);
+  }
+
+  @Override
+  public <T extends InstructionSubject> Iterator<T> iterateInstructions(
+      Predicate<InstructionSubject> filter) {
+    return new FilteredInstructionIterator<>(dexInspector, this, filter);
+  }
+
+  @Override
+  public String toString() {
+    return dexMethod.toSourceString();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/dexinspector/InstructionIterator.java b/src/test/java/com/android/tools/r8/utils/dexinspector/InstructionIterator.java
new file mode 100644
index 0000000..a777319
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/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.dexinspector;
+
+import java.util.Iterator;
+
+interface InstructionIterator extends Iterator<InstructionSubject> {}
diff --git a/src/test/java/com/android/tools/r8/utils/dexinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/InstructionSubject.java
new file mode 100644
index 0000000..00102e5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/InstructionSubject.java
@@ -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 com.android.tools.r8.utils.dexinspector;
+
+public interface InstructionSubject {
+  boolean isFieldAccess();
+
+  boolean isInvokeVirtual();
+
+  boolean isInvokeInterface();
+
+  boolean isInvokeDirect();
+
+  boolean isInvokeSuper();
+
+  boolean isInvokeStatic();
+
+  boolean isNop();
+
+  boolean isConstString();
+
+  boolean isConstString(String value);
+
+  boolean isGoto();
+
+  boolean isIfNez();
+
+  boolean isIfEqz();
+
+  boolean isReturnVoid();
+
+  boolean isThrow();
+
+  default boolean isInvoke() {
+    return isInvokeVirtual()
+        || isInvokeInterface()
+        || isInvokeDirect()
+        || isInvokeSuper()
+        || isInvokeStatic();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/dexinspector/InvokeDexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/InvokeDexInstructionSubject.java
new file mode 100644
index 0000000..a4f5636
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/InvokeDexInstructionSubject.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.dexinspector;
+
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.graph.DexMethod;
+
+public class InvokeDexInstructionSubject extends DexInstructionSubject
+    implements InvokeInstructionSubject {
+
+  private DexInspector dexInspector;
+
+  public InvokeDexInstructionSubject(DexInspector dexInspector, Instruction instruction) {
+    super(instruction);
+    this.dexInspector = dexInspector;
+    assert isInvoke();
+  }
+
+  public TypeSubject holder() {
+    return new TypeSubject(dexInspector, invokedMethod().getHolder());
+  }
+
+  public DexMethod invokedMethod() {
+    return instruction.getMethod();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/dexinspector/InvokeInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/InvokeInstructionSubject.java
new file mode 100644
index 0000000..9b59340
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/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.dexinspector;
+
+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/dexinspector/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/dexinspector/Matchers.java
index 1986bbc..ab3ef13 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/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.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.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/dexinspector/MemberSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/MemberSubject.java
new file mode 100644
index 0000000..1852659
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/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.dexinspector;
+
+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/dexinspector/MethodSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/MethodSubject.java
new file mode 100644
index 0000000..3427924
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/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.dexinspector;
+
+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/dexinspector/Subject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/Subject.java
new file mode 100644
index 0000000..f83eb14
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/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.dexinspector;
+
+public abstract class Subject {
+
+  public abstract boolean isPresent();
+
+  public abstract boolean isRenamed();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/dexinspector/TypeSubject.java b/src/test/java/com/android/tools/r8/utils/dexinspector/TypeSubject.java
new file mode 100644
index 0000000..6a911eb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/dexinspector/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.dexinspector;
+
+import com.android.tools.r8.graph.DexType;
+
+public class TypeSubject extends Subject {
+
+  private DexInspector dexInspector;
+  private final DexType dexType;
+
+  TypeSubject(DexInspector dexInspector, DexType dexType) {
+    this.dexInspector = dexInspector;
+    this.dexType = dexType;
+  }
+
+  @Override
+  public boolean isPresent() {
+    return true;
+  }
+
+  @Override
+  public boolean isRenamed() {
+    return false;
+  }
+
+  public boolean is(String type) {
+    return dexType.equals(dexInspector.toDexType(type));
+  }
+
+  public String toString() {
+    return dexType.toSourceString();
+  }
+}