Merge "Collect newly added SSA values by LensCodeRewriter."
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 6fe8ad0..d28e045 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
@@ -42,10 +43,11 @@
     try {
       DexApplication application =
           new ApplicationReader(app, options, timing).read(executor).toDirect();
-      AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
+      AppView<? extends AppInfoWithSubtyping> appView =
+          new AppView<>(new AppInfoWithSubtyping(application), GraphLense.getIdentityLense());
       RootSet mainDexRootSet =
-          new RootSetBuilder(appInfo, application, options.mainDexKeepRules, options).run(executor);
-      Enqueuer enqueuer = new Enqueuer(appInfo, GraphLense.getIdentityLense(), options, true);
+          new RootSetBuilder(appView, application, options.mainDexKeepRules, options).run(executor);
+      Enqueuer enqueuer = new Enqueuer(appView, options, true);
       AppInfoWithLiveness mainDexAppInfo = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
       // LiveTypes is the result.
       Set<DexType> mainDexClasses =
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index 78cb75a..4c52b53 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -5,9 +5,11 @@
 
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.RootSetBuilder;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.ExceptionUtils;
@@ -76,21 +78,21 @@
   private static void run(
       R8Command command, Set<String> descriptors, InternalOptions options, ExecutorService executor)
       throws IOException {
+    assert !options.forceProguardCompatibility;
     Timing timing = new Timing("PrintSeeds");
     try {
       DexApplication application =
           new ApplicationReader(command.getInputApp(), options, timing).read(executor).toDirect();
-      AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
+      AppView<? extends AppInfoWithSubtyping> appView =
+          new AppView<>(new AppInfoWithSubtyping(application), GraphLense.getIdentityLense());
       RootSet rootSet =
           new RootSetBuilder(
-                  appInfo, application, options.proguardConfiguration.getRules(), options)
+                  appView, application, options.proguardConfiguration.getRules(), options)
               .run(executor);
-      Enqueuer enqueuer = new Enqueuer(appInfo, GraphLense.getIdentityLense(), options, false);
-      appInfo = enqueuer.traceApplication(rootSet, executor, timing);
+      Enqueuer enqueuer = new Enqueuer(appView, options);
+      AppInfoWithLiveness appInfo = enqueuer.traceApplication(rootSet, executor, timing);
       RootSetBuilder.writeSeeds(
-          appInfo.withLiveness(),
-          System.out,
-          type -> descriptors.contains(type.toDescriptorString()));
+          appInfo, System.out, type -> descriptors.contains(type.toDescriptorString()));
     } catch (ExecutionException e) {
       throw R8.unwrapExecutionException(e);
     }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 8be6306..ec863ee 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -275,24 +275,15 @@
         // kotlin metadata annotation is removed.
         computeKotlinInfoForProgramClasses(application, appView.appInfo());
 
-        final ProguardConfiguration.Builder compatibility =
+        ProguardConfiguration.Builder compatibility =
             ProguardConfiguration.builder(application.dexItemFactory, options.reporter);
 
         rootSet =
             new RootSetBuilder(
-                    appView.appInfo(),
-                    application,
-                    options.proguardConfiguration.getRules(),
-                    options)
+                    appView, application, options.proguardConfiguration.getRules(), options)
                 .run(executorService);
 
-        Enqueuer enqueuer =
-            new Enqueuer(
-                appView.appInfo(),
-                appView.graphLense(),
-                options,
-                options.forceProguardCompatibility,
-                compatibility);
+        Enqueuer enqueuer = new Enqueuer(appView, options, compatibility);
         appView.setAppInfo(enqueuer.traceApplication(rootSet, executorService, timing));
         if (options.proguardConfiguration.isPrintSeeds()) {
           ByteArrayOutputStream bytes = new ByteArrayOutputStream();
@@ -416,10 +407,10 @@
 
       if (!options.mainDexKeepRules.isEmpty()) {
         appView.setAppInfo(new AppInfoWithSubtyping(application));
-        Enqueuer enqueuer = new Enqueuer(appView.appInfo(), appView.graphLense(), options, true);
+        Enqueuer enqueuer = new Enqueuer(appView, options, true);
         // Lets find classes which may have code executed before secondary dex files installation.
         RootSet mainDexRootSet =
-            new RootSetBuilder(appView.appInfo(), application, options.mainDexKeepRules, options)
+            new RootSetBuilder(appView, application, options.mainDexKeepRules, options)
                 .run(executorService);
         AppInfoWithLiveness mainDexAppInfo =
             enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
@@ -448,12 +439,7 @@
       if (options.enableTreeShaking || options.enableMinification) {
         timing.begin("Post optimization code stripping");
         try {
-          Enqueuer enqueuer =
-              new Enqueuer(
-                  appView.appInfo(),
-                  appView.graphLense(),
-                  options,
-                  options.forceProguardCompatibility);
+          Enqueuer enqueuer = new Enqueuer(appView, options);
           appView.setAppInfo(enqueuer.traceApplication(rootSet, executorService, timing));
 
           AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index aeaac60..3dbaa56 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.4.1-dev";
+  public static final String LABEL = "1.4.2-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/code/CheckCast.java b/src/main/java/com/android/tools/r8/code/CheckCast.java
index 9416a7b..57151fa 100644
--- a/src/main/java/com/android/tools/r8/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/code/CheckCast.java
@@ -38,6 +38,11 @@
   }
 
   @Override
+  public boolean isCheckCast() {
+    return true;
+  }
+
+  @Override
   public void registerUse(UseRegistry registry) {
     registry.registerCheckCast(getType());
   }
diff --git a/src/main/java/com/android/tools/r8/code/ConstString.java b/src/main/java/com/android/tools/r8/code/ConstString.java
index d165de5..ed7847a 100644
--- a/src/main/java/com/android/tools/r8/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/code/ConstString.java
@@ -45,6 +45,11 @@
   }
 
   @Override
+  public boolean isConstString() {
+    return true;
+  }
+
+  @Override
   public String toString(ClassNameMapper naming) {
     return formatString("v" + AA + ", \"" + BBBB.toString() + "\"");
   }
diff --git a/src/main/java/com/android/tools/r8/code/Instruction.java b/src/main/java/com/android/tools/r8/code/Instruction.java
index c8b5154..fbb93ca 100644
--- a/src/main/java/com/android/tools/r8/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/code/Instruction.java
@@ -122,6 +122,14 @@
     this.offset = offset;
   }
 
+  public boolean isCheckCast() {
+    return false;
+  }
+
+  public boolean isConstString() {
+    return false;
+  }
+
   public boolean isSimpleNop() {
     return !isPayload() && this instanceof Nop;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
index 0c80f96..6b368b3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
@@ -31,7 +31,7 @@
   }
 
   @Override
-  TypeLatticeElement asNullable() {
+  public TypeLatticeElement asNullable() {
     return isNullable() ? this : new ArrayTypeLatticeElement(type, true);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/BottomTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/BottomTypeLatticeElement.java
index b9a45d2..dba0a2a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/BottomTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/BottomTypeLatticeElement.java
@@ -14,7 +14,7 @@
   }
 
   @Override
-  TypeLatticeElement asNullable() {
+  public TypeLatticeElement asNullable() {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
index 72825a9..de75d5f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
@@ -27,7 +27,7 @@
   }
 
   @Override
-  TypeLatticeElement asNullable() {
+  public TypeLatticeElement asNullable() {
     return isNullable() ? this : new ClassTypeLatticeElement(type, true, interfaces);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/NullLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/NullLatticeElement.java
new file mode 100644
index 0000000..caad0a2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/NullLatticeElement.java
@@ -0,0 +1,65 @@
+// 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.analysis.type;
+
+/**
+ * Encodes the following lattice.
+ *
+ * <pre>
+ *          MAYBE NULL
+ *          /        \
+ *   DEFINITELY     DEFINITELY
+ *      NULL         NOT NULL
+ *          \        /
+ *            BOTTOM
+ * </pre>
+ */
+public class NullLatticeElement {
+
+  private static final NullLatticeElement BOTTOM = new NullLatticeElement();
+  private static final NullLatticeElement DEFINITELY_NULL = new NullLatticeElement();
+  private static final NullLatticeElement DEFINITELY_NOT_NULL = new NullLatticeElement();
+  private static final NullLatticeElement MAYBE_NULL = new NullLatticeElement();
+
+  private NullLatticeElement() {}
+
+  public boolean isDefinitelyNull() {
+    return this == DEFINITELY_NULL;
+  }
+
+  public boolean isDefinitelyNotNull() {
+    return this == DEFINITELY_NOT_NULL;
+  }
+
+  public NullLatticeElement leastUpperBound(NullLatticeElement other) {
+    if (this == BOTTOM) {
+      return other;
+    }
+    if (this == other || other == BOTTOM) {
+      return this;
+    }
+    return MAYBE_NULL;
+  }
+
+  public boolean lessThanOrEqual(NullLatticeElement other) {
+    return leastUpperBound(other) == other;
+  }
+
+  static NullLatticeElement bottom() {
+    return BOTTOM;
+  }
+
+  static NullLatticeElement definitelyNull() {
+    return DEFINITELY_NULL;
+  }
+
+  static NullLatticeElement definitelyNotNull() {
+    return DEFINITELY_NOT_NULL;
+  }
+
+  static NullLatticeElement maybeNull() {
+    return MAYBE_NULL;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java
index 7cd6a6a..53c67e4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java
@@ -18,7 +18,7 @@
   }
 
   @Override
-  TypeLatticeElement asNullable() {
+  public TypeLatticeElement asNullable() {
     return TypeLatticeElement.TOP;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
index 387d6c5..ef36c4e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
@@ -49,7 +49,7 @@
   }
 
   @Override
-  TypeLatticeElement asNullable() {
+  public TypeLatticeElement asNullable() {
     assert isNull() || isReferenceInstance();
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TopTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TopTypeLatticeElement.java
index b028963..7aba6dc 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TopTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TopTypeLatticeElement.java
@@ -14,7 +14,7 @@
   }
 
   @Override
-  TypeLatticeElement asNullable() {
+  public TypeLatticeElement asNullable() {
     return this;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
index ed30610..a09039c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
@@ -37,6 +37,7 @@
   public static final ReferenceTypeLatticeElement REFERENCE =
       ReferenceTypeLatticeElement.getReferenceTypeLatticeElement();
 
+  // TODO(b/72693244): Switch to NullLatticeElement.
   private final boolean isNullable;
 
   TypeLatticeElement(boolean isNullable) {
@@ -47,12 +48,22 @@
     return isNullable;
   }
 
+  public NullLatticeElement nullElement() {
+    if (isNull()) {
+      return NullLatticeElement.definitelyNull();
+    }
+    if (!isNullable()) {
+      return NullLatticeElement.definitelyNotNull();
+    }
+    return NullLatticeElement.maybeNull();
+  }
+
   /**
    * Defines how to join with null or switch to nullable lattice element.
    *
    * @return {@link TypeLatticeElement} a result of joining with null.
    */
-  abstract TypeLatticeElement asNullable();
+  public abstract TypeLatticeElement asNullable();
 
   /**
    * Defines how to switch to non-nullable lattice element.
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index ae508e8..a8c9b2f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.ir.code.IRCode.INSTRUCTION_NUMBER_DELTA;
 
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DebugLocalInfo.PrintLevel;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -69,6 +70,11 @@
     return true;
   }
 
+  public boolean verifyTypes(AppInfo appInfo) {
+    assert instructions.stream().allMatch(instruction -> instruction.verifyTypes(appInfo));
+    return true;
+  }
+
   public void setLocalsAtEntry(Int2ReferenceMap<DebugLocalInfo> localsAtEntry) {
     this.localsAtEntry = localsAtEntry;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 4c1824b..8d461bd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -123,6 +123,47 @@
   }
 
   @Override
+  public boolean verifyTypes(AppInfo appInfo) {
+    TypeLatticeElement inType = object().getTypeLattice();
+
+    // TODO(b/72693244): There should never be a value with imprecise type lattice.
+    if (!inType.isPreciseType()) {
+      return true;
+    }
+
+    TypeLatticeElement outType = outValue().getTypeLattice();
+    TypeLatticeElement castType =
+        TypeLatticeElement.fromDexType(getType(), appInfo, inType.isNullable());
+
+    if (TypeLatticeElement.lessThanOrEqual(appInfo, inType, castType)) {
+      // Cast can be removed. Check that it is sound to replace all users of the out-value by the
+      // in-value.
+      assert TypeLatticeElement.lessThanOrEqual(appInfo, inType, outType);
+
+      // TODO(b/72693244): Consider checking equivalence. This requires that the types are always
+      // as precise as possible, though, meaning that almost all changes to the IR must be followed
+      // by a fix-point analysis.
+      // assert outType.equals(inType);
+    } else {
+      // We don't have enough information to remove the cast. Check that the out-value does not
+      // have a more precise type than the cast-type.
+      assert castType.asNullable().equals(outType.asNullable());
+
+      // Check soundness of null information.
+      assert inType.nullElement().lessThanOrEqual(outType.nullElement());
+
+      // Since we cannot remove the cast the in-value must be different from null.
+      assert !inType.isNull();
+
+      // TODO(b/72693244): Consider checking equivalence. This requires that the types are always
+      // as precise as possible, though, meaning that almost all changes to the IR must be followed
+      // by a fix-point analysis.
+      // assert outType.equals(castType);
+    }
+    return true;
+  }
+
+  @Override
   public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
     helper.loadInValues(this, it);
     helper.storeOutValue(this, it);
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index b1e4e28..92e2ddb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
@@ -23,6 +24,7 @@
 import java.util.Map;
 import java.util.Queue;
 import java.util.Set;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 public class IRCode {
@@ -408,6 +410,7 @@
     assert consistentDefUseChains();
     assert validThrowingInstructions();
     assert noCriticalEdges();
+    assert noBottomTypeLatticeLeft();
     return true;
   }
 
@@ -421,6 +424,11 @@
     return true;
   }
 
+  public boolean verifyTypes(AppInfo appInfo) {
+    assert blocks.stream().allMatch(block -> block.verifyTypes(appInfo));
+    return true;
+  }
+
   private boolean noCriticalEdges() {
     for (BasicBlock block : blocks) {
       List<BasicBlock> predecessors = block.getPredecessors();
@@ -606,6 +614,29 @@
     return true;
   }
 
+  private boolean noBottomTypeLatticeLeft() {
+    return verifySSATypeLattice(lattice -> !lattice.isBottom());
+  }
+
+  private boolean noImpreciseTypeLatticeLeft() {
+    return verifySSATypeLattice(TypeLatticeElement::isPreciseType);
+  }
+
+  private boolean verifySSATypeLattice(Predicate<TypeLatticeElement> tester) {
+    for (BasicBlock block : blocks) {
+      for (Instruction instruction : block.getInstructions()) {
+        Value outValue = instruction.outValue();
+        if (outValue != null) {
+          assert tester.test(outValue.getTypeLattice());
+        }
+      }
+      for (Phi phi : block.getPhis()) {
+        assert tester.test(phi.getTypeLattice());
+      }
+    }
+    return true;
+  }
+
   public InstructionIterator instructionIterator() {
     return new IRCodeInstructionsIterator(this);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 8f1a004..d3355b4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -1089,6 +1089,11 @@
         "Implement type lattice evaluation for: " + getInstructionName());
   }
 
+  public boolean verifyTypes(AppInfo appInfo) {
+    // TODO(b/72693244): for instructions with invariant out type, we can verify type directly here.
+    return true;
+  }
+
   /**
    * Indicates whether the instruction throws a NullPointerException if the object denoted by the
    * given value is null at runtime execution.
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 7e5759a..3c657ec 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
@@ -790,6 +790,9 @@
     if (devirtualizer != null) {
       devirtualizer.devirtualizeInvokeInterface(code, method.method.getHolder());
     }
+
+    assert code.verifyTypes(appInfo);
+
     codeRewriter.removeCasts(code);
     codeRewriter.rewriteLongCompareAndRequireNonNull(code, options);
     codeRewriter.commonSubexpressionElimination(code);
@@ -834,6 +837,8 @@
       assert code.isConsistentSSA();
     }
 
+    assert code.verifyTypes(appInfo);
+
     if (classInliner != null) {
       // Class inliner should work before lambda merger, so if it inlines the
       // lambda, it does not get collected by merger.
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 02c06da..f8b85de 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
@@ -1661,25 +1661,29 @@
         TypeLatticeElement outTypeLattice = outValue.getTypeLattice();
         TypeLatticeElement castTypeLattice =
             TypeLatticeElement.fromDexType(castType, appInfo, inTypeLattice.isNullable());
-        // 1) Trivial cast.
-        //   A a = ...
-        //   A a' = (A) a;
-        // 2) Up-cast: we already have finer type info.
-        //   A < B
-        //   A a = ...
-        //   B b = (B) a;
+
+        assert inTypeLattice.nullElement().lessThanOrEqual(outTypeLattice.nullElement());
+
         if (TypeLatticeElement.lessThanOrEqual(appInfo, inTypeLattice, castTypeLattice)) {
-          assert outTypeLattice.equals(inTypeLattice);
+          // 1) Trivial cast.
+          //   A a = ...
+          //   A a' = (A) a;
+          // 2) Up-cast: we already have finer type info.
+          //   A < B
+          //   A a = ...
+          //   B b = (B) a;
+          assert TypeLatticeElement.lessThanOrEqual(appInfo, inTypeLattice, outTypeLattice);
           needToRemoveTrivialPhis = needToRemoveTrivialPhis || outValue.numberOfPhiUsers() != 0;
           removeOrReplaceByDebugLocalWrite(checkCast, it, inValue, outValue);
-          continue;
+        } else {
+          // Otherwise, keep the checkcast to preserve verification errors. E.g., down-cast:
+          // A < B < C
+          // c = ...        // Even though we know c is of type A,
+          // a' = (B) c;    // (this could be removed, since chained below.)
+          // a'' = (A) a';  // this should remain for runtime verification.
+          assert !inTypeLattice.isNull();
+          assert outTypeLattice.asNullable().equals(castTypeLattice.asNullable());
         }
-        // Otherwise, keep the checkcast to preserve verification errors. E.g., down-cast:
-        // A < B < C
-        // c = ...        // Even though we know c is of type A,
-        // a' = (B) c;    // (this could be removed, since chained below.)
-        // a'' = (A) a';  // this should remain for runtime verification.
-        assert outTypeLattice.equals(castTypeLattice);
       }
     }
     // ... v1
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 0ef4597..6999f9a 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
@@ -137,6 +137,7 @@
     // of roots to avoid infinite inlining. Looping makes possible for some roots to
     // become eligible after other roots are inlined.
 
+    boolean anyInlinedMethods = false;
     boolean repeat;
     do {
       repeat = false;
@@ -169,18 +170,24 @@
         }
 
         // Inline the class instance.
-        boolean anyInlinedMethods = processor.processInlining(code, inliner);
+        anyInlinedMethods |= processor.processInlining(code, inliner);
 
         // Restore normality.
         code.removeAllTrivialPhis();
         assert code.isConsistentSSA();
-        if (anyInlinedMethods) {
-          codeRewriter.simplifyIf(code);
-        }
         rootsIterator.remove();
         repeat = true;
       }
     } while (repeat);
+
+    if (anyInlinedMethods) {
+      // If a method was inlined we may be able to remove check-cast instructions because we may
+      // have more information about the types of the arguments at the call site. This is
+      // particularly important for bridge methods.
+      codeRewriter.removeCasts(code);
+      // If a method was inlined we may be able to prune additional branches.
+      codeRewriter.simplifyIf(code);
+    }
   }
 
   private boolean isClassEligible(AppInfo appInfo, DexClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 9145de1..bfb2b95 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo.ResolutionResult;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Descriptor;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexApplication;
@@ -95,7 +96,7 @@
   private boolean tracingMainDex = false;
 
   private final AppInfoWithSubtyping appInfo;
-  private final GraphLense graphLense;
+  private final AppView<? extends AppInfoWithSubtyping> appView;
   private final InternalOptions options;
   private RootSet rootSet;
 
@@ -220,25 +221,34 @@
    */
   private final ProguardConfiguration.Builder compatibility;
 
-  public Enqueuer(
-      AppInfoWithSubtyping appInfo,
-      GraphLense graphLense,
-      InternalOptions options,
-      boolean forceProguardCompatibility) {
-    this(appInfo, graphLense, options, forceProguardCompatibility, null);
+  public Enqueuer(AppView<? extends AppInfoWithSubtyping> appView, InternalOptions options) {
+    this(appView, options, options.forceProguardCompatibility, null);
   }
 
   public Enqueuer(
-      AppInfoWithSubtyping appInfo,
-      GraphLense graphLense,
+      AppView<? extends AppInfoWithSubtyping> appView,
+      InternalOptions options,
+      ProguardConfiguration.Builder compatibility) {
+    this(appView, options, options.forceProguardCompatibility, compatibility);
+  }
+
+  public Enqueuer(
+      AppView<? extends AppInfoWithSubtyping> appView,
+      InternalOptions options,
+      boolean forceProguardCompatibility) {
+    this(appView, options, forceProguardCompatibility, null);
+  }
+
+  public Enqueuer(
+      AppView<? extends AppInfoWithSubtyping> appView,
       InternalOptions options,
       boolean forceProguardCompatibility,
       ProguardConfiguration.Builder compatibility) {
-    this.appInfo = appInfo;
-    this.graphLense = graphLense;
+    this.appInfo = appView.appInfo();
+    this.appView = appView;
     this.compatibility = compatibility;
-    this.options = options;
     this.forceProguardCompatibility = forceProguardCompatibility;
+    this.options = options;
   }
 
   private void enqueueRootItems(Map<DexDefinition, ProguardKeepRule> items) {
@@ -1272,7 +1282,7 @@
         numOfLiveItemsAfterProcessing += (long) liveFields.items.size();
         if (numOfLiveItemsAfterProcessing > numOfLiveItems) {
           RootSetBuilder consequentSetBuilder =
-              new RootSetBuilder(appInfo, rootSet.ifRules, options);
+              new RootSetBuilder(appView, rootSet.ifRules, options);
           ConsequentRootSet consequentRootSet = consequentSetBuilder.runForIfRules(
               executorService, liveTypes, liveMethods.getItems(), liveFields.getItems());
           enqueueRootItems(consequentRootSet.noShrinking);
@@ -1518,7 +1528,7 @@
   private void handleReflectiveBehavior(DexEncodedMethod method) {
     DexType originHolder = method.method.holder;
     Origin origin = appInfo.originFor(originHolder);
-    IRCode code = method.buildIR(appInfo, graphLense, options, origin);
+    IRCode code = method.buildIR(appInfo, appView.graphLense(), options, origin);
     code.instructionIterator().forEachRemaining(this::handleReflectiveBehavior);
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
index 5d610a8..2aff5df 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
@@ -320,11 +320,12 @@
   }
 
   protected StringBuilder append(StringBuilder builder, boolean includeMemberRules) {
-    StringUtils.appendNonEmpty(builder, "@", classAnnotation, null);
-    StringUtils.appendNonEmpty(builder, "", classAccessFlags, null);
-    StringUtils.appendNonEmpty(builder, "!", negatedClassAccessFlags.toString().replace(" ", " !"),
-        null);
-    if (builder.length() > 0) {
+    boolean needsSpaceBeforeClassType =
+        StringUtils.appendNonEmpty(builder, "@", classAnnotation, null)
+            | StringUtils.appendNonEmpty(builder, "", classAccessFlags, null)
+            | StringUtils.appendNonEmpty(
+                builder, "!", negatedClassAccessFlags.toString().replace(" ", " !"), null);
+    if (needsSpaceBeforeClassType) {
       builder.append(' ');
     }
     if (classTypeNegated) {
@@ -339,12 +340,12 @@
       builder.append(' ');
       builder.append(inheritanceClassName);
     }
-    if (includeMemberRules) {
-      builder.append(" {\n");
+    if (includeMemberRules && !memberRules.isEmpty()) {
+      builder.append(" {").append(System.lineSeparator());
       memberRules.forEach(memberRule -> {
         builder.append("  ");
         builder.append(memberRule);
-        builder.append(";\n");
+        builder.append(";").append(System.lineSeparator());
       });
       builder.append("}");
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 30def9e..e6f6a17 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
@@ -51,7 +52,7 @@
 
 public class RootSetBuilder {
 
-  private final AppInfo appInfo;
+  private final AppView<? extends AppInfo> appView;
   private final DirectMappedDexApplication application;
   private final Collection<ProguardConfigurationRule> rules;
   private final Map<DexDefinition, ProguardKeepRule> noShrinking = new IdentityHashMap<>();
@@ -76,22 +77,20 @@
   private final Set<ProguardIfRule> ifRules = Sets.newIdentityHashSet();
 
   public RootSetBuilder(
-      AppInfo appInfo,
+      AppView<? extends AppInfo> appView,
       DexApplication application,
       List<ProguardConfigurationRule> rules,
       InternalOptions options) {
-    this.appInfo = appInfo;
+    this.appView = appView;
     this.application = application.asDirect();
     this.rules = rules == null ? null : Collections.unmodifiableCollection(rules);
     this.options = options;
   }
 
   RootSetBuilder(
-      AppInfo appInfo,
-      Set<ProguardIfRule> ifRules,
-      InternalOptions options) {
-    this.appInfo = appInfo;
-    this.application = appInfo.app.asDirect();
+      AppView<? extends AppInfo> appView, Set<ProguardIfRule> ifRules, InternalOptions options) {
+    this.appView = appView;
+    this.application = appView.appInfo().app.asDirect();
     this.rules = Collections.unmodifiableCollection(ifRules);
     this.options = options;
   }
@@ -341,13 +340,14 @@
       Set<DexEncodedMethod> liveMethods,
       Set<DexEncodedField> liveFields) throws ExecutionException {
     application.timing.begin("Find consequent items for -if rules...");
-    Function<DexType, DexClass> definitionForWithLiveTypes = type -> {
-      DexClass clazz = appInfo.definitionFor(type);
-      if (clazz != null && liveTypes.contains(clazz.type)) {
-        return clazz;
-      }
-      return null;
-    };
+    Function<DexType, DexClass> definitionForWithLiveTypes =
+        type -> {
+          DexClass clazz = appView.appInfo().definitionFor(type);
+          if (clazz != null && liveTypes.contains(clazz.type)) {
+            return clazz;
+          }
+          return null;
+        };
     try {
       List<Future<?>> futures = new ArrayList<>();
       if (rules != null) {
@@ -358,7 +358,7 @@
           // -keep rule may vary (due to back references). So, we need to try all pairs of -if rule
           // and live types.
           for (DexType currentLiveType : liveTypes) {
-            DexClass currentLiveClass = appInfo.definitionFor(currentLiveType);
+            DexClass currentLiveClass = appView.appInfo().definitionFor(currentLiveType);
             if (currentLiveClass == null) {
               continue;
             }
@@ -550,7 +550,7 @@
     out.close();
   }
 
-  private static boolean satisfyClassType(ProguardConfigurationRule rule, DexClass clazz) {
+  private boolean satisfyClassType(ProguardConfigurationRule rule, DexClass clazz) {
     return rule.getClassType().matches(clazz) != rule.getClassTypeNegated();
   }
 
@@ -723,7 +723,7 @@
     if (type.isPrimitiveType()) {
       return;
     }
-    DexClass definition = appInfo.definitionFor(type);
+    DexClass definition = appView.appInfo().definitionFor(type);
     if (definition == null || definition.isLibraryClass()) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index 4f0aa2c..244639e 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -59,9 +59,10 @@
     return builder.toString();
   }
 
-  public static void appendNonEmpty(StringBuilder builder, String pre, Object item, String post) {
+  public static boolean appendNonEmpty(
+      StringBuilder builder, String pre, Object item, String post) {
     if (item == null) {
-      return;
+      return false;
     }
     String text = item.toString();
     if (!text.isEmpty()) {
@@ -72,7 +73,9 @@
       if (post != null) {
         builder.append(post);
       }
+      return true;
     }
+    return false;
   }
 
   public static StringBuilder appendIndent(StringBuilder builder, String subject, int indent) {
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index fa35d0d..6f93df4 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -16,7 +16,6 @@
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.CfCode;
-import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.SmaliWriter;
@@ -711,7 +710,7 @@
 
   protected ProcessResult runOnVMRaw(AndroidApp app, Class<?> mainClass, Backend backend)
       throws IOException {
-    return runOnVMRaw(app, mainClass.getCanonicalName(), backend);
+    return runOnVMRaw(app, mainClass.getTypeName(), backend);
   }
 
   protected ProcessResult runOnVMRaw(AndroidApp app, String mainClass, Backend backend)
@@ -830,13 +829,6 @@
         .map(kind::cast);
   }
 
-  protected Stream<CfInstruction> filterInstructionKind(
-      CfCode code, Class<? extends CfInstruction> kind) {
-    return code.getInstructions().stream()
-        .filter(kind::isInstance)
-        .map(kind::cast);
-  }
-
   public enum MinifyMode {
     NONE,
     JAVA,
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index f5eaa25..481c50c 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -949,7 +949,7 @@
   }
 
   public static ProcessResult runJava(Class clazz) throws Exception {
-    String main = clazz.getCanonicalName();
+    String main = clazz.getTypeName();
     Path path = getClassPathForTests();
     return runJava(path, main);
   }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
index 44720ed..1eddbd3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
@@ -13,19 +13,18 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.debug.CfDebugTestConfig;
 import com.android.tools.r8.debug.DebugTestBase;
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.debug.DexDebugTestConfig;
-import com.android.tools.r8.graph.Code;
-import com.android.tools.r8.code.CheckCast;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Streams;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
@@ -82,7 +81,8 @@
     ClassSubject classSubject = inspector.clazz(MAIN);
     MethodSubject method = classSubject.method("void", "differentLocals", ImmutableList.of());
     assertThat(method, isPresent());
-    long count = countCheckCast(method.getMethod().getCode());
+    long count =
+        Streams.stream(method.iterateInstructions(InstructionSubject::isCheckCast)).count();
     assertEquals(1, count);
 
     DebugTestConfig config = backend == Backend.CF
@@ -134,7 +134,8 @@
     ClassSubject classSubject = inspector.clazz(MAIN);
     MethodSubject method = classSubject.method("void", "sameLocal", ImmutableList.of());
     assertThat(method, isPresent());
-    long count = countCheckCast(method.getMethod().getCode());
+    long count =
+        Streams.stream(method.iterateInstructions(InstructionSubject::isCheckCast)).count();
     assertEquals(1, count);
 
     DebugTestConfig config = backend == Backend.CF
@@ -166,13 +167,4 @@
     );
   }
 
-  private long countCheckCast(Code code) {
-    if (backend == Backend.DEX) {
-      return filterInstructionKind(code.asDexCode(), CheckCast.class).count();
-    } else {
-      assert backend == Backend.CF;
-      return filterInstructionKind(code.asCfCode(), CfCheckCast.class).count();
-    }
-  }
-
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/RemoveCheckCastAfterClassInlining.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/RemoveCheckCastAfterClassInlining.java
new file mode 100644
index 0000000..d52725e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/RemoveCheckCastAfterClassInlining.java
@@ -0,0 +1,79 @@
+// 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.checkcast;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class RemoveCheckCastAfterClassInlining extends TestBase {
+
+  @Test
+  public void test() throws Exception {
+    AndroidApp input = readClasses(Lambda.class, Lambda.Consumer.class);
+    AndroidApp output =
+        compileWithR8(
+            input,
+            keepMainProguardConfiguration(Lambda.class),
+            options -> options.enableMinification = false);
+
+    // Extract main method.
+    CodeInspector inspector = new CodeInspector(output);
+    ClassSubject classSubject = inspector.clazz(Lambda.class);
+    MethodSubject methodSubject = classSubject.mainMethod();
+    assertThat(methodSubject, isPresent());
+
+    DexEncodedMethod method = methodSubject.getMethod();
+    assertTrue(method.hasCode());
+
+    DexCode code = method.getCode().asDexCode();
+    int numberOfConstStringInstructions = 0;
+    for (Instruction instruction : code.instructions) {
+      // Make sure that we do not load a const-string and then subsequently use a check-cast
+      // instruction to check if it is actually a string.
+      assertFalse(instruction.isCheckCast());
+      if (instruction.isConstString()) {
+        numberOfConstStringInstructions++;
+      }
+    }
+
+    // Sanity check that load() was actually inlined.
+    assertThat(
+        classSubject.method("void", "load", ImmutableList.of(Lambda.Consumer.class.getName())),
+        not(isPresent()));
+    assertEquals(2, numberOfConstStringInstructions);
+  }
+}
+
+class Lambda {
+
+  interface Consumer<T> {
+    void accept(T value);
+  }
+
+  public static void main(String... args) {
+    load(s -> System.out.println(s));
+    // Other code…
+    load(s -> System.out.println(s));
+  }
+
+  public static void load(Consumer<String> c) {
+    c.accept("Hello!");
+  }
+}
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 67afdd7..6b7a7c2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClassInlinerTest.java
@@ -10,7 +10,6 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.code.InvokeStatic;
 import com.android.tools.r8.code.NewInstance;
 import com.android.tools.r8.code.SgetObject;
 import com.android.tools.r8.graph.DexClass;
@@ -19,9 +18,12 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
 import java.util.Collection;
 import java.util.List;
 import java.util.Set;
@@ -229,8 +231,8 @@
   private List<String> collectStaticCalls(ClassSubject clazz, String methodName, String... params) {
     assertNotNull(clazz);
     MethodSignature signature = new MethodSignature(methodName, "void", params);
-    DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
-    return filterInstructionKind(code, InvokeStatic.class)
+    MethodSubject method = clazz.method(signature);
+    return Streams.stream(method.iterateInstructions(InstructionSubject::isInvokeStatic))
         .map(insn -> insn.getMethod().toSourceString())
         .sorted()
         .collect(Collectors.toList());
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java b/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java
new file mode 100644
index 0000000..b280242
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/IndirectSuperInterfaceTest.java
@@ -0,0 +1,95 @@
+// 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.memberrebinding;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IndirectSuperInterfaceTest extends TestBase {
+
+  public interface Interface {
+    @NeverInline
+    default void foo() {
+      System.out.print("Interface::foo ");
+    }
+  }
+
+  public static class A implements Interface {
+    // Intentionally empty.
+  }
+
+  public static class B extends A {
+    @Override
+    public void foo() {
+      System.out.print("B::foo ");
+      super.foo();
+    }
+
+    public static void main(String[] args) {
+      new B().foo();
+    }
+  }
+
+  private final Backend backend;
+
+  @Parameters(name = "{0}")
+  public static Backend[] setup() {
+    return new Backend[] {Backend.CF, Backend.DEX};
+  }
+
+  public IndirectSuperInterfaceTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void test() throws Exception {
+    String expected = "B::foo Interface::foo ";
+    String reference = runOnJava(B.class);
+    assertEquals(expected, reference);
+
+    AndroidAppConsumers sink = new AndroidAppConsumers();
+    Builder builder =
+        R8Command.builder()
+            .addClassProgramData(ToolHelper.getClassAsBytes(Interface.class), Origin.unknown())
+            .addClassProgramData(ToolHelper.getClassAsBytes(A.class), Origin.unknown())
+            .addClassProgramData(ToolHelper.getClassAsBytes(B.class), Origin.unknown())
+            .setProgramConsumer(sink.wrapProgramConsumer(emptyConsumer(backend)))
+            .addLibraryFiles(runtimeJar(backend))
+            .addProguardConfiguration(
+                ImmutableList.of(
+                    "-keep class " + Interface.class.getTypeName(),
+                    "-keep class " + A.class.getTypeName(),
+                    keepMainProguardConfigurationWithInliningAnnotation(B.class)),
+                Origin.unknown());
+    ToolHelper.allowTestProguardOptions(builder);
+    if (backend == Backend.DEX) {
+      builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
+    }
+    R8.run(builder.build());
+
+    ProcessResult result = runOnVMRaw(sink.build(), B.class, backend);
+
+    // TODO(b/117407667): Assert the test does not fail once fixed.
+    assertTrue(result.toString(), result.exitCode == (backend == Backend.DEX ? 0 : 1));
+    if (result.exitCode == 0) {
+      assertEquals(reference, result.stdout);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index 932c09b..e340c98 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -75,20 +75,17 @@
 
     ExecutorService executor = ThreadUtils.getExecutorService(1);
 
-    AppInfoWithSubtyping appInfo = appView.appInfo();
-    RootSet rootSet = new RootSetBuilder(appInfo, program, configuration.getRules(), options)
-        .run(executor);
+    RootSet rootSet =
+        new RootSetBuilder(appView, program, configuration.getRules(), options).run(executor);
 
     if (options.proguardConfiguration.isAccessModificationAllowed()) {
       ClassAndMemberPublicizer.run(executor, timing, program, appView, rootSet);
       rootSet =
-          new RootSetBuilder(appInfo, program, configuration.getRules(), options).run(executor);
+          new RootSetBuilder(appView, program, configuration.getRules(), options).run(executor);
     }
 
-    Enqueuer enqueuer =
-        new Enqueuer(
-            appInfo, GraphLense.getIdentityLense(), options, options.forceProguardCompatibility);
-    appInfo = enqueuer.traceApplication(rootSet, executor, timing);
+    Enqueuer enqueuer = new Enqueuer(appView, options, options.forceProguardCompatibility);
+    AppInfoWithSubtyping appInfo = enqueuer.traceApplication(rootSet, executor, timing);
     return new Minifier(appInfo.withLiveness(), rootSet, Collections.emptySet(), options)
         .run(timing);
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index fbf4e68..deb91d8 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.AsmTestBase;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -102,18 +103,18 @@
     InternalOptions options = new InternalOptions();
     AndroidApp app = readClassesAndAsmDump(CLASSES, ASM_CLASSES);
     DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
-    AppInfoWithSubtyping appInfoWithSubtyping = new AppInfoWithSubtyping(application);
+    AppView<? extends AppInfoWithSubtyping> appView =
+        new AppView<>(new AppInfoWithSubtyping(application), GraphLense.getIdentityLense());
 
     ExecutorService executor = Executors.newSingleThreadExecutor();
-    RootSet rootSet = new RootSetBuilder(appInfoWithSubtyping, application,
-        buildKeepRuleForClass(Main.class, application.dexItemFactory), options).run(executor);
-    appInfo =
-        new Enqueuer(
-                appInfoWithSubtyping,
-                GraphLense.getIdentityLense(),
-                options,
-                options.forceProguardCompatibility)
-            .traceApplication(rootSet, executor, timing);
+    RootSet rootSet =
+        new RootSetBuilder(
+                appView,
+                application,
+                buildKeepRuleForClass(Main.class, application.dexItemFactory),
+                options)
+            .run(executor);
+    appInfo = new Enqueuer(appView, options).traceApplication(rootSet, executor, timing);
     // We do not run the tree pruner to ensure that the hierarchy is as designed and not modified
     // due to liveness.
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
index 24986e1..44527a2 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatibilityTestBase.java
@@ -63,6 +63,14 @@
         || shrinker == Shrinker.R8_CF;
   }
 
+  protected static Backend toBackend(Shrinker shrinker) {
+    if (generatesDex(shrinker)) {
+      return Backend.DEX;
+    }
+    assert generatesCf(shrinker);
+    return Backend.CF;
+  }
+
   protected AndroidApp runShrinker(
       Shrinker mode, List<Class> programClasses, Iterable<String> proguardConfigs)
       throws Exception {
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
new file mode 100644
index 0000000..fe2a93f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ExternalizableTest.java
@@ -0,0 +1,345 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.forceproguardcompatibility.defaultctor;
+
+import static com.android.tools.r8.shaking.forceproguardcompatibility.defaultctor.ExternalizableDataClass.TYPE_1;
+import static com.android.tools.r8.shaking.forceproguardcompatibility.defaultctor.ExternalizableDataClass.TYPE_2;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.InvalidClassException;
+import java.io.InvalidObjectException;
+import java.io.ObjectInput;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+final class ExternalizableDataClass implements Externalizable {
+  static final byte TYPE_1 = 1;
+  static final byte TYPE_2 = 2;
+
+  private byte byteField;
+  private Object objectField;
+
+  // Default constructor for deserialization
+  public ExternalizableDataClass() {
+  }
+
+  // Constructor for serialization
+  public ExternalizableDataClass(byte byteField, Object objectField) {
+    this.byteField = byteField;
+    this.objectField = objectField;
+  }
+
+  @Override
+  public void writeExternal(ObjectOutput objectOutput) throws IOException {
+    writeInternal(byteField, objectField, objectOutput);
+  }
+
+  private static void writeInternal(byte b, Object obj, DataOutput out) throws IOException {
+    out.writeByte(b);
+    switch (b) {
+      case TYPE_1:
+        ((Delegate1) obj).delegateWrite(out);
+        break;
+      case TYPE_2:
+        ((Delegate2) obj).delegateWrite(out);
+        break;
+      default:
+        throw new InvalidClassException("Unknown type: " + b);
+    }
+  }
+
+  @Override
+  public void readExternal(ObjectInput objectInput) throws IOException {
+    byteField = objectInput.readByte();
+    objectField = readInternal(byteField, objectInput);
+  }
+
+  private static Object readInternal(byte type, DataInput in) throws IOException {
+    switch (type) {
+      case TYPE_1: return Delegate1.delegateRead(in);
+      case TYPE_2: return Delegate2.delegateRead(in);
+      default:
+        throw new InvalidClassException("Unknown type: " + type);
+    }
+  }
+
+  private Object readResolve() {
+    return objectField;
+  }
+
+  @Override
+  public String toString() {
+    return "{ type: " + byteField + ", obj: " + objectField.toString() + " }";
+  }
+}
+
+final class Delegate1 implements Serializable {
+  private int intField;
+
+  private Object writeReplace() {
+    return new ExternalizableDataClass(TYPE_1, this);
+  }
+
+  static Delegate1 of(int intField) {
+    Delegate1 instance = new Delegate1();
+    instance.intField = intField;
+    return instance;
+  }
+
+  void delegateWrite(DataOutput out) throws IOException {
+    out.writeInt(intField);
+  }
+
+  static Object delegateRead(DataInput in) throws IOException {
+    int i = in.readInt();
+    return Delegate1.of(i);
+  }
+
+  private Object readResolve() throws ObjectStreamException {
+    throw new InvalidObjectException("Deserialization via serialization delegate");
+  }
+
+  @Override
+  public String toString() {
+    return "(" + intField + ")";
+  }
+}
+
+final class Delegate2 implements Serializable {
+  private String stringField;
+
+  private Object writeReplace() {
+    return new ExternalizableDataClass(TYPE_2, this);
+  }
+
+  static Delegate2 of(String stringField) {
+    Delegate2 instance = new Delegate2();
+    instance.stringField = stringField;
+    return instance;
+  }
+
+  void delegateWrite(DataOutput out) throws IOException {
+    out.writeUTF(stringField);
+  }
+
+  static Object delegateRead(DataInput in) throws IOException {
+    String s = in.readUTF();
+    return Delegate2.of(s);
+  }
+
+  private Object readResolve() throws ObjectStreamException {
+    throw new InvalidObjectException("Deserialization via serialization delegate");
+  }
+
+  @Override
+  public String toString() {
+    return "<" + stringField + ">";
+  }
+}
+
+class ExternalizableTestMain {
+  public static void main(String[] args) throws Exception {
+    Delegate2 data2 = Delegate2.of("MessageToSerialize");
+    // "Before: <MessageToSerialize>"
+    System.out.println("Before: " + data2.toString());
+
+    // Serialization
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
+    objectOutputStream.writeObject(data2);
+    objectOutputStream.close();
+
+    byte[] byteArray = out.toByteArray();
+
+    // Deserialization
+    ByteArrayInputStream in = new ByteArrayInputStream(byteArray);
+    ObjectInputStream objectInputStream = new ObjectInputStream(in);
+    Object copy = objectInputStream.readObject();
+    assert copy instanceof Delegate2;
+    // "After: <MessageToSerialize>"
+    System.out.println("After: " + copy.toString());
+  }
+}
+
+class NonSerializableSuperClass {
+  protected String tag;
+
+  // Default constructor for deserialization
+  public NonSerializableSuperClass() {
+    this.tag = null;
+  }
+
+  public NonSerializableSuperClass(String tag) {
+    this.tag = tag;
+  }
+
+  @Override
+  public String toString() {
+    return tag == null ? "NULL" : tag;
+  }
+}
+
+class SerializableDataClass extends NonSerializableSuperClass implements Serializable {
+  private String extraTag;
+
+  public SerializableDataClass() {
+    super();
+    this.extraTag = null;
+  }
+
+  public SerializableDataClass(String tag, String extraTag) {
+    super(tag);
+    this.extraTag = extraTag;
+  }
+
+  @Override
+  public String toString() {
+    return super.toString() + ", " + (extraTag == null ? "NULL" : extraTag);
+  }
+}
+
+class SerializableTestMain {
+  public static void main(String[] args) throws Exception {
+    SerializableDataClass data = new SerializableDataClass("TagToSerialize", "ExtraToSerialize");
+    // "Before: TagToSerialize, ExtraToSerialize"
+    System.out.println("Before: " + data.toString());
+
+    // Serialization
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
+    objectOutputStream.writeObject(data);
+    objectOutputStream.close();
+
+    byte[] byteArray = out.toByteArray();
+
+    // Deserialization
+    ByteArrayInputStream in = new ByteArrayInputStream(byteArray);
+    ObjectInputStream objectInputStream = new ObjectInputStream(in);
+    Object copy = objectInputStream.readObject();
+    assert copy instanceof SerializableDataClass;
+    // "After: NULL, ExtraToSerialize"
+    System.out.println("After: " + copy.toString());
+  }
+}
+
+@RunWith(Parameterized.class)
+public class ExternalizableTest extends ProguardCompatibilityTestBase {
+  private final static List<Class> CLASSES_FOR_EXTERNALIZABLE = ImmutableList.of(
+      ExternalizableDataClass.class, Delegate1.class, Delegate2.class, ExternalizableTestMain.class
+  );
+
+  private final static List<Class> CLASSES_FOR_SERIALIZABLE = ImmutableList.of(
+      NonSerializableSuperClass.class, SerializableDataClass.class, SerializableTestMain.class
+  );
+
+  private final Shrinker shrinker;
+
+  @Parameterized.Parameters(name = "Shrinker: {0}")
+  public static Collection<Object> data() {
+    return ImmutableList.of(
+        Shrinker.PROGUARD6_THEN_D8, Shrinker.PROGUARD6, Shrinker.R8, Shrinker.R8_CF);
+  }
+
+  public ExternalizableTest(Shrinker shrinker) {
+    this.shrinker = shrinker;
+  }
+
+  @Test
+  public void testExternalizable() throws Exception {
+    // TODO(b/116735204): R8 should keep default ctor() of classes that implement Externalizable
+    if (isR8(shrinker)) {
+      return;
+    }
+
+    String javaOutput = runOnJava(ExternalizableTestMain.class);
+
+    List<String> config = ImmutableList.of(
+        keepMainProguardConfiguration(ExternalizableTestMain.class),
+        // https://www.guardsquare.com/en/products/proguard/manual/examples#serializable
+        "-keepclassmembers class * implements java.io.Serializable {",
+        //"  private static final java.io.ObjectStreamField[] serialPersistentFields;",
+        "  private void writeObject(java.io.ObjectOutputStream);",
+        "  private void readObject(java.io.ObjectInputStream);",
+        "  java.lang.Object writeReplace();",
+        "  java.lang.Object readResolve();",
+        "}");
+
+    AndroidApp processedApp = runShrinker(shrinker, CLASSES_FOR_EXTERNALIZABLE, config);
+
+    // TODO(b/117302947): Need to update ART binary.
+    if (generatesCf(shrinker)) {
+      String output = runOnVM(
+          processedApp, ExternalizableTestMain.class.getCanonicalName(), toBackend(shrinker));
+      assertEquals(javaOutput.trim(), output.trim());
+    }
+
+    CodeInspector codeInspector = new CodeInspector(processedApp, proguardMap);
+    ClassSubject classSubject = codeInspector.clazz(ExternalizableDataClass.class);
+    assertThat(classSubject, isPresent());
+    MethodSubject init = classSubject.init(ImmutableList.of());
+    assertThat(init, isPresent());
+  }
+
+  @Test
+  public void testSerializable() throws Exception {
+    // TODO(b/116735204): R8 should keep default ctor() of first non-serializable superclass of
+    // serializable class.
+    if (isR8(shrinker)) {
+      return;
+    }
+
+    String javaOutput = runOnJava(SerializableTestMain.class);
+
+    List<String> config = ImmutableList.of(
+        keepMainProguardConfiguration(SerializableTestMain.class),
+        // https://www.guardsquare.com/en/products/proguard/manual/examples#serializable
+        "-keepclassmembers class * implements java.io.Serializable {",
+        //"  private static final java.io.ObjectStreamField[] serialPersistentFields;",
+        "  private void writeObject(java.io.ObjectOutputStream);",
+        "  private void readObject(java.io.ObjectInputStream);",
+        "  java.lang.Object writeReplace();",
+        "  java.lang.Object readResolve();",
+        "}");
+
+    AndroidApp processedApp = runShrinker(shrinker, CLASSES_FOR_SERIALIZABLE, config);
+    // TODO(b/117302947): Need to update ART binary.
+    if (generatesCf(shrinker)) {
+      String output = runOnVM(
+          processedApp, SerializableTestMain.class.getCanonicalName(), toBackend(shrinker));
+      assertEquals(javaOutput.trim(), output.trim());
+    }
+
+    CodeInspector codeInspector = new CodeInspector(processedApp, proguardMap);
+    ClassSubject classSubject = codeInspector.clazz(NonSerializableSuperClass.class);
+    assertThat(classSubject, isPresent());
+    MethodSubject init = classSubject.init(ImmutableList.of());
+    assertThat(init, isPresent());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
index 1a74018..1f06e72 100644
--- a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
@@ -33,7 +33,7 @@
     String expected = String.join(System.lineSeparator(), ImmutableList.of(
         "com.android.tools.r8.shaking.whyareyoukeeping.A",
         "|- is live because referenced in keep rule:",
-        "|    -keep  class com.android.tools.r8.shaking.whyareyoukeeping.A {",
+        "|    -keep class com.android.tools.r8.shaking.whyareyoukeeping.A {",
         "|      *;",
         "|    };",
         ""));
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 3203f9c..dedb839 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -24,6 +24,8 @@
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.cf.code.CfSwitch;
 import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.code.ValueType;
 import org.objectweb.asm.Opcodes;
 
@@ -40,6 +42,41 @@
   }
 
   @Override
+  public boolean isInstancePut() {
+    return instruction instanceof CfFieldInstruction
+        && ((CfFieldInstruction) instruction).getOpcode() == Opcodes.PUTFIELD;
+  }
+
+  @Override
+  public boolean isStaticPut() {
+    return instruction instanceof CfFieldInstruction
+        && ((CfFieldInstruction) instruction).getOpcode() == Opcodes.PUTSTATIC;
+  }
+
+  @Override
+  public boolean isInstanceGet() {
+    return instruction instanceof CfFieldInstruction
+        && ((CfFieldInstruction) instruction).getOpcode() == Opcodes.GETFIELD;
+  }
+
+  @Override
+  public boolean isStaticGet() {
+    return instruction instanceof CfFieldInstruction
+        && ((CfFieldInstruction) instruction).getOpcode() == Opcodes.GETSTATIC;
+  }
+
+  @Override
+  public DexField getField() {
+    assert isFieldAccess();
+    return ((CfFieldInstruction) instruction).getField();
+  }
+
+  @Override
+  public boolean isInvoke() {
+    return instruction instanceof CfInvoke || instruction instanceof CfInvokeDynamic;
+  }
+
+  @Override
   public boolean isInvokeVirtual() {
     return instruction instanceof CfInvoke
         && ((CfInvoke) instruction).getOpcode() == Opcodes.INVOKEVIRTUAL;
@@ -58,6 +95,12 @@
   }
 
   @Override
+  public DexMethod getMethod() {
+    assert isInvoke();
+    return ((CfInvoke) instruction).getMethod();
+  }
+
+  @Override
   public boolean isNop() {
     return instruction instanceof CfNop;
   }
@@ -105,40 +148,11 @@
   }
 
   @Override
-  public boolean isInvoke() {
-    return instruction instanceof CfInvoke || instruction instanceof CfInvokeDynamic;
-  }
-
-  @Override
   public boolean isNewInstance() {
     return instruction instanceof CfNew;
   }
 
   @Override
-  public boolean isInstancePut() {
-    return instruction instanceof CfFieldInstruction
-        && ((CfFieldInstruction) instruction).getOpcode() == Opcodes.PUTFIELD;
-  }
-
-  @Override
-  public boolean isStaticPut() {
-    return instruction instanceof CfFieldInstruction
-        && ((CfFieldInstruction) instruction).getOpcode() == Opcodes.PUTSTATIC;
-  }
-
-  @Override
-  public boolean isInstanceGet() {
-    return instruction instanceof CfFieldInstruction
-        && ((CfFieldInstruction) instruction).getOpcode() == Opcodes.GETFIELD;
-  }
-
-  @Override
-  public boolean isStaticGet() {
-    return instruction instanceof CfFieldInstruction
-        && ((CfFieldInstruction) instruction).getOpcode() == Opcodes.GETSTATIC;
-  }
-
-  @Override
   public boolean isCheckCast() {
     return instruction instanceof CfCheckCast;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 4ea699a..e1d74c1 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -67,6 +67,8 @@
 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.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
 
 public class DexInstructionSubject implements InstructionSubject {
   protected final Instruction instruction;
@@ -81,6 +83,65 @@
   }
 
   @Override
+  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;
+  }
+
+  @Override
+  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;
+  }
+
+  @Override
+  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;
+  }
+
+  @Override
+  public boolean isStaticPut() {
+    return instruction instanceof Sput
+        || instruction instanceof SputBoolean
+        || instruction instanceof SputByte
+        || instruction instanceof SputShort
+        || instruction instanceof SputChar
+        || instruction instanceof SputWide
+        || instruction instanceof SputObject;
+  }
+
+  @Override
+  public DexField getField() {
+    assert isFieldAccess();
+    return instruction.getField();
+  }
+
+  @Override
+  public boolean isInvoke() {
+    return isInvokeVirtual()
+        || isInvokeInterface()
+        || isInvokeDirect()
+        || isInvokeSuper()
+        || isInvokeStatic();
+  }
+
+  @Override
   public boolean isInvokeVirtual() {
     return instruction instanceof InvokeVirtual || instruction instanceof InvokeVirtualRange;
   }
@@ -95,6 +156,20 @@
     return instruction instanceof InvokeStatic || instruction instanceof InvokeStaticRange;
   }
 
+  public boolean isInvokeSuper() {
+    return instruction instanceof InvokeSuper || instruction instanceof InvokeSuperRange;
+  }
+
+  public boolean isInvokeDirect() {
+    return instruction instanceof InvokeDirect || instruction instanceof InvokeDirectRange;
+  }
+
+  @Override
+  public DexMethod getMethod() {
+    assert isInvoke();
+    return instruction.getMethod();
+  }
+
   @Override
   public boolean isNop() {
     return instruction instanceof Nop;
@@ -147,15 +222,6 @@
   }
 
   @Override
-  public boolean isInvoke() {
-    return isInvokeVirtual()
-        || isInvokeInterface()
-        || isInvokeDirect()
-        || isInvokeSuper()
-        || isInvokeStatic();
-  }
-
-  @Override
   public boolean isNewInstance() {
     return instruction instanceof NewInstance;
   }
@@ -170,63 +236,11 @@
     return isCheckCast() && ((CheckCast) instruction).getType().toString().equals(type);
   }
 
-  public boolean isInvokeSuper() {
-    return instruction instanceof InvokeSuper || instruction instanceof InvokeSuperRange;
-  }
-
-  public boolean isInvokeDirect() {
-    return instruction instanceof InvokeDirect || instruction instanceof InvokeDirectRange;
-  }
-
   public boolean isConst4() {
     return instruction instanceof Const4;
   }
 
   @Override
-  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;
-  }
-
-  @Override
-  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;
-  }
-
-  @Override
-  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;
-  }
-
-  @Override
-  public boolean isStaticPut() {
-    return instruction instanceof Sput
-        || instruction instanceof SputBoolean
-        || instruction instanceof SputByte
-        || instruction instanceof SputShort
-        || instruction instanceof SputChar
-        || instruction instanceof SputWide
-        || instruction instanceof SputObject;
-  }
-
-  @Override
   public boolean isIf() {
     return instruction instanceof IfEq
         || instruction instanceof IfEqz
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index e7eaa60..fb0e0d3 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -4,6 +4,9 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+
 public interface InstructionSubject {
 
   enum JumboStringMode {
@@ -13,12 +16,26 @@
 
   boolean isFieldAccess();
 
+  boolean isInstancePut();
+
+  boolean isStaticPut();
+
+  boolean isInstanceGet();
+
+  boolean isStaticGet();
+
+  DexField getField();
+
+  boolean isInvoke();
+
   boolean isInvokeVirtual();
 
   boolean isInvokeInterface();
 
   boolean isInvokeStatic();
 
+  DexMethod getMethod();
+
   boolean isNop();
 
   boolean isConstString(JumboStringMode jumboStringMode);
@@ -37,18 +54,8 @@
 
   boolean isThrow();
 
-  boolean isInvoke();
-
   boolean isNewInstance();
 
-  boolean isInstancePut();
-
-  boolean isStaticPut();
-
-  boolean isInstanceGet();
-
-  boolean isStaticGet();
-
   boolean isCheckCast();
 
   boolean isCheckCast(String type);
diff --git a/tools/test.py b/tools/test.py
index 6c15ea5..b57d00f 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -23,32 +23,32 @@
 
 def ParseOptions():
   result = optparse.OptionParser()
-  result.add_option('--no_internal',
+  result.add_option('--no-internal', '--no_internal',
       help='Do not run Google internal tests.',
       default=False, action='store_true')
-  result.add_option('--archive_failures',
+  result.add_option('--archive-failures', '--archive_failures',
       help='Upload test results to cloud storage on failure.',
       default=False, action='store_true')
-  result.add_option('--only_internal',
+  result.add_option('--only-internal', '--only_internal',
       help='Only run Google internal tests.',
       default=False, action='store_true')
-  result.add_option('--all_tests',
+  result.add_option('--all-tests', '--all_tests',
       help='Run tests in all configurations.',
       default=False, action='store_true')
   result.add_option('-v', '--verbose',
       help='Print test stdout to, well, stdout.',
       default=False, action='store_true')
-  result.add_option('--dex_vm',
+  result.add_option('--dex-vm', '--dex_vm',
       help='The android version of the vm to use. "all" will run the tests on '
            'all art vm versions (stopping after first failed execution)',
       default="default",
       choices=ALL_ART_VMS + ["all"])
-  result.add_option('--dex_vm_kind',
+  result.add_option('--dex-vm-kind', '--dex_vm_kind',
                     help='Whether to use host or target version of runtime',
                     default="host",
                     nargs=1,
                     choices=["host", "target"])
-  result.add_option('--one_line_per_test',
+  result.add_option('--one-line-per-test', '--one_line_per_test',
       help='Print a line before a tests starts and after it ends to stdout.',
       default=False, action='store_true')
   result.add_option('--tool',
@@ -58,32 +58,32 @@
   result.add_option('--jctf',
       help='Run JCTF tests with: "r8" (default) or "d8".',
       default=False, action='store_true')
-  result.add_option('--only_jctf',
+  result.add_option('--only-jctf', '--only_jctf',
       help='Run only JCTF tests with: "r8" (default) or "d8".',
       default=False, action='store_true')
-  result.add_option('--jctf_compile_only',
+  result.add_option('--jctf-compile-only', '--jctf_compile_only',
       help="Don't run, only compile JCTF tests.",
       default=False, action='store_true')
-  result.add_option('--aosp_jar',
+  result.add_option('--aosp-jar', '--aosp_jar',
       help='Run aosp_jar test.',
       default=False, action='store_true')
-  result.add_option('--disable_assertions',
+  result.add_option('--disable-assertions', '--disable_assertions',
       help='Disable assertions when running tests.',
       default=False, action='store_true')
-  result.add_option('--with_code_coverage',
+  result.add_option('--with-code-coverage', '--with_code_coverage',
       help='Enable code coverage with Jacoco.',
       default=False, action='store_true')
-  result.add_option('--test_dir',
+  result.add_option('--test-dir', '--test_dir',
       help='Use a custom directory for the test artifacts instead of a'
           ' temporary (which is automatically removed after the test).'
           ' Note that the directory will not be cleared before the test.')
-  result.add_option('--java_home',
+  result.add_option('--java-home', '--java_home',
       help='Use a custom java version to run tests.')
-  result.add_option('--generate_golden_files_to',
+  result.add_option('--generate-golden-files-to', '--generate_golden_files_to',
       help='Store dex files produced by tests in the specified directory.'
            ' It is aimed to be read on platforms with no host runtime available'
            ' for comparison.')
-  result.add_option('--use_golden_files_in',
+  result.add_option('--use-golden-files-in', '--use_golden_files_in',
       help='Download golden files hierarchy for this commit in the specified'
            ' location and use them instead of executing on host runtime.')