Merge "Parse <n> wildcard in members' names and type descriptors."
diff --git a/build.gradle b/build.gradle
index f857f9e..2cf3fe1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -36,7 +36,7 @@
     joptSimpleVersion = '4.6'
     jsonSimpleVersion = '1.1'
     junitVersion = '4.12'
-    kotlinVersion = '1.2.0'
+    kotlinVersion = '1.2.30'
     protobufVersion = '3.0.0'
     smaliVersion = '2.2b4'
 }
@@ -300,7 +300,7 @@
                 "android_jar/lib-v25",
                 "android_jar/lib-v26",
                 "proguard/proguard5.2.1",
-                "proguard/proguard6.0",
+                "proguard/proguard6.0.1",
                 "gradle/gradle",
                 "jdwp-tests",
                 "jasmin",
@@ -347,6 +347,7 @@
 
 def x20Dependencies = [
     "third_party": [
+        "benchmarks/santa-tracker",
         "gmail/gmail_android_170604.16",
         "gmscore/v4",
         "gmscore/v5",
@@ -356,6 +357,7 @@
         "gmscore/gmscore_v9",
         "gmscore/gmscore_v10",
         "gmscore/latest",
+        "gradle-plugin",
         "photos/2017-06-06",
         "youtube/youtube.android_12.10",
         "youtube/youtube.android_12.17",
diff --git a/src/main/java/com/android/tools/r8/ResourceShrinker.java b/src/main/java/com/android/tools/r8/ResourceShrinker.java
new file mode 100644
index 0000000..1df78be
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ResourceShrinker.java
@@ -0,0 +1,463 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.code.Const;
+import com.android.tools.r8.code.Const16;
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.ConstHigh16;
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.ConstStringJumbo;
+import com.android.tools.r8.code.ConstWide16;
+import com.android.tools.r8.code.ConstWide32;
+import com.android.tools.r8.code.FillArrayData;
+import com.android.tools.r8.code.FillArrayDataPayload;
+import com.android.tools.r8.code.Format35c;
+import com.android.tools.r8.code.Format3rc;
+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.NewArray;
+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.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.DexApplication;
+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.DexProgramClass;
+import com.android.tools.r8.graph.DexValue;
+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.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Stream;
+
+/**
+ * This class is deprecated and should not be used. It is a temporary solution to use R8 to analyze
+ * dex files and compute resource shrinker related bits.
+ *
+ * <p>Users or this API should implement {@link ReferenceChecker} interface, and through callbacks
+ * in that interface, they will be notified when element relevant for resource shrinking is found.
+ *
+ * <p>This class extracts all integer constants and string constants, which might refer to resource.
+ * More specifically, we look for the following while analyzing dex:
+ * <ul>
+ *   <li>const instructions that might load integers or strings
+ *   <li>static fields that have an initial value. This initial value might be integer, string,
+ *   or array of integers.
+ *   <li>integer array payloads. Only payloads referenced in fill-array-data instructions will be
+ *   processed. More specifically, if a payload is referenced in fill-array-data, and we are able
+ *   to determine that array is not array of integers, payload will be ignored. Otherwise, it will
+ *   be processed once fill-array-data-payload instruction is encountered.
+ *   <li>all annotations (class, field, method) that contain annotation element whose value is
+ *   integer, string or array of integers are processed.
+ * </ul>
+ *
+ * <p>Please note that switch payloads are not analyzed. Although they might contain integer
+ * constants, ones referring to resource ids would have to be loaded in the code analyzed in list
+ * above.
+ *
+ * <p>Usage of this feature is intentionally not supported from the command line.
+ */
+@Deprecated
+final public class ResourceShrinker {
+
+  final static class Command extends BaseCommand {
+
+    Command(AndroidApp app) {
+      super(app);
+    }
+
+    @Override
+    InternalOptions getInternalOptions() {
+      return new InternalOptions();
+    }
+  }
+
+  final public static class Builder extends BaseCommand.Builder<Command, Builder> {
+
+    @Override
+    Builder self() {
+      return this;
+    }
+
+    @Override
+    Command makeCommand() {
+      return new Command(getAppBuilder().build());
+    }
+  }
+
+  /**
+   * Classes that would like to process data relevant to resource shrinking should implement this
+   * interface.
+   */
+  interface ReferenceChecker {
+
+    /**
+     * Returns if the class with specified internal name should be processed. Typically,
+     * resource type classes like R$drawable, R$styleable etc. should be skipped.
+     */
+    boolean shouldProcess(String internalName);
+
+    void referencedInt(int value);
+
+    void referencedString(String value);
+
+    void referencedStaticField(String internalName, String fieldName);
+
+    void referencedMethod(String internalName, String methodName, String methodDescriptor);
+  }
+
+  private static final class DexClassUsageVisitor {
+
+    private final DexProgramClass classDef;
+    private final ReferenceChecker callback;
+
+    DexClassUsageVisitor(DexProgramClass classDef, ReferenceChecker callback) {
+      this.classDef = classDef;
+      this.callback = callback;
+    }
+
+    public void visit() {
+      if (!callback.shouldProcess(classDef.type.getInternalName())) {
+        return;
+      }
+
+      for (DexEncodedField field : classDef.staticFields()) {
+        if (field.staticValue != null) {
+          processFieldValue(field.staticValue);
+        }
+      }
+
+      for (DexEncodedMethod method : classDef.allMethodsSorted()) {
+        processMethod(method);
+      }
+
+      if (classDef.hasAnnotations()) {
+        processAnnotations(classDef);
+      }
+    }
+
+    private void processFieldValue(DexValue value) {
+      if (value instanceof DexValue.DexValueString) {
+        callback.referencedString(((DexValue.DexValueString) value).value.toString());
+      } else if (value instanceof DexValue.DexValueInt) {
+        int constantValue = ((DexValue.DexValueInt) value).getValue();
+        callback.referencedInt(constantValue);
+      } else if (value instanceof DexValue.DexValueArray) {
+        DexValue.DexValueArray arrayEncodedValue = (DexValue.DexValueArray) value;
+        for (DexValue encodedValue : arrayEncodedValue.getValues()) {
+          if (encodedValue instanceof DexValue.DexValueInt) {
+            int constantValue = ((DexValue.DexValueInt) encodedValue).getValue();
+            callback.referencedInt(constantValue);
+          }
+        }
+      }
+    }
+
+    private void processMethod(DexEncodedMethod method) {
+      Code implementation = method.getCode();
+      if (implementation != null) {
+
+        // Tracks the offsets of integer array payloads.
+        final Set<Integer> methodIntArrayPayloadOffsets = Sets.newHashSet();
+        // First we collect payloads, and then we process them because payload can be before the
+        // fill-array-data instruction referencing it.
+        final List<FillArrayDataPayload> payloads = Lists.newArrayList();
+
+        Instruction[] instructions = implementation.asDexCode().instructions;
+        int current = 0;
+        while (current < instructions.length) {
+          Instruction instruction = instructions[current];
+          if (isIntConstInstruction(instruction)) {
+            processIntConstInstruction(instruction);
+          } else if (isStringConstInstruction(instruction)) {
+            processStringConstantInstruction(instruction);
+          } else if (isGetStatic(instruction)) {
+            processGetStatic(instruction);
+          } else if (isInvokeInstruction(instruction)) {
+            processInvokeInstruction(instruction);
+          } else if (isInvokeRangeInstruction(instruction)) {
+            processInvokeRangeInstruction(instruction);
+          } else if (instruction instanceof FillArrayData) {
+            processFillArray(instructions, current, methodIntArrayPayloadOffsets);
+          } else if (instruction instanceof FillArrayDataPayload) {
+            payloads.add((FillArrayDataPayload) instruction);
+          }
+          current++;
+        }
+
+        for (FillArrayDataPayload payload : payloads) {
+          if (isIntArrayPayload(payload, methodIntArrayPayloadOffsets)) {
+            processIntArrayPayload(payload);
+          }
+        }
+      }
+    }
+
+    private void processAnnotations(DexProgramClass classDef) {
+      Stream<DexAnnotation> instanceFieldAnnotations =
+          Arrays.stream(classDef.instanceFields())
+              .filter(DexEncodedField::hasAnnotation)
+              .flatMap(f -> Arrays.stream(f.annotations.annotations));
+      Stream<DexAnnotation> staticFieldAnnotations =
+          Arrays.stream(classDef.staticFields())
+              .filter(DexEncodedField::hasAnnotation)
+              .flatMap(f -> Arrays.stream(f.annotations.annotations));
+      Stream<DexAnnotation> virtualMethodAnnotations =
+          Arrays.stream(classDef.virtualMethods())
+              .filter(DexEncodedMethod::hasAnnotation)
+              .flatMap(m -> Arrays.stream(m.annotations.annotations));
+      Stream<DexAnnotation> directMethodAnnotations =
+          Arrays.stream(classDef.directMethods())
+              .filter(DexEncodedMethod::hasAnnotation)
+              .flatMap(m -> Arrays.stream(m.annotations.annotations));
+      Stream<DexAnnotation> classAnnotations = Arrays.stream(classDef.annotations.annotations);
+
+      Streams.concat(
+          instanceFieldAnnotations,
+          staticFieldAnnotations,
+          virtualMethodAnnotations,
+          directMethodAnnotations,
+          classAnnotations)
+          .forEach(annotation -> {
+            for (DexAnnotationElement element : annotation.annotation.elements) {
+              DexValue value = element.value;
+              processAnnotationValue(value);
+            }
+          });
+    }
+
+    private void processIntArrayPayload(Instruction instruction) {
+      FillArrayDataPayload payload = (FillArrayDataPayload) instruction;
+
+      for (int i = 0; i < payload.data.length / 2; i++) {
+        int intValue = payload.data[2 * i + 1] << 16 | payload.data[2 * i];
+        callback.referencedInt(intValue);
+      }
+    }
+
+    private boolean isIntArrayPayload(
+        Instruction instruction, Set<Integer> methodIntArrayPayloadOffsets) {
+      if (!(instruction instanceof FillArrayDataPayload)) {
+        return false;
+      }
+
+      FillArrayDataPayload payload = (FillArrayDataPayload) instruction;
+      return methodIntArrayPayloadOffsets.contains(payload.getOffset());
+    }
+
+    private void processFillArray(
+        Instruction[] instructions, int current, Set<Integer> methodIntArrayPayloadOffsets) {
+      FillArrayData fillArrayData = (FillArrayData) instructions[current];
+      if (current > 0 && instructions[current - 1] instanceof NewArray) {
+        NewArray newArray = (NewArray) instructions[current - 1];
+        if (!Objects.equals(newArray.getType().descriptor.toString(), "[I")) {
+          return;
+        }
+        // Typically, new-array is right before fill-array-data. If not, assume referenced array is
+        // of integers. This can be improved later, but for now we make sure no ints are missed.
+      }
+
+      methodIntArrayPayloadOffsets.add(fillArrayData.getPayloadOffset() + fillArrayData.offset);
+    }
+
+    private void processAnnotationValue(DexValue value) {
+      if (value instanceof DexValue.DexValueInt) {
+        DexValue.DexValueInt dexValueInt = (DexValue.DexValueInt) value;
+        callback.referencedInt(dexValueInt.value);
+      } else if (value instanceof DexValue.DexValueString) {
+        DexValue.DexValueString dexValueString = (DexValue.DexValueString) value;
+        callback.referencedString(dexValueString.value.toString());
+      } else if (value instanceof DexValue.DexValueArray) {
+        DexValue.DexValueArray dexValueArray = (DexValue.DexValueArray) value;
+        for (DexValue dexValue : dexValueArray.getValues()) {
+          processAnnotationValue(dexValue);
+        }
+      } else if (value instanceof DexValue.DexValueAnnotation) {
+        DexValue.DexValueAnnotation dexValueAnnotation = (DexValue.DexValueAnnotation) value;
+        for (DexAnnotationElement element : dexValueAnnotation.value.elements) {
+          processAnnotationValue(element.value);
+        }
+      }
+    }
+
+    private boolean isIntConstInstruction(Instruction instruction) {
+      int opcode = instruction.getOpcode();
+      return opcode == Const4.OPCODE
+          || opcode == Const16.OPCODE
+          || opcode == Const.OPCODE
+          || opcode == ConstWide32.OPCODE
+          || opcode == ConstHigh16.OPCODE
+          || opcode == ConstWide16.OPCODE;
+    }
+
+    private void processIntConstInstruction(Instruction instruction) {
+      assert isIntConstInstruction(instruction);
+
+      int constantValue;
+      if (instruction instanceof SingleConstant) {
+        SingleConstant singleConstant = (SingleConstant) instruction;
+        constantValue = singleConstant.decodedValue();
+      } else if (instruction instanceof WideConstant) {
+        WideConstant wideConstant = (WideConstant) instruction;
+        if (((int) wideConstant.decodedValue()) != wideConstant.decodedValue()) {
+          // We care only about values that fit in int range.
+          return;
+        }
+        constantValue = (int) wideConstant.decodedValue();
+      } else {
+        throw new AssertionError("Not an int const instruction.");
+      }
+
+      callback.referencedInt(constantValue);
+    }
+
+    private boolean isStringConstInstruction(Instruction instruction) {
+      int opcode = instruction.getOpcode();
+      return opcode == ConstString.OPCODE || opcode == ConstStringJumbo.OPCODE;
+    }
+
+    private void processStringConstantInstruction(Instruction instruction) {
+      assert isStringConstInstruction(instruction);
+
+      String constantValue;
+      if (instruction instanceof ConstString) {
+        ConstString constString = (ConstString) instruction;
+        constantValue = constString.getString().toString();
+      } else if (instruction instanceof ConstStringJumbo) {
+        ConstStringJumbo constStringJumbo = (ConstStringJumbo) instruction;
+        constantValue = constStringJumbo.getString().toString();
+      } else {
+        throw new AssertionError("Not a string constant instruction.");
+      }
+
+      callback.referencedString(constantValue);
+    }
+
+    private boolean isGetStatic(Instruction instruction) {
+      int opcode = instruction.getOpcode();
+      return opcode == Sget.OPCODE
+          || opcode == SgetBoolean.OPCODE
+          || opcode == SgetByte.OPCODE
+          || opcode == SgetChar.OPCODE
+          || opcode == SgetObject.OPCODE
+          || opcode == SgetShort.OPCODE
+          || opcode == SgetWide.OPCODE;
+    }
+
+    private void processGetStatic(Instruction instruction) {
+      assert isGetStatic(instruction);
+
+      DexField field;
+      if (instruction instanceof Sget) {
+        Sget sget = (Sget) instruction;
+        field = sget.getField();
+      } else if (instruction instanceof SgetBoolean) {
+        SgetBoolean sgetBoolean = (SgetBoolean) instruction;
+        field = sgetBoolean.getField();
+      } else if (instruction instanceof SgetByte) {
+        SgetByte sgetByte = (SgetByte) instruction;
+        field = sgetByte.getField();
+      } else if (instruction instanceof SgetChar) {
+        SgetChar sgetChar = (SgetChar) instruction;
+        field = sgetChar.getField();
+      } else if (instruction instanceof SgetObject) {
+        SgetObject sgetObject = (SgetObject) instruction;
+        field = sgetObject.getField();
+      } else if (instruction instanceof SgetShort) {
+        SgetShort sgetShort = (SgetShort) instruction;
+        field = sgetShort.getField();
+      } else if (instruction instanceof SgetWide) {
+        SgetWide sgetWide = (SgetWide) instruction;
+        field = sgetWide.getField();
+      } else {
+        throw new AssertionError("Not a get static instruction");
+      }
+
+      callback.referencedStaticField(field.clazz.getInternalName(), field.name.toString());
+    }
+
+    private boolean isInvokeInstruction(Instruction instruction) {
+      int opcode = instruction.getOpcode();
+      return opcode == InvokeVirtual.OPCODE
+          || opcode == InvokeSuper.OPCODE
+          || opcode == InvokeDirect.OPCODE
+          || opcode == InvokeStatic.OPCODE
+          || opcode == InvokeInterface.OPCODE;
+    }
+
+    private void processInvokeInstruction(Instruction instruction) {
+      assert isInvokeInstruction(instruction);
+
+      Format35c ins35c = (Format35c) instruction;
+      DexMethod method = (DexMethod) ins35c.BBBB;
+
+      callback.referencedMethod(
+          method.holder.getInternalName(),
+          method.name.toString(),
+          method.proto.toDescriptorString());
+    }
+
+    private boolean isInvokeRangeInstruction(Instruction instruction) {
+      int opcode = instruction.getOpcode();
+      return opcode == InvokeVirtualRange.OPCODE
+          || opcode == InvokeSuperRange.OPCODE
+          || opcode == InvokeDirectRange.OPCODE
+          || opcode == InvokeStaticRange.OPCODE
+          || opcode == InvokeInterfaceRange.OPCODE;
+    }
+
+    private void processInvokeRangeInstruction(Instruction instruction) {
+      assert isInvokeRangeInstruction(instruction);
+
+      Format3rc ins3rc = (Format3rc) instruction;
+      DexMethod method = (DexMethod) ins3rc.BBBB;
+
+      callback.referencedMethod(
+          method.holder.getInternalName(),
+          method.name.toString(),
+          method.proto.toDescriptorString());
+    }
+  }
+
+  public static void run(Command command, ReferenceChecker callback)
+      throws IOException, ExecutionException {
+    AndroidApp inputApp = command.getInputApp();
+    Timing timing = new Timing("resource shrinker analyzer");
+    DexApplication dexApplication =
+        new ApplicationReader(inputApp, command.getInternalOptions(), timing).read();
+    for (DexProgramClass programClass : dexApplication.classes()) {
+      new DexClassUsageVisitor(programClass, callback).visit();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/StringConsumer.java b/src/main/java/com/android/tools/r8/StringConsumer.java
index 543e8cd..9232e08 100644
--- a/src/main/java/com/android/tools/r8/StringConsumer.java
+++ b/src/main/java/com/android/tools/r8/StringConsumer.java
@@ -14,7 +14,6 @@
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.Collections;
 
 /** Interface for receiving String resource. */
 public interface StringConsumer {
@@ -101,7 +100,7 @@
     public void accept(String string, DiagnosticsHandler handler) {
       super.accept(string, handler);
       try {
-        Files.write(outputPath, Collections.singletonList(string), encoding);
+        Files.write(outputPath, string.getBytes(encoding));
       } catch (IOException e) {
         handler.error(new IOExceptionDiagnostic(e, new PathOrigin(outputPath)));
       }
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 670211f..e51c08a 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 = "v1.1.9-dev";
+  public static final String LABEL = "v1.2.0-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 d3576c1..9416a7b 100644
--- a/src/main/java/com/android/tools/r8/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/code/CheckCast.java
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry registry) {
-    registry.registerTypeReference(getType());
+    registry.registerCheckCast(getType());
   }
 
   public DexType getType() {
diff --git a/src/main/java/com/android/tools/r8/code/ConstClass.java b/src/main/java/com/android/tools/r8/code/ConstClass.java
index f140022..ef8b257 100644
--- a/src/main/java/com/android/tools/r8/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/code/ConstClass.java
@@ -39,7 +39,7 @@
 
   @Override
   public void registerUse(UseRegistry registry) {
-    registry.registerTypeReference(getType());
+    registry.registerConstClass(getType());
   }
 
   public DexType getType() {
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index f9ea993..8933353 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -187,8 +187,14 @@
     if (context != null) {
       // The SecondVistor is in charge of setting the context to null.
       DexProgramClass owner = context.owner;
-      new ClassReader(context.classCache).accept(new SecondVisitor(context, application),
-          ClassReader.SKIP_FRAMES);
+      int flags = ClassReader.SKIP_FRAMES;
+      if (application.options.isGeneratingClassFiles()) {
+        // TODO(mathiasr): Keep frames in JarCode until IR->CF construction is complete.
+        // When we throw Unimplemented in IR->CF construction, the original JarCode is output
+        // instead. In this case we must output frames as well, or we will fail verification.
+        flags = 0;
+      }
+      new ClassReader(context.classCache).accept(new SecondVisitor(context, application), flags);
       assert verifyNoReparseContext(owner);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 78b2f56..48ef91d 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -27,6 +27,14 @@
 
   public abstract boolean registerTypeReference(DexType type);
 
+  public boolean registerConstClass(DexType type) {
+    return registerTypeReference(type);
+  }
+
+  public boolean registerCheckCast(DexType type) {
+    return registerTypeReference(type);
+  }
+
   public void registerMethodHandle(DexMethodHandle methodHandle) {
     switch (methodHandle.type) {
       case INSTANCE_GET:
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index 1d5a2f5..cea44bc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -16,6 +16,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.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -143,7 +144,14 @@
 
   @Override
   public DexType computeVerificationType(TypeVerificationHelper helper) {
-    return helper.getType(array()).toArrayElementType(helper.getFactory());
+    // This method is not called for ArrayGet on primitive array.
+    assert this.outValue.type.isObject();
+    DexType arrayType = helper.getType(array());
+    if (arrayType == DexItemFactory.nullValueType) {
+      // JVM 8 §4.10.1.9.aaload: Array component type of null is null.
+      return arrayType;
+    }
+    return arrayType.toArrayElementType(helper.getFactory());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
index 4d18b92..d8e78e5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -62,4 +63,9 @@
     // They should also have a non-empty set of debug values (see RegAlloc::computeDebugInfo)
     return false;
   }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    // Non-materializing so no stack values are needed.
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 76a1a43..20051f9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -396,10 +396,6 @@
     // necessary.
     ir.splitCriticalEdges();
 
-    // Create block order and make sure that all blocks are immediately followed by their
-    // fallthrough block if any.
-    ir.traceBlocks();
-
     // Clear the code so we don't build multiple times.
     source.clear();
     source = null;
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 42b06ef..0f0b44a 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
@@ -476,6 +476,7 @@
       Log.debug(getClass(), "Initial (SSA) flow graph for %s:\n%s", method.toSourceString(), code);
     }
     assert code.isConsistentSSA();
+    code.traceBlocks();
     RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
     method.setCode(code, registerAllocator, options);
     if (Log.ENABLED) {
@@ -635,7 +636,6 @@
 
     if (options.testing.invertConditionals) {
       invertConditionalsForTesting(code);
-      code.traceBlocks();
     }
 
     if (options.enableNonNullTracking && nonNullTracker != null) {
@@ -699,6 +699,7 @@
   }
 
   private void finalizeIR(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+    code.traceBlocks();
     if (options.isGeneratingClassFiles()) {
       finalizeToCf(method, code, feedback);
     } else {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 12429e9..894ba4f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -53,6 +53,7 @@
 import org.objectweb.asm.Type;
 import org.objectweb.asm.tree.AbstractInsnNode;
 import org.objectweb.asm.tree.FieldInsnNode;
+import org.objectweb.asm.tree.FrameNode;
 import org.objectweb.asm.tree.IincInsnNode;
 import org.objectweb.asm.tree.InsnNode;
 import org.objectweb.asm.tree.IntInsnNode;
@@ -218,8 +219,16 @@
     return (node.access & Opcodes.ACC_STATIC) > 0;
   }
 
-  private boolean isSynchronized() {
-    return (node.access & Opcodes.ACC_SYNCHRONIZED) > 0;
+  /**
+   * Determine if we should emit monitor enter/exit instructions at method entry/exit.
+   *
+   * @return true if we are generating Dex and method is marked synchronized, otherwise false.
+   */
+  private boolean generateMethodSynchronization() {
+    // When generating class files, don't treat the method specially because it is synchronized.
+    // At runtime, the JVM will automatically perform the correct monitor enter/exit instructions.
+    return !application.options.isGeneratingClassFiles()
+        && (node.access & Opcodes.ACC_SYNCHRONIZED) > 0;
   }
 
   private int formalParameterCount() {
@@ -348,7 +357,7 @@
       }
     }
 
-    if (isSynchronized()) {
+    if (generateMethodSynchronization()) {
       generatingMethodSynchronization = true;
       Type clazzType = application.getAsmType(clazz.toDescriptorString());
       int monitorRegister;
@@ -450,7 +459,7 @@
 
   @Override
   public void buildPostlude(IRBuilder builder) {
-    if (isSynchronized()) {
+    if (generateMethodSynchronization()) {
       generatingMethodSynchronization = true;
       buildMonitorExit(builder);
       generatingMethodSynchronization = false;
@@ -458,7 +467,7 @@
   }
 
   private void buildExceptionalPostlude(IRBuilder builder) {
-    assert isSynchronized();
+    assert generateMethodSynchronization();
     generatingMethodSynchronization = true;
     currentPosition = getExceptionalExitPosition();
     buildMonitorExit(builder);
@@ -748,7 +757,7 @@
         handlers.add(tryCatchBlock);
       }
     }
-    if (isSynchronized()) {
+    if (generateMethodSynchronization()) {
       // Add synchronized exceptional exit for synchronized-method instructions without a default.
       assert handlers.isEmpty() || handlers.get(handlers.size() - 1).getType() != null;
       handlers.add(EXCEPTIONAL_SYNC_EXIT);
@@ -1165,6 +1174,9 @@
       case AbstractInsnNode.LINE:
         updateState((LineNumberNode) insn);
         break;
+      case AbstractInsnNode.FRAME:
+        updateState((FrameNode) insn);
+        break;
       default:
         throw new Unreachable("Unexpected instruction " + insn);
     }
@@ -1786,6 +1798,11 @@
     // Intentionally empty.
   }
 
+  private void updateState(FrameNode insn) {
+    assert application.options.isGeneratingClassFiles();
+    // Intentionally empty.
+  }
+
   private void updateStateForConversion(Type from, Type to) {
     state.pop();
     state.push(to);
@@ -1840,6 +1857,9 @@
       case AbstractInsnNode.LINE:
         build((LineNumberNode) insn, builder);
         break;
+      case AbstractInsnNode.FRAME:
+        build((FrameNode) insn, builder);
+        break;
       default:
         throw new Unreachable("Unexpected instruction " + insn);
     }
@@ -2296,7 +2316,7 @@
     if (handlers.isEmpty()) {
       return true;
     }
-    if (!isSynchronized() || handlers.size() > 1) {
+    if (!generateMethodSynchronization() || handlers.size() > 1) {
       return false;
     }
     return handlers.get(0) == EXCEPTIONAL_SYNC_EXIT;
@@ -2908,6 +2928,11 @@
     builder.addDebugPosition(currentPosition);
   }
 
+  private void build(FrameNode insn, IRBuilder builder) {
+    assert application.options.isGeneratingClassFiles();
+    // Intentionally empty.
+  }
+
   @Override
   public Position getDebugPositionAtOffset(int offset) {
     if (offset == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 3d1f79d..9379a83 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -180,9 +180,6 @@
             continue;
           }
 
-          // This is a private instance method call. Note that the referenced method
-          // is expected to be in the current class since it is private, but desugaring
-          // may move some methods or their code into other classes.
           DexClass clazz = findDefinitionFor(method.holder);
           if (clazz == null) {
             // Report missing class since we don't know if it is an interface.
@@ -195,9 +192,32 @@
                   getMethodOrigin(encodedMethod.method));
 
             }
-            instructions.replaceCurrentInstruction(
-                new InvokeStatic(privateAsMethodOfCompanionClass(method),
-                    invokeDirect.outValue(), invokeDirect.arguments()));
+
+            // This might be either private method call, or a call to default
+            // interface method made via invoke-direct.
+            DexEncodedMethod virtualTarget = null;
+            for (DexEncodedMethod candidate : clazz.virtualMethods()) {
+              if (candidate.method == method) {
+                virtualTarget = candidate;
+                break;
+              }
+            }
+
+            if (virtualTarget != null) {
+              // This is a invoke-direct call to a virtual method.
+              instructions.replaceCurrentInstruction(
+                  new InvokeStatic(defaultAsMethodOfCompanionClass(method),
+                      invokeDirect.outValue(), invokeDirect.arguments()));
+
+            } else {
+              // Otherwise this must be a private instance method call. Note that the referenced
+              // method is expected to be in the current class since it is private, but desugaring
+              // may move some methods or their code into other classes.
+
+              instructions.replaceCurrentInstruction(
+                  new InvokeStatic(privateAsMethodOfCompanionClass(method),
+                      invokeDirect.outValue(), invokeDirect.arguments()));
+            }
           }
         }
       }
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 a6b29e8..30e9bae4 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
@@ -565,7 +565,6 @@
     // being split on the way in but does not maintain this property. We therefore split
     // critical edges at exit.
     code.splitCriticalEdges();
-    code.traceBlocks();
     assert code.isConsistentSSA();
   }
 
@@ -2028,9 +2027,6 @@
     }
     code.removeMarkedBlocks(color);
     code.returnMarkingColor(color);
-    if (ifBranchFlipped) {
-      code.traceBlocks();
-    }
     assert code.isConsistentSSA();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index acf4395..85e0060 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -54,6 +54,8 @@
 import java.util.PriorityQueue;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
 
 /**
  * Linear scan register allocator.
@@ -1348,10 +1350,17 @@
     // free position.
     int candidate = getLargestValidCandidate(
         unhandledInterval, registerConstraint, needsRegisterPair, freePositions, Type.ANY);
-    assert candidate != REGISTER_CANDIDATE_NOT_FOUND;
-    int largestFreePosition = freePositions.get(candidate);
-    if (needsRegisterPair) {
-      largestFreePosition = Math.min(largestFreePosition, freePositions.get(candidate + 1));
+
+    // It is not always possible to find a largest valid candidate. If none of the usable register
+    // are free we typically get the last candidate. However, if that candidate has to be
+    // discarded in order to workaround bugs we get REGISTER_CANDIDATE_NOT_FOUND. In both cases
+    // we need to spill a valid candidate. That path is triggered when largestFreePosition is 0.
+    int largestFreePosition = 0;
+    if (candidate != REGISTER_CANDIDATE_NOT_FOUND) {
+      largestFreePosition = freePositions.get(candidate);
+      if (needsRegisterPair) {
+        largestFreePosition = Math.min(largestFreePosition, freePositions.get(candidate + 1));
+      }
     }
 
     // Determine what to do based on how long the selected candidate is free.
@@ -1554,57 +1563,47 @@
     return candidate;
   }
 
+  private int handleWorkaround(
+      Predicate<LiveIntervals> workaroundNeeded,
+      BiPredicate<LiveIntervals, Integer> workaroundNeededForCandidate,
+      int candidate, LiveIntervals unhandledInterval, int registerConstraint,
+      boolean needsRegisterPair, RegisterPositions freePositions, RegisterPositions.Type type) {
+    if (workaroundNeeded.test(unhandledInterval)) {
+      int lastCandidate = candidate;
+      while (workaroundNeededForCandidate.test(unhandledInterval, candidate)) {
+        // Make the unusable register unavailable for allocation and try again.
+        freePositions.set(candidate, 0);
+        candidate = getLargestCandidate(registerConstraint, freePositions, needsRegisterPair, type);
+        // If there are only invalid candidates of the give type we will end up with the same
+        // candidate returned again once we have tried them all. In that case we didn't find a
+        // valid register candidate and we need to broaden the search to other types.
+        if (lastCandidate == candidate) {
+          return REGISTER_CANDIDATE_NOT_FOUND;
+        }
+        lastCandidate = candidate;
+      }
+    }
+    return candidate;
+  }
+
   private int getLargestValidCandidate(LiveIntervals unhandledInterval, int registerConstraint,
       boolean needsRegisterPair, RegisterPositions freePositions, RegisterPositions.Type type) {
     int candidate = getLargestCandidate(registerConstraint, freePositions, needsRegisterPair, type);
     if (candidate == REGISTER_CANDIDATE_NOT_FOUND) {
       return candidate;
     }
-    if (needsLongResultOverlappingLongOperandsWorkaround(unhandledInterval)) {
-      int lastCandidate = candidate;
-      while (isLongResultOverlappingLongOperands(unhandledInterval, candidate)) {
-        // Make the overlapping register unavailable for allocation and try again.
-        freePositions.set(candidate, 0);
-        candidate = getLargestCandidate(registerConstraint, freePositions, needsRegisterPair, type);
-        // If there are only invalid candidates of the give type we will end up with the same
-        // candidate returned again once we have tried them all. In that case we didn't find a
-        // valid register candidate and we need to broaden the search to other types.
-        if (lastCandidate == candidate) {
-          return REGISTER_CANDIDATE_NOT_FOUND;
-        }
-        lastCandidate = candidate;
-      }
-    }
-    if (needsSingleResultOverlappingLongOperandsWorkaround(unhandledInterval)) {
-      int lastCandidate = candidate;
-      while (isSingleResultOverlappingLongOperands(unhandledInterval, candidate)) {
-        // Make the overlapping register unavailable for allocation and try again.
-        freePositions.set(candidate, 0);
-        candidate = getLargestCandidate(registerConstraint, freePositions, needsRegisterPair, type);
-        // If there are only invalid candidates of the give type we will end up with the same
-        // candidate returned again once we have tried them all. In that case we didn't find a
-        // valid register candidate and we need to broaden the search to other types.
-        if (lastCandidate == candidate) {
-          return REGISTER_CANDIDATE_NOT_FOUND;
-        }
-        lastCandidate = candidate;
-      }
-    }
-    if (needsArrayGetWideWorkaround(unhandledInterval)) {
-      int lastCandidate = candidate;
-      while (isArrayGetArrayRegister(unhandledInterval, candidate)) {
-        // Make the overlapping register unavailable for allocation and try again.
-        freePositions.set(candidate, 0);
-        candidate = getLargestCandidate(registerConstraint, freePositions, needsRegisterPair, type);
-        // If there are only invalid candidates of the give type we will end up with the same
-        // candidate returned again once we have tried them all. In that case we didn't find a
-        // valid register candidate and we need to broaden the search to other types.
-        if (lastCandidate == candidate) {
-          return REGISTER_CANDIDATE_NOT_FOUND;
-        }
-        lastCandidate = candidate;
-      }
-    }
+    candidate = handleWorkaround(
+        this::needsLongResultOverlappingLongOperandsWorkaround,
+        this::isLongResultOverlappingLongOperands,
+        candidate, unhandledInterval, registerConstraint, needsRegisterPair, freePositions, type);
+    candidate = handleWorkaround(
+        this::needsSingleResultOverlappingLongOperandsWorkaround,
+        this::isSingleResultOverlappingLongOperands,
+        candidate, unhandledInterval, registerConstraint, needsRegisterPair, freePositions, type);
+    candidate = handleWorkaround(
+        this::needsArrayGetWideWorkaround,
+        this::isArrayGetArrayRegister,
+        candidate, unhandledInterval, registerConstraint, needsRegisterPair, freePositions, type);
     return candidate;
   }
 
diff --git a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
index f9a7f49..37c9aea 100644
--- a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
@@ -35,6 +35,8 @@
     DexType type = application.getTypeFromName(name);
     if (opcode == org.objectweb.asm.Opcodes.NEW) {
       registry.registerNewInstance(type);
+    } else if (opcode == Opcodes.CHECKCAST) {
+        registry.registerCheckCast(type);
     } else {
       registry.registerTypeReference(type);
     }
@@ -51,7 +53,7 @@
       // Nothing to register for method type, it represents only a prototype not associated with a
       // method name.
       if (((Type) cst).getSort() != Type.METHOD) {
-        registry.registerTypeReference(application.getType((Type) cst));
+        registry.registerConstClass(application.getType((Type) cst));
       }
     } else if (cst instanceof Handle) {
       registerMethodHandleType((Handle) cst);
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
index 1fa8aaa..9e85188 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
@@ -32,8 +32,8 @@
     this.dictionary = options.proguardConfiguration.getObfuscationDictionary();
     this.useUniqueMemberNames = options.proguardConfiguration.isUseUniqueClassMemberNames();
     this.overloadAggressively = options.proguardConfiguration.isOverloadAggressively();
-    this.globalState =
-        NamingState.createRoot(appInfo.dexItemFactory, dictionary, getKeyTransform());
+    this.globalState = NamingState.createRoot(
+        appInfo.dexItemFactory, dictionary, getKeyTransform(), useUniqueMemberNames);
   }
 
   abstract Function<StateType, ?> getKeyTransform();
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 936a06a..ce37763 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -143,22 +143,34 @@
   private void assignNamesToClassesMethods(DexType type, boolean doPrivates) {
     DexClass holder = appInfo.definitionFor(type);
     if (holder != null && !holder.isLibraryClass()) {
+      Map<DexMethod, DexString> renamingAtThisLevel = new IdentityHashMap<>();
       NamingState<DexProto, ?> state =
           computeStateIfAbsent(type, k -> getState(holder.superType).createChild());
-      holder.forEachMethod(method -> assignNameToMethod(method, state, doPrivates));
+      holder.forEachMethod(method ->
+          assignNameToMethod(method, state, renamingAtThisLevel, doPrivates));
+      if (!doPrivates && !useUniqueMemberNames) {
+        renamingAtThisLevel.forEach((method, candidate) ->
+            state.addRenaming(method.name, method.proto, candidate));
+      }
     }
     type.forAllExtendsSubtypes(subtype -> assignNamesToClassesMethods(subtype, doPrivates));
   }
 
   private void assignNameToMethod(
-      DexEncodedMethod encodedMethod, NamingState<DexProto, ?> state, boolean doPrivates) {
+      DexEncodedMethod encodedMethod,
+      NamingState<DexProto, ?> state,
+      Map<DexMethod, DexString> renamingAtThisLevel,
+      boolean doPrivates) {
     if (encodedMethod.accessFlags.isPrivate() != doPrivates) {
       return;
     }
     DexMethod method = encodedMethod.method;
     if (!state.isReserved(method.name, method.proto)
         && !encodedMethod.accessFlags.isConstructor()) {
-      renaming.put(method, state.assignNewNameFor(method.name, method.proto, !doPrivates));
+      DexString renamedName =
+          state.assignNewNameFor(method.name, method.proto, useUniqueMemberNames);
+      renaming.put(method, renamedName);
+      renamingAtThisLevel.put(method, renamedName);
     }
   }
 
@@ -331,7 +343,8 @@
         computeStateIfAbsent(
             libraryFrontier,
             ignore -> parent == null
-                ? NamingState.createRoot(appInfo.dexItemFactory, dictionary, getKeyTransform())
+                ? NamingState.createRoot(
+                    appInfo.dexItemFactory, dictionary, getKeyTransform(), useUniqueMemberNames)
                 : parent.createChild());
     if (holder != null) {
       boolean keepAll = holder.isLibraryClass() || holder.accessFlags.isAnnotation();
diff --git a/src/main/java/com/android/tools/r8/naming/NamingState.java b/src/main/java/com/android/tools/r8/naming/NamingState.java
index b3ff544..abfa3c1 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingState.java
@@ -7,9 +7,11 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.HashBiMap;
+import com.google.common.collect.HashBasedTable;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
+import com.google.common.collect.Table;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -20,80 +22,86 @@
 class NamingState<ProtoType extends CachedHashValueDexItem, KeyType> {
 
   private final NamingState<ProtoType, KeyType> parent;
-  private final Map<KeyType, InternalState> usedNames = new HashMap<>();
+  private final Map<KeyType, InternalState<ProtoType>> usedNames = new HashMap<>();
   private final DexItemFactory itemFactory;
   private final ImmutableList<String> dictionary;
   private final Function<ProtoType, KeyType> keyTransform;
+  private final boolean useUniqueMemberNames;
 
   static <S, T extends CachedHashValueDexItem> NamingState<T, S> createRoot(
-      DexItemFactory itemFactory, ImmutableList<String> dictionary, Function<T, S> keyTransform) {
-    return new NamingState<>(null, itemFactory, dictionary, keyTransform);
+      DexItemFactory itemFactory,
+      ImmutableList<String> dictionary,
+      Function<T, S> keyTransform,
+      boolean useUniqueMemberNames) {
+    return new NamingState<>(null, itemFactory, dictionary, keyTransform, useUniqueMemberNames);
   }
 
   private NamingState(
       NamingState<ProtoType, KeyType> parent,
       DexItemFactory itemFactory,
       ImmutableList<String> dictionary,
-      Function<ProtoType, KeyType> keyTransform) {
+      Function<ProtoType, KeyType> keyTransform,
+      boolean useUniqueMemberNames) {
     this.parent = parent;
     this.itemFactory = itemFactory;
     this.dictionary = dictionary;
     this.keyTransform = keyTransform;
+    this.useUniqueMemberNames = useUniqueMemberNames;
   }
 
   public NamingState<ProtoType, KeyType> createChild() {
-    return new NamingState<>(this, itemFactory, dictionary, keyTransform);
+    return new NamingState<>(this, itemFactory, dictionary, keyTransform, useUniqueMemberNames);
   }
 
-  private InternalState findInternalStateFor(ProtoType proto) {
+  private InternalState<ProtoType> findInternalStateFor(ProtoType proto) {
     KeyType key = keyTransform.apply(proto);
-    InternalState result = usedNames.get(key);
+    InternalState<ProtoType> result = usedNames.get(key);
     if (result == null && parent != null) {
       result = parent.findInternalStateFor(proto);
     }
     return result;
   }
 
-  private InternalState getOrCreateInternalStateFor(ProtoType proto) {
+  private InternalState<ProtoType> getOrCreateInternalStateFor(ProtoType proto) {
     // TODO(herhut): Maybe allocate these sparsely and search via state chain.
     KeyType key = keyTransform.apply(proto);
-    InternalState result = usedNames.get(key);
+    InternalState<ProtoType> result = usedNames.get(key);
     if (result == null) {
       if (parent != null) {
-        InternalState parentState = parent.getOrCreateInternalStateFor(proto);
+        InternalState<ProtoType> parentState = parent.getOrCreateInternalStateFor(proto);
         result = parentState.createChild();
       } else {
-        result = new InternalState(itemFactory, null, dictionary);
+        result = new InternalState<>(itemFactory, null, dictionary);
       }
       usedNames.put(key, result);
     }
     return result;
   }
 
-  public DexString getAssignedNameFor(DexString name, ProtoType proto) {
-    InternalState state = findInternalStateFor(proto);
+  private DexString getAssignedNameFor(DexString name, ProtoType proto) {
+    InternalState<ProtoType> state = findInternalStateFor(proto);
     if (state == null) {
       return null;
     }
-    return state.getAssignedNameFor(name);
+    return state.getAssignedNameFor(name, proto);
   }
 
   public DexString assignNewNameFor(DexString original, ProtoType proto, boolean markAsUsed) {
     DexString result = getAssignedNameFor(original, proto);
     if (result == null) {
-      InternalState state = getOrCreateInternalStateFor(proto);
-      result = state.getNameFor(original, markAsUsed);
+      InternalState<ProtoType> state = getOrCreateInternalStateFor(proto);
+      result = state.getNameFor(original, proto, markAsUsed);
     }
     return result;
   }
 
   public void reserveName(DexString name, ProtoType proto) {
-    InternalState state = getOrCreateInternalStateFor(proto);
+    InternalState<ProtoType> state = getOrCreateInternalStateFor(proto);
     state.reserveName(name);
   }
 
   public boolean isReserved(DexString name, ProtoType proto) {
-    InternalState state = findInternalStateFor(proto);
+    InternalState<ProtoType> state = findInternalStateFor(proto);
     if (state == null) {
       return false;
     }
@@ -101,32 +109,34 @@
   }
 
   public boolean isAvailable(DexString original, ProtoType proto, DexString candidate) {
-    InternalState state = findInternalStateFor(proto);
+    InternalState<ProtoType> state = findInternalStateFor(proto);
     if (state == null) {
       return true;
     }
-    assert state.getAssignedNameFor(original) != candidate;
+    assert state.getAssignedNameFor(original, proto) != candidate;
     return state.isAvailable(candidate);
   }
 
   public void addRenaming(DexString original, ProtoType proto, DexString newName) {
-    InternalState state = getOrCreateInternalStateFor(proto);
-    state.addRenaming(original, newName);
+    InternalState<ProtoType> state = getOrCreateInternalStateFor(proto);
+    state.addRenaming(original, proto, newName);
   }
 
-  private static class InternalState {
+  private class InternalState<InternalProtoType extends CachedHashValueDexItem> {
 
     private static final int INITIAL_NAME_COUNT = 1;
-    private final static char[] EMPTY_CHAR_ARRARY = new char[0];
+    private final char[] EMPTY_CHAR_ARRARY = new char[0];
 
     protected final DexItemFactory itemFactory;
-    private final InternalState parentInternalState;
+    private final InternalState<InternalProtoType> parentInternalState;
     private Set<DexString> reservedNames = null;
-    private Map<DexString, DexString> renamings = null;
+    private Table<DexString, InternalProtoType, DexString> renamings = null;
     private int nameCount;
     private final Iterator<String> dictionaryIterator;
 
-    private InternalState(DexItemFactory itemFactory, InternalState parentInternalState,
+    private InternalState(
+        DexItemFactory itemFactory,
+        InternalState<InternalProtoType> parentInternalState,
         Iterator<String> dictionaryIterator) {
       this.itemFactory = itemFactory;
       this.parentInternalState = parentInternalState;
@@ -135,7 +145,9 @@
       this.dictionaryIterator = dictionaryIterator;
     }
 
-    private InternalState(DexItemFactory itemFactory, InternalState parentInternalState,
+    private InternalState(
+        DexItemFactory itemFactory,
+        InternalState<InternalProtoType> parentInternalState,
         List<String> dictionary) {
       this(itemFactory, parentInternalState, dictionary.iterator());
     }
@@ -151,27 +163,40 @@
           && (parentInternalState == null || parentInternalState.isAvailable(name));
     }
 
-    public InternalState createChild() {
-      return new InternalState(itemFactory, this, dictionaryIterator);
+    InternalState<InternalProtoType> createChild() {
+      return new InternalState<>(itemFactory, this, dictionaryIterator);
     }
 
-    public void reserveName(DexString name) {
+    void reserveName(DexString name) {
       if (reservedNames == null) {
         reservedNames = Sets.newIdentityHashSet();
       }
       reservedNames.add(name);
     }
 
-    public DexString getAssignedNameFor(DexString original) {
-      DexString result = renamings == null ? null : renamings.get(original);
+    DexString getAssignedNameFor(DexString original, InternalProtoType proto) {
+      DexString result = null;
+      if (renamings != null) {
+        if (useUniqueMemberNames) {
+          Map<InternalProtoType, DexString> row = renamings.row(original);
+          if (row != null) {
+            // Either not renamed yet (0) or renamed (1). If renamed, return the same renamed name
+            // so that other members with the same name can be renamed to the same renamed name.
+            assert row.values().size() <= 1;
+            result = Iterables.getOnlyElement(row.values(), null);
+          }
+        } else {
+          result = renamings.get(original, proto);
+        }
+      }
       if (result == null && parentInternalState != null) {
-        result = parentInternalState.getAssignedNameFor(original);
+        result = parentInternalState.getAssignedNameFor(original, proto);
       }
       return result;
     }
 
-    public DexString getNameFor(DexString original, boolean markAsUsed) {
-      DexString name = getAssignedNameFor(original);
+    DexString getNameFor(DexString original, InternalProtoType proto, boolean markAsUsed) {
+      DexString name = getAssignedNameFor(original, proto);
       if (name != null) {
         return name;
       }
@@ -179,19 +204,19 @@
         name = itemFactory.createString(nextSuggestedName());
       } while (!isAvailable(name));
       if (markAsUsed) {
-        addRenaming(original, name);
+        addRenaming(original, proto, name);
       }
       return name;
     }
 
-    public void addRenaming(DexString original, DexString newName) {
+    void addRenaming(DexString original, InternalProtoType proto, DexString newName) {
       if (renamings == null) {
-        renamings = HashBiMap.create();
+        renamings = HashBasedTable.create();
       }
-      renamings.put(original, newName);
+      renamings.put(original, proto, newName);
     }
 
-    protected String nextSuggestedName() {
+    String nextSuggestedName() {
       if (dictionaryIterator.hasNext()) {
         return dictionaryIterator.next();
       } else {
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 f36f8a6..9aa73de 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -203,11 +203,30 @@
   }
 
   private void enqueueRootItems(Map<DexItem, ProguardKeepRule> items) {
-    workList.addAll(
-        items.entrySet().stream().map(Action::forRootItem).collect(Collectors.toList()));
+    items.entrySet().forEach(this::enqueueRootItem);
     pinnedItems.addAll(items.keySet());
   }
 
+  private void enqueueRootItem(Map.Entry<DexItem, ProguardKeepRule> root) {
+    DexItem item = root.getKey();
+    KeepReason reason = KeepReason.dueToKeepRule(root.getValue());
+    if (item instanceof DexClass) {
+      DexClass clazz = (DexClass) item;
+      workList.add(Action.markInstantiated(clazz, reason));
+      if (options.forceProguardCompatibility && clazz.hasDefaultInitializer()) {
+        ProguardKeepRule rule = ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
+        proguardCompatibilityWorkList.add(Action.markMethodLive(
+            clazz.getDefaultInitializer(), KeepReason.dueToProguardCompatibilityKeepRule(rule)));
+      }
+    } else if (item instanceof DexEncodedField) {
+      workList.add(Action.markFieldKept((DexEncodedField) item, reason));
+    } else if (item instanceof DexEncodedMethod) {
+      workList.add(Action.markMethodKept((DexEncodedMethod) item, reason));
+    } else {
+      throw new IllegalArgumentException(item.toString());
+    }
+  }
+
   //
   // Things to do with registering events. This is essentially the interface for byte-code
   // traversals.
@@ -372,6 +391,16 @@
     }
 
     @Override
+    public boolean registerConstClass(DexType type) {
+      return registerConstClassOrCheckCast(type);
+    }
+
+    @Override
+    public boolean registerCheckCast(DexType type) {
+      return registerConstClassOrCheckCast(type);
+    }
+
+    @Override
     public boolean registerTypeReference(DexType type) {
       DexType baseType = type.toBaseType(appInfo.dexItemFactory);
       if (baseType.isClassType()) {
@@ -380,6 +409,25 @@
       }
       return false;
     }
+
+    private boolean registerConstClassOrCheckCast(DexType type) {
+      if (options.forceProguardCompatibility) {
+        DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+        if (baseType.isClassType()) {
+          DexClass baseClass = appInfo.definitionFor(baseType);
+          if (baseClass != null) {
+            markClassAsInstantiatedWithCompatRule(baseClass);
+          } else {
+            // This handles reporting of missing classes.
+            markTypeAsLive(baseType);
+          }
+          return true;
+        }
+        return false;
+      } else {
+        return registerTypeReference(type);
+      }
+    }
   }
 
   private DexMethod getInvokeSuperTarget(DexMethod method, DexEncodedMethod currentMethod) {
@@ -439,14 +487,12 @@
         annotations.forEach(this::handleAnnotationOfLiveType);
       }
 
-      // Add all dependent static members to the workqueue.
-      enqueueRootItems(rootSet.getDependentStaticMembers(type));
-
-      // For Proguard compatibility keep the default initializer for live types.
       if (options.forceProguardCompatibility) {
-        if (holder.isProgramClass() && holder.hasDefaultInitializer()) {
-          markClassAsInstantiatedWithCompatRule(holder);
-        }
+        // Add all dependent members to the workqueue.
+        enqueueRootItems(rootSet.getDependentItems(type));
+      } else {
+        // Add all dependent static members to the workqueue.
+        enqueueRootItems(rootSet.getDependentStaticMembers(type));
       }
     }
   }
@@ -566,6 +612,7 @@
     if (!instantiatedTypes.add(clazz.type, reason)) {
       return;
     }
+
     collectProguardCompatibilityRule(reason);
     if (Log.ENABLED) {
       Log.verbose(getClass(), "Class `%s` is instantiated, processing...", clazz);
@@ -1286,20 +1333,6 @@
       return new Action(Kind.MARK_FIELD_KEPT, method, null, reason);
     }
 
-    public static Action forRootItem(Map.Entry<DexItem, ProguardKeepRule> root) {
-      DexItem item = root.getKey();
-      KeepReason reason = KeepReason.dueToKeepRule(root.getValue());
-      if (item instanceof DexClass) {
-        return markInstantiated((DexClass) item, reason);
-      } else if (item instanceof DexEncodedField) {
-        return markFieldKept((DexEncodedField) item, reason);
-      } else if (item instanceof DexEncodedMethod) {
-        return markMethodKept((DexEncodedMethod) item, reason);
-      } else {
-        throw new IllegalArgumentException(item.toString());
-      }
-    }
-
     private enum Kind {
       MARK_REACHABLE_VIRTUAL,
       MARK_REACHABLE_INTERFACE,
diff --git a/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java b/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java
index 46669ec..c37102c 100644
--- a/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java
@@ -348,7 +348,6 @@
     // the fallthrough label. This can introduce critical edges. Therefore, we split critical
     // edges to maintain our edge-split form.
     code.splitCriticalEdges();
-    code.traceBlocks();
     assert code.isConsistentSSA();
   }
 
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index 8ff72c7..c4351ce 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -4542,7 +4542,7 @@
           // java.lang.AssertionError: Failed to load serialization resource file: serialization/com/google/jctf/test/lib/java/util/concurrent/PriorityBlockingQueue/serialization/PriorityBlockingQueue_serialization_A01.golden.0.ser
 
           .put("lang.ThreadGroup.destroy.ThreadGroup_destroy_A01",
-              match(D8_COMPILER, runtimesUpTo(Version.V6_0_1)))
+              match(runtimesUpTo(Version.V6_0_1)))
           // 1) t02
           // java.lang.IllegalThreadStateException: Thread group still contains threads: Test group
           // 2) t04
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index a410f07..ebb8100 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ToolHelper.DexVm.Kind;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 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;
@@ -67,6 +68,7 @@
 
   @Rule
   public ExpectedException thrown = ExpectedException.none();
+  private boolean expectedException = false;
 
   public enum DexTool {
     JACK,
@@ -77,7 +79,8 @@
   public enum CompilerUnderTest {
     D8,
     R8,
-    R8_AFTER_D8 // refers to the R8 (default: debug) step but implies a previous D8 step as well
+    R8_AFTER_D8, // refers to the R8 (default: debug) step but implies a previous D8 step as well
+    D8_AFTER_R8CF
   }
 
   private static final String ART_TESTS_DIR = "tests/2017-10-04/art";
@@ -145,10 +148,6 @@
           .put("974-verify-interface-super", AndroidApiLevel.N)
           // Desugaring of interface private methods is not yet supported.
           .put("975-iface-private", AndroidApiLevel.N)
-          // The extended check for overlapping long registers cause this to run out of registers.
-          .put("421-large-frame", AndroidApiLevel.N)
-          // The extended check for overlapping long registers cause this to run out of registers.
-          .put("551-checker-shifter-operand", AndroidApiLevel.N)
           .build();
 
   // Tests that timeout when run with Art.
@@ -554,11 +553,18 @@
           // This test relies on specific field access patterns, which we rewrite.
           .put("064-field-access",
               TestCondition.match(
-                  TestCondition.R8_NOT_AFTER_D8_COMPILER,
+                  TestCondition.R8DEX_NOT_AFTER_D8_COMPILER,
                   TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           .put("064-field-access",
               TestCondition.match(
-                  TestCondition.R8_COMPILER,
+                  TestCondition.R8DEX_COMPILER,
+                  TestCondition.runtimes(
+                      DexVm.Version.DEFAULT, DexVm.Version.V7_0_0, DexVm.Version.V6_0_1,
+                      DexVm.Version.V5_1_1)))
+          .put("064-field-access",
+              TestCondition.match(
+                  TestCondition.tools(DexTool.NONE),
+                  TestCondition.D8_AFTER_R8CF_COMPILER,
                   TestCondition.runtimes(
                       DexVm.Version.DEFAULT, DexVm.Version.V7_0_0, DexVm.Version.V6_0_1,
                       DexVm.Version.V5_1_1)))
@@ -608,15 +614,10 @@
           .put("600-verifier-fails",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // VFY: args to if-eq/if-ne must both be refs or cat1.
-          .put("134-reg-promotion",
+          .put(
+              "134-reg-promotion",
               TestCondition.match(
-                  TestCondition.R8_COMPILER,
-                  TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
-          .put("134-reg-promotion",
-              TestCondition.match(
-                  TestCondition.tools(DexTool.NONE, DexTool.JACK),
-                  TestCondition.D8_COMPILER,
-                  TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
+                  TestCondition.R8DEX_COMPILER, TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
           // VFY: tried to get class from non-ref register.
           .put("506-verify-aput",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
@@ -647,7 +648,7 @@
           .put("551-implicit-null-checks",
               TestCondition.match(
                   TestCondition.tools(DexTool.NONE, DexTool.DX),
-                  TestCondition.R8_COMPILER,
+                  TestCondition.R8DEX_COMPILER,
                   TestCondition.runtimes(DexVm.Version.V5_1_1)))
           // Contains a method (B.<init>) which pass too few arguments to invoke. Also, contains an
           // iput on a static field.
@@ -679,6 +680,7 @@
           // Switch regression still present in Dalvik 4.0.4.
           .put("095-switch-MAX_INT",
               TestCondition.match(
+                  TestCondition.tools(DexTool.DX),
                   TestCondition.D8_COMPILER,
                   TestCondition.runtimes(DexVm.Version.V4_0_4)))
           .build();
@@ -711,7 +713,7 @@
           .put("474-fp-sub-neg",
               TestCondition.match(
                   TestCondition.tools(DexTool.NONE, DexTool.JACK),
-                  TestCondition.D8_COMPILER,
+                  TestCondition.D8_NOT_AFTER_R8CF_COMPILER,
                   TestCondition.runtimes(DexVm.Version.V6_0_1)))
           .build();
 
@@ -766,13 +768,7 @@
               TestCondition.match(
                   TestCondition.runtimes(DexVm.Version.V4_0_4, DexVm.Version.V4_4_4,
                       DexVm.Version.V5_1_1, DexVm.Version.V6_0_1, DexVm.Version.V7_0_0)))
-          .put(
-              "454-get-vreg",
-              TestCondition.match(
-                  TestCondition.tools(DexTool.NONE),
-                  TestCondition.D8_COMPILER,
-                  TestCondition.runtimes(DexVm.Version.DEFAULT)))
-          .put("454-get-vreg", TestCondition.match(TestCondition.R8_COMPILER))
+          .put("454-get-vreg", TestCondition.match(TestCondition.R8DEX_COMPILER))
           // Fails: regs_jni.cc:42] Check failed: GetVReg(m, 0, kIntVReg, &value)
           // The R8/D8 code does not put values in the same registers as the tests expects.
           .put(
@@ -780,20 +776,14 @@
               TestCondition.match(
                   TestCondition.runtimes(DexVm.Version.V4_0_4, DexVm.Version.V4_4_4,
                       DexVm.Version.V5_1_1, DexVm.Version.V6_0_1, DexVm.Version.V7_0_0)))
-          .put(
-              "457-regs",
-              TestCondition.match(
-                  TestCondition.tools(DexTool.NONE),
-                  TestCondition.D8_COMPILER,
-                  TestCondition.runtimes(DexVm.Version.DEFAULT)))
-          .put("457-regs", TestCondition.match(TestCondition.R8_COMPILER))
+          .put("457-regs", TestCondition.match(TestCondition.R8DEX_COMPILER))
           // Class not found.
           .put("529-checker-unresolved", TestCondition.any())
           // Fails: env_long_ref.cc:44] Check failed: GetVReg(m, 1, kReferenceVReg, &value)
           // The R8/D8 code does not produce values in the same registers as the tests expects in
           // the stack frame for TestCase.testCase checked by the native Main.lookForMyRegisters
           // (v1 vs v0).
-          .put("543-env-long-ref", TestCondition.match(TestCondition.R8_COMPILER))
+          .put("543-env-long-ref", TestCondition.match(TestCondition.R8DEX_COMPILER))
           // Array index out of bounds exception.
           .put("555-UnsafeGetLong-regression", TestCondition.any())
           // Array index out of bounds exception.
@@ -814,9 +804,9 @@
           // Main.testThisWithInstanceCall checked by the native Main.doNativeCallRef (v0 vs. v1 and
           // only 1 register instead fof 2).
           .put("461-get-reference-vreg", TestCondition.match(TestCondition.R8_COMPILER))
-          // This test uses register r1 in method that is declared to only use 1 register (r0). This
-          // is in dex code which D8 does not convert. Therefore the error is a verification error
-          // at runtime and that is expected.
+          // This test uses register r1 in method that is declared to only use 1 register (r0).
+          // This is in dex code which D8 does not convert and which R8/CF does not process.
+          // Therefore the error is a verification error at runtime and that is expected.
           .put("142-classloader2", TestCondition.match(TestCondition.D8_COMPILER))
           // Invoke-custom is supported by D8 and R8, but it can only run on our newest version
           // of art.
@@ -839,9 +829,6 @@
           .put("973-default-multidex", beforeAndroidN) // --min-sdk = 24
           .put("974-verify-interface-super", beforeAndroidN) // --min-sdk = 24
           .put("975-iface-private", beforeAndroidN) // --min-sdk = 24
-          // These tests have min-api set to N.
-          .put("421-large-frame", beforeAndroidN) // --min-sdk = 24
-          .put("551-checker-shifter-operand", beforeAndroidN) // --min-sdk = 24
           // Uses dex file version 37 and therefore only runs on Android N and above.
           .put("972-iface-super-multidex",
               TestCondition.match(TestCondition.tools(DexTool.JACK, DexTool.DX),
@@ -862,9 +849,9 @@
       new ImmutableListMultimap.Builder<String, TestCondition>()
           // Contains two methods with the same name and signature but different code.
           .put("097-duplicate-method", TestCondition.any())
-          // Contains a method (B.<init>) which pass too few arguments to invoke. Also, contains an
-          // iput on a static field.
-          .put("600-verifier-fails", TestCondition.match(TestCondition.R8_COMPILER))
+          // Dex code contains a method (B.<init>) which pass too few arguments to invoke, and it
+          // also contains an iput on a static field.
+          .put("600-verifier-fails", TestCondition.match(TestCondition.R8DEX_COMPILER))
           // Contains a method that falls off the end without a return.
           .put("606-erroneous-class", TestCondition.match(
               TestCondition.tools(DexTool.JACK),
@@ -873,11 +860,15 @@
               TestCondition.tools(DexTool.DX),
               TestCondition.R8_NOT_AFTER_D8_COMPILER,
               LEGACY_RUNTIME))
-          // Contains an illegal invoke that R8 will fail to compile.
-          .put("594-invoke-super", TestCondition.match(
-              TestCondition.R8_COMPILER))
-          .put("974-verify-interface-super", TestCondition.match(
-              TestCondition.R8_COMPILER))
+          // Dex input contains an illegal InvokeSuper in Z.foo() to Y.foo()
+          // that R8 will fail to compile.
+          .put("594-invoke-super", TestCondition.match(TestCondition.R8DEX_COMPILER))
+          .put("974-verify-interface-super", TestCondition.match(TestCondition.R8DEX_COMPILER))
+          // R8 generates too large code in Goto.bigGoto(). b/74327727
+          .put("003-omnibus-opcodes", TestCondition.match(TestCondition.D8_AFTER_R8CF_COMPILER))
+          // Contains a subset of JUnit which collides with library definitions of JUnit.
+          .put("021-string2", TestCondition.match(TestCondition.D8_AFTER_R8CF_COMPILER))
+          .put("082-inline-execute", TestCondition.match(TestCondition.D8_AFTER_R8CF_COMPILER))
           .build();
 
   // Tests that are invalid dex files and on which R8/D8 fails and that is OK.
@@ -889,12 +880,12 @@
           // only one A$B class because of a custom build script that merges them.
           .put("121-modifiers", TestCondition.match(TestCondition.tools(DexTool.NONE)))
           // This test uses register r1 in method that is declared to only use 1 register (r0).
-          .put("142-classloader2", TestCondition.match(TestCondition.R8_COMPILER))
+          .put("142-classloader2", TestCondition.match(TestCondition.R8DEX_COMPILER))
           // This test uses an uninitialized register.
-          .put("471-uninitialized-locals", TestCondition.match(TestCondition.R8_COMPILER))
+          .put("471-uninitialized-locals", TestCondition.match(TestCondition.R8DEX_COMPILER))
           // This test is starting from invalid dex code. It splits up a double value and uses
           // the first register of a double with the second register of another double.
-          .put("800-smali", TestCondition.match(TestCondition.R8_COMPILER))
+          .put("800-smali", TestCondition.match(TestCondition.R8DEX_COMPILER))
           // Contains a loop in the class hierarchy.
           .put("804-class-extends-itself", TestCondition.any())
           // These tests have illegal class flag combinations, so we reject them.
@@ -1115,18 +1106,28 @@
     // Has missing classes.
     private final boolean hasMissingClasses;
 
-    TestSpecification(String name, DexTool dexTool,
-        File directory, boolean skipArt, boolean skipTest, boolean failsWithX8,
-        boolean failsWithArt, boolean failsWithArtOutput, boolean failsWithArtOriginalOnly,
-        String nativeLibrary, boolean expectedToFailWithX8, boolean outputMayDiffer,
-        boolean disableInlining, boolean hasMissingClasses) {
+    TestSpecification(
+        String name,
+        DexTool dexTool,
+        File directory,
+        boolean skipArt,
+        boolean skipTest,
+        boolean failsWithX8,
+        boolean failsWithArt,
+        boolean failsWithArtOutput,
+        boolean failsWithArtOriginalOnly,
+        String nativeLibrary,
+        boolean expectedToFailWithX8,
+        boolean outputMayDiffer,
+        boolean disableInlining,
+        boolean hasMissingClasses,
+        DexVm dexVm) {
       this.name = name;
       this.dexTool = dexTool;
       this.nativeLibrary = nativeLibrary;
       this.directory = directory;
       this.skipArt = skipArt;
-      this.skipTest =
-          skipTest || (ToolHelper.isWindows() && ToolHelper.getDexVm().getKind() == Kind.HOST);
+      this.skipTest = skipTest || (ToolHelper.isWindows() && dexVm.getKind() == Kind.HOST);
       this.failsWithX8 = failsWithX8;
       this.failsWithArt = failsWithArt;
       this.failsWithArtOutput = failsWithArtOutput;
@@ -1137,10 +1138,30 @@
       this.hasMissingClasses = hasMissingClasses;
     }
 
-    TestSpecification(String name, DexTool dexTool, File directory, boolean skipArt,
-        boolean failsWithArt, boolean disableInlining) {
-      this(name, dexTool, directory, skipArt,
-          false, false, failsWithArt, false, false, null, false, false, disableInlining, false);
+    TestSpecification(
+        String name,
+        DexTool dexTool,
+        File directory,
+        boolean skipArt,
+        boolean failsWithArt,
+        boolean disableInlining,
+        DexVm dexVm) {
+      this(
+          name,
+          dexTool,
+          directory,
+          skipArt,
+          false,
+          false,
+          failsWithArt,
+          false,
+          false,
+          null,
+          false,
+          false,
+          disableInlining,
+          false,
+          dexVm);
     }
 
     public File resolveFile(String name) {
@@ -1195,7 +1216,8 @@
   }
 
   private static Map<SpecificationKey, TestSpecification> getTestsMap(
-      CompilerUnderTest compilerUnderTest, CompilationMode compilationMode, DexVm.Version version) {
+      CompilerUnderTest compilerUnderTest, CompilationMode compilationMode, DexVm dexVm) {
+    DexVm.Version version = dexVm.getVersion();
     File defaultArtTestDir = new File(ART_TESTS_DIR);
     File legacyArtTestDir = new File(ART_LEGACY_TESTS_DIR);
     if (!defaultArtTestDir.exists() || !legacyArtTestDir.exists()) {
@@ -1255,12 +1277,11 @@
         failsWithArt.addAll(tmpSet);
       }
 
-      if (!ToolHelper.isDefaultDexVm(ToolHelper.getDexVm())) {
+      if (!ToolHelper.isDefaultDexVm(dexVm)) {
         // Generally failing when not TOT art.
         failsWithArt.addAll(expectedToFailRunWithArtNonDefault);
         // Version specific failures
-        failsWithArt
-            .addAll(expectedToFailRunWithArtVersion.get(ToolHelper.getDexVm().getVersion()));
+        failsWithArt.addAll(expectedToFailRunWithArtVersion.get(version));
       }
 
       // Collect the tests failing with output differences in Art.
@@ -1303,7 +1324,8 @@
                 expectedToFailWithCompilerSet.contains(name),
                 outputMayDiffer.contains(name),
                 requireInliningToBeDisabled.contains(name),
-                hasMissingClasses.contains(name)));
+                hasMissingClasses.contains(name),
+                dexVm));
       }
     }
     return data;
@@ -1317,6 +1339,7 @@
         break;
       case D8:
       case R8_AFTER_D8:
+      case D8_AFTER_R8CF:
         compilationMode = CompilationMode.DEBUG;
         break;
       default:
@@ -1356,11 +1379,6 @@
     return builder;
   }
 
-  protected void runArtTest() throws Throwable {
-    // Use the default dex VM specified.
-    runArtTest(ToolHelper.getDexVm(), CompilerUnderTest.R8);
-  }
-
   protected void runArtTest(CompilerUnderTest compilerUnderTest) throws Throwable {
     // Use the default dex VM specified.
     runArtTest(ToolHelper.getDexVm(), compilerUnderTest);
@@ -1390,6 +1408,97 @@
         CompilationFailedException {
     assert mode != null;
     switch (compilerUnderTest) {
+      case D8_AFTER_R8CF:
+        {
+          assert keepRulesFile == null : "Keep-rules file specified for D8.";
+
+          List<ProgramResource> dexInputs = new ArrayList<>();
+          List<ProgramResource> cfInputs = new ArrayList<>();
+          for (String f : fileNames) {
+            Path p = Paths.get(f);
+            if (FileUtils.isDexFile(p)) {
+              dexInputs.add(ProgramResource.fromFile(ProgramResource.Kind.DEX, p));
+            } else if (FileUtils.isClassFile(p)) {
+              cfInputs.add(ProgramResource.fromFile(ProgramResource.Kind.CF, p));
+            } else {
+              assert FileUtils.isArchive(p);
+              ArchiveProgramResourceProvider provider =
+                  ArchiveProgramResourceProvider.fromArchive(p);
+
+              try {
+                for (ProgramResource pr : provider.getProgramResources()) {
+                  if (pr.getKind() == ProgramResource.Kind.DEX) {
+                    dexInputs.add(pr);
+                  } else {
+                    assert pr.getKind() == ProgramResource.Kind.CF;
+                    cfInputs.add(pr);
+                  }
+                }
+              } catch (ResourceException e) {
+                throw new CompilationException(e);
+              }
+            }
+          }
+
+          D8Command.Builder builder =
+              D8Command.builder()
+                  .setMode(mode)
+                  .addProgramResourceProvider(
+                      new ProgramResourceProvider() {
+                        @Override
+                        public Collection<ProgramResource> getProgramResources()
+                            throws ResourceException {
+                          return dexInputs;
+                        }
+                      })
+                  .setOutput(Paths.get(resultPath), OutputMode.DexIndexed);
+
+          Origin cfOrigin =
+              new Origin(Origin.root()) {
+                @Override
+                public String part() {
+                  return "R8/CF";
+                }
+              };
+
+          R8Command.Builder r8builder =
+              R8Command.builder()
+                  .setMode(mode)
+                  .setProgramConsumer(
+                      new ClassFileConsumer() {
+
+                        @Override
+                        public synchronized void accept(
+                            byte[] data, String descriptor, DiagnosticsHandler handler) {
+                          builder.addClassProgramData(data, cfOrigin);
+                        }
+
+                        @Override
+                        public void finished(DiagnosticsHandler handler) {}
+                      })
+                  .addProgramResourceProvider(
+                      new ProgramResourceProvider() {
+                        @Override
+                        public Collection<ProgramResource> getProgramResources()
+                            throws ResourceException {
+                          return cfInputs;
+                        }
+                      });
+
+          AndroidApiLevel minSdkVersion = needMinSdkVersion.get(name);
+          if (minSdkVersion != null) {
+            builder.setMinApiLevel(minSdkVersion.getLevel());
+            r8builder.setMinApiLevel(minSdkVersion.getLevel());
+            builder.addLibraryFiles(ToolHelper.getAndroidJar(minSdkVersion));
+            r8builder.addLibraryFiles(ToolHelper.getAndroidJar(minSdkVersion));
+          } else {
+            builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.getDefault()));
+            r8builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.getDefault()));
+          }
+          ToolHelper.runR8(r8builder.build(), options -> options.ignoreMissingClasses = true);
+          D8.run(builder.build());
+          break;
+        }
       case D8: {
         assert keepRulesFile == null : "Keep-rules file specified for D8.";
         D8Command.Builder builder =
@@ -1496,12 +1605,17 @@
   }
 
   private static BiFunction<Outcome, Boolean, TestSpecification> jctfOutcomeToSpecification(
-      String name, DexTool dexTool, File resultDir) {
-    return (outcome, noInlining) -> new TestSpecification(name, dexTool, resultDir,
-        outcome == JctfTestSpecifications.Outcome.TIMEOUTS_WITH_ART
-            || outcome == JctfTestSpecifications.Outcome.FLAKY_WITH_ART,
-        outcome == JctfTestSpecifications.Outcome.FAILS_WITH_ART,
-        noInlining);
+      String name, DexTool dexTool, File resultDir, DexVm dexVm) {
+    return (outcome, noInlining) ->
+        new TestSpecification(
+            name,
+            dexTool,
+            resultDir,
+            outcome == JctfTestSpecifications.Outcome.TIMEOUTS_WITH_ART
+                || outcome == JctfTestSpecifications.Outcome.FLAKY_WITH_ART,
+            outcome == JctfTestSpecifications.Outcome.FAILS_WITH_ART,
+            noInlining,
+            dexVm);
   }
 
   protected void runJctfTest(CompilerUnderTest compilerUnderTest, String classFilePath,
@@ -1518,9 +1632,13 @@
 
     File resultDir = temp.newFolder(firstCompilerUnderTest.toString().toLowerCase() + "-output");
 
-    TestSpecification specification = JctfTestSpecifications.getExpectedOutcome(
-        name, firstCompilerUnderTest, dexVm, compilationMode,
-        jctfOutcomeToSpecification(name, DexTool.NONE, resultDir));
+    TestSpecification specification =
+        JctfTestSpecifications.getExpectedOutcome(
+            name,
+            firstCompilerUnderTest,
+            dexVm,
+            compilationMode,
+            jctfOutcomeToSpecification(name, DexTool.NONE, resultDir, dexVm));
 
     if (specification.skipTest) {
       return;
@@ -1603,9 +1721,13 @@
               .collect(Collectors.toList());
       File r8ResultDir = temp.newFolder("r8-output");
       compilationMode = CompilationMode.DEBUG;
-      specification = JctfTestSpecifications.getExpectedOutcome(
-          name, CompilerUnderTest.R8_AFTER_D8, dexVm, compilationMode,
-          jctfOutcomeToSpecification(name, DexTool.DX, r8ResultDir));
+      specification =
+          JctfTestSpecifications.getExpectedOutcome(
+              name,
+              CompilerUnderTest.R8_AFTER_D8,
+              dexVm,
+              compilationMode,
+              jctfOutcomeToSpecification(name, DexTool.DX, r8ResultDir, dexVm));
       if (specification.skipTest) {
         return;
       }
@@ -1661,7 +1783,7 @@
     }
 
     ArtCommandBuilder builder = buildArtCommand(processedFile, specification, dexVm);
-    if (ToolHelper.getDexVm().isNewerThan(DexVm.ART_4_4_4_HOST)) {
+    if (dexVm.isNewerThan(DexVm.ART_4_4_4_HOST)) {
       builder.appendArtOption("-Ximage:/system/non/existent/image.art");
     }
     for (String s : ToolHelper.getBootLibs()) {
@@ -1671,7 +1793,7 @@
     builder.appendProgramArgument(fullClassName);
 
     if (specification.failsWithArt) {
-      thrown.expect(AssertionError.class);
+      expectException(AssertionError.class);
     }
 
     try {
@@ -1687,8 +1809,7 @@
     }
   }
 
-  protected void runArtTest(DexVm version, CompilerUnderTest compilerUnderTest)
-      throws Throwable {
+  protected void runArtTest(DexVm dexVm, CompilerUnderTest compilerUnderTest) throws Throwable {
     CompilerUnderTest firstCompilerUnderTest =
         compilerUnderTest == CompilerUnderTest.R8_AFTER_D8
             ? CompilerUnderTest.D8
@@ -1697,11 +1818,11 @@
     CompilationMode compilationMode = defaultCompilationMode(compilerUnderTest);
 
     TestSpecification specification =
-        getTestsMap(firstCompilerUnderTest, compilationMode, version.getVersion())
+        getTestsMap(firstCompilerUnderTest, compilationMode, dexVm)
             .get(new SpecificationKey(name, toolchain));
 
     if (specification == null) {
-      if (version.getVersion() == DexVm.Version.DEFAULT) {
+      if (dexVm.getVersion() == DexVm.Version.DEFAULT) {
         throw new RuntimeException("Test " + name + " has no specification for toolchain"
             + toolchain + ".");
       } else {
@@ -1715,7 +1836,7 @@
       return;
     }
 
-    if (specification.nativeLibrary != null && ToolHelper.getDexVm().getKind() == Kind.TARGET) {
+    if (specification.nativeLibrary != null && dexVm.getKind() == Kind.TARGET) {
       // JNI tests not yet supported for devices
       return;
     }
@@ -1746,12 +1867,17 @@
     File resultDir = temp.newFolder(firstCompilerUnderTest.toString().toLowerCase() + "-output");
 
     runArtTestDoRunOnArt(
-        version, firstCompilerUnderTest, specification, fileNames, resultDir, compilationMode);
+        dexVm, firstCompilerUnderTest, specification, fileNames, resultDir, compilationMode);
 
     if (compilerUnderTest == CompilerUnderTest.R8_AFTER_D8) {
+      if (expectedException) {
+        // The expected exception was not thrown while running D8.
+        return;
+      }
+
       compilationMode = CompilationMode.DEBUG;
       specification =
-          getTestsMap(CompilerUnderTest.R8_AFTER_D8, compilationMode, version.getVersion())
+          getTestsMap(CompilerUnderTest.R8_AFTER_D8, compilationMode, dexVm)
               .get(new SpecificationKey(name, DexTool.DX));
 
       if (specification == null) {
@@ -1771,7 +1897,7 @@
       resultDir = temp.newFolder("r8-output");
 
       runArtTestDoRunOnArt(
-          version, CompilerUnderTest.R8, specification, fileNames, resultDir, compilationMode);
+          dexVm, CompilerUnderTest.R8, specification, fileNames, resultDir, compilationMode);
     }
   }
 
@@ -1796,7 +1922,7 @@
       CompilationMode compilationMode)
       throws Throwable {
     if (specification.expectedToFailWithX8) {
-      thrown.expect(CompilationError.class);
+      expectException(CompilationError.class);
       try {
         executeCompilerUnderTest(
             compilerUnderTest, fileNames, resultDir.getCanonicalPath(), compilationMode,
@@ -1809,7 +1935,7 @@
       System.err.println("Should have failed R8/D8 compilation with a CompilationError.");
       return;
     } else if (specification.failsWithX8) {
-      thrown.expect(Throwable.class);
+      expectException(Throwable.class);
       executeCompilerUnderTest(
           compilerUnderTest, fileNames, resultDir.getCanonicalPath(), compilationMode,
           specification.disableInlining, specification.hasMissingClasses);
@@ -1842,7 +1968,7 @@
       String expected =
           com.google.common.io.Files.asCharSource(expectedFile, Charsets.UTF_8).read();
       if (specification.failsWithArt) {
-        thrown.expect(AssertionError.class);
+        expectException(AssertionError.class);
       }
 
       ArtCommandBuilder builder = buildArtCommand(processedFile, specification, version);
@@ -1879,7 +2005,7 @@
           // produces.
           originalFile = specification.resolveFile(specification.name + ".jar");
           if (specification.failsWithArtOriginalOnly) {
-            thrown.expect(AssertionError.class);
+            expectException(AssertionError.class);
           }
           builder = buildArtCommand(originalFile, specification, version);
           expected = ToolHelper.runArt(builder);
@@ -1889,7 +2015,7 @@
           }
         }
         if (specification.failsWithArtOutput) {
-          thrown.expect(ComparisonFailure.class);
+          expectException(ComparisonFailure.class);
         }
         if (!specification.outputMayDiffer) {
           assertEquals(expected, output);
@@ -1898,6 +2024,11 @@
     }
   }
 
+  private void expectException(Class<? extends Throwable> exception) {
+    thrown.expect(exception);
+    expectedException = true;
+  }
+
   private void failWithDexDiff(File originalFile, File processedFile)
       throws IOException, ExecutionException {
     DexInspector inspectOriginal =
diff --git a/src/test/java/com/android/tools/r8/ResourceShrinkerTest.java b/src/test/java/com/android/tools/r8/ResourceShrinkerTest.java
new file mode 100644
index 0000000..301f78f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ResourceShrinkerTest.java
@@ -0,0 +1,245 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.utils.AndroidApp;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Tests for resource shrinker analyzer. This is checking that dex files are processed correctly.
+ */
+public class ResourceShrinkerTest extends TestBase {
+
+  @Rule
+  public TemporaryFolder tmp = new TemporaryFolder();
+
+  private static class TrackAll implements ResourceShrinker.ReferenceChecker {
+    Set<Integer> integers = Sets.newHashSet();
+    Set<String> strings = Sets.newHashSet();
+    List<List<String>> fields = Lists.newArrayList();
+    List<List<String>> methods = Lists.newArrayList();
+
+    @Override
+    public boolean shouldProcess(String internalName) {
+      return !internalName.equals(ResourceClassToSkip.class.getName());
+    }
+
+    @Override
+    public void referencedInt(int value) {
+      integers.add(value);
+    }
+
+    @Override
+    public void referencedString(String value) {
+      strings.add(value);
+    }
+
+    @Override
+    public void referencedStaticField(String internalName, String fieldName) {
+      fields.add(Lists.newArrayList(internalName, fieldName));
+    }
+
+    @Override
+    public void referencedMethod(String internalName, String methodName, String methodDescriptor) {
+      if (Objects.equals(internalName, "java/lang/Object")
+          && Objects.equals(methodName, "<init>")) {
+        return;
+      }
+      methods.add(Lists.newArrayList(internalName, methodName, methodDescriptor));
+    }
+  }
+
+  private static class EmptyClass {
+  }
+
+  @Test
+  public void testEmptyClass()
+      throws CompilationFailedException, IOException, ExecutionException, CompilationException {
+    TrackAll analysis = runAnalysis(EmptyClass.class);
+
+    assertThat(analysis.integers, is(Sets.newHashSet()));
+    assertThat(analysis.strings, is(Sets.newHashSet()));
+    assertThat(analysis.fields, is(Lists.newArrayList()));
+    assertThat(analysis.methods, is(Lists.newArrayList()));
+  }
+
+  private static class ConstInCode {
+    public void foo() {
+      int i = 10;
+      System.out.print(i);
+      System.out.print(11);
+      String s = "my_layout";
+      System.out.print("another_layout");
+    }
+  }
+
+  @Test
+  public void testConstsAndFieldAndMethods()
+      throws CompilationFailedException, IOException, ExecutionException, CompilationException {
+    TrackAll analysis = runAnalysis(ConstInCode.class);
+
+    assertThat(analysis.integers, is(Sets.newHashSet(10, 11)));
+    assertThat(analysis.strings, is(Sets.newHashSet("my_layout", "another_layout")));
+
+    assertEquals(3, analysis.fields.size());
+    assertThat(analysis.fields.get(0), is(Lists.newArrayList("java/lang/System", "out")));
+    assertThat(analysis.fields.get(1), is(Lists.newArrayList("java/lang/System", "out")));
+    assertThat(analysis.fields.get(2), is(Lists.newArrayList("java/lang/System", "out")));
+
+    assertEquals(3, analysis.methods.size());
+    assertThat(analysis.methods.get(0),
+        is(Lists.newArrayList("java/io/PrintStream", "print", "(I)V")));
+    assertThat(analysis.methods.get(1),
+        is(Lists.newArrayList("java/io/PrintStream", "print", "(I)V")));
+    assertThat(analysis.methods.get(2),
+        is(Lists.newArrayList("java/io/PrintStream", "print", "(Ljava/lang/String;)V")));
+  }
+
+  @SuppressWarnings("unused")
+  private static class StaticFields {
+    static final String sStringValue = "staticValue";
+    static final int sIntValue = 10;
+    static final int[] sIntArrayValue = {11, 12, 13};
+    static final String[] sStringArrayValue = {"a", "b", "c"};
+  }
+
+  @Test
+  public void testStaticValues()
+      throws CompilationFailedException, IOException, ExecutionException, CompilationException {
+    TrackAll analysis = runAnalysis(StaticFields.class);
+
+    assertThat(analysis.integers, hasItems(10, 11, 12, 13));
+    assertThat(analysis.strings, hasItems("staticValue", "a", "b", "c"));
+    assertThat(analysis.fields, is(Lists.newArrayList()));
+    assertThat(analysis.methods, is(Lists.newArrayList()));
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  private @interface IntAnnotation {
+    int value() default 10;
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  private @interface OuterAnnotation {
+    IntAnnotation inner() default @IntAnnotation(11);
+  }
+
+  @IntAnnotation(42)
+  private static class Annotated {
+    @OuterAnnotation
+    Object defaultAnnotated = new Object();
+    @IntAnnotation(12)
+    Object withValueAnnotated = new Object();
+    @IntAnnotation(13)
+    static Object staticValueAnnotated = new Object();
+
+    @IntAnnotation(14)
+    public void annotatedPublic() {
+    }
+
+    @IntAnnotation(15)
+    public void annotatedPrivate() {
+    }
+  }
+
+  @Test
+  public void testAnnotations()
+      throws CompilationFailedException, IOException, ExecutionException, CompilationException {
+    TrackAll analysis = runAnalysis(IntAnnotation.class, OuterAnnotation.class, Annotated.class);
+
+    assertThat(analysis.integers, hasItems(10, 11, 12, 13, 14, 15, 42));
+    assertThat(analysis.strings, is(Sets.newHashSet()));
+    assertThat(analysis.fields, is(Lists.newArrayList()));
+    assertThat(analysis.methods, is(Lists.newArrayList()));
+  }
+
+  private static class ResourceClassToSkip {
+    int[] i = {100, 101, 102};
+  }
+
+  private static class ToProcess {
+    int[] i = {10, 11, 12};
+    String[] s = {"10", "11", "12"};
+  }
+
+  @Test
+  public void testWithSkippingSome()
+      throws ExecutionException, CompilationFailedException, CompilationException, IOException {
+    TrackAll analysis = runAnalysis(ResourceClassToSkip.class, ToProcess.class);
+
+    assertThat(analysis.integers, hasItems(10, 11, 12));
+    assertThat(analysis.strings, is(Sets.newHashSet("10", "11", "12")));
+    assertThat(analysis.fields, is(Lists.newArrayList()));
+    assertThat(analysis.methods, is(Lists.newArrayList()));
+  }
+
+  @Test
+  public void testPayloadBeforeFillArrayData() throws Exception {
+    SmaliBuilder builder = new SmaliBuilder("Test");
+    builder.addMainMethod(
+        2,
+        "goto :start",
+        "",
+        ":array_data",
+        ".array-data 4",
+        "  4 5 6",
+        ".end array-data",
+        "",
+        ":start",
+        "const/4 v1, 3",
+        "new-array v0, v1, [I",
+        "fill-array-data v0, :array_data",
+        "return-object v0"
+    );
+    AndroidApp app =
+        AndroidApp.builder().addDexProgramData(builder.compile(), Origin.unknown()).build();
+    TrackAll analysis = runOnApp(app);
+
+    assertThat(analysis.integers, hasItems(4, 5, 6));
+    assertThat(analysis.strings, is(Sets.newHashSet()));
+    assertThat(analysis.fields, is(Lists.newArrayList()));
+    assertThat(analysis.methods, is(Lists.newArrayList()));
+  }
+
+  private TrackAll runAnalysis(Class<?>... classes)
+      throws IOException, CompilationException, ExecutionException, CompilationFailedException {
+    AndroidApp app = readClasses(classes);
+    return runOnApp(app);
+  }
+
+  private TrackAll runOnApp(AndroidApp app)
+      throws IOException, ExecutionException, CompilationFailedException, CompilationException {
+    AndroidApp outputApp = compileWithD8(app);
+    Path outputDex = tmp.newFolder().toPath().resolve("classes.dex");
+    outputApp.writeToDirectory(outputDex.getParent(), OutputMode.DexIndexed);
+
+    ProgramResourceProvider provider =
+        () -> Lists.newArrayList(ProgramResource.fromFile(ProgramResource.Kind.DEX, outputDex));
+    ResourceShrinker.Command command =
+        new ResourceShrinker.Builder().addProgramResourceProvider(provider).build();
+
+    TrackAll analysis = new TrackAll();
+    ResourceShrinker.run(command, analysis);
+    return analysis;
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 2b73eb9..d30b2e2 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -34,31 +34,39 @@
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.lang.reflect.Modifier;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
 import java.util.jar.JarOutputStream;
 import java.util.stream.Stream;
 import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
 import java.util.zip.ZipOutputStream;
+import joptsimple.internal.Strings;
 import org.junit.Rule;
 import org.junit.rules.TemporaryFolder;
 
 public class TestBase {
 
+  // Actually running Proguard should only be during development.
+  private boolean runProguard = System.getProperty("run_proguard") != null;
+
   @Rule
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
   /**
+   * Check if tests should also run Proguard when applicable.
+   */
+  protected boolean isRunProguard() {
+    return runProguard;
+  }
+
+  /**
    * Write lines of text to a temporary file.
    */
   protected Path writeTextToTempFile(String... lines) throws IOException {
@@ -80,6 +88,15 @@
   }
 
   /**
+   * Build an AndroidApp with the specified jar.
+   */
+  protected AndroidApp readJar(Path jar) {
+    return AndroidApp.builder()
+        .addProgramResourceProvider(ArchiveProgramResourceProvider.fromArchive(jar))
+        .build();
+  }
+
+  /**
    * Build an AndroidApp with the specified test classes.
    */
   protected static AndroidApp readClasses(Class... classes) throws IOException {
@@ -174,27 +191,9 @@
   }
 
   /**
-   * Read the names of classes in a jar.
-   */
-  protected Set<String> readClassesInJar(Path jar) throws IOException {
-    Set<String> result = new HashSet<>();
-    try (ZipFile zipFile = new ZipFile(jar.toFile())) {
-      final Enumeration<? extends ZipEntry> entries = zipFile.entries();
-      while (entries.hasMoreElements()) {
-        ZipEntry entry = entries.nextElement();
-        String name = entry.getName();
-        if (name.endsWith(".class")) {
-          result.add(name.substring(0, name.length() - ".class".length()).replace('/', '.'));
-        }
-      }
-    }
-    return result;
-  }
-
-  /**
    * Get the class name generated by javac.
    */
-  protected String getJavacGeneratedClassName(Class clazz) {
+  protected static String getJavacGeneratedClassName(Class clazz) {
     List<String> parts = Lists.newArrayList(clazz.getCanonicalName().split("\\."));
     Class enclosing = clazz;
     while (enclosing.getEnclosingClass() != null) {
@@ -356,13 +355,29 @@
    * the specified class.
    */
   public static String keepMainProguardConfiguration(Class clazz) {
-    return keepMainProguardConfiguration(clazz.getCanonicalName());
+    return keepMainProguardConfiguration(clazz, ImmutableList.of());
   }
 
   /**
    * Generate a Proguard configuration for keeping the "public static void main(String[])" method of
    * the specified class.
    */
+  public static String keepMainProguardConfiguration(Class clazz, List<String> additionalLines) {
+    String modifier = (clazz.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC ? "public " : "";
+    return Strings.join(ImmutableList.of(
+        "-keep " + modifier + "class " + getJavacGeneratedClassName(clazz) + " {",
+        "  public static void main(java.lang.String[]);",
+        "}",
+        "-printmapping"
+    ), "\n") + (additionalLines.size() > 0 ? ("\n" + Strings.join(additionalLines, "\n")) : "");
+  }
+
+  /**
+   * Generate a Proguard configuration for keeping the "public static void main(String[])" method of
+   * the specified class.
+   *
+   * The class is assumed to be public.
+   */
   public static String keepMainProguardConfiguration(String clazz) {
     return "-keep public class " + clazz + " {\n"
         + "  public static void main(java.lang.String[]);\n"
diff --git a/src/test/java/com/android/tools/r8/TestCondition.java b/src/test/java/com/android/tools/r8/TestCondition.java
index 309f631..b85e2f2 100644
--- a/src/test/java/com/android/tools/r8/TestCondition.java
+++ b/src/test/java/com/android/tools/r8/TestCondition.java
@@ -53,14 +53,23 @@
     }
   }
 
-  public static final CompilerSet D8_COMPILER = compilers(CompilerUnderTest.D8);
+  public static final CompilerSet D8_COMPILER =
+      compilers(CompilerUnderTest.D8, CompilerUnderTest.D8_AFTER_R8CF);
+  public static final CompilerSet D8_NOT_AFTER_R8CF_COMPILER = compilers(CompilerUnderTest.D8);
+  public static final CompilerSet D8_AFTER_R8CF_COMPILER =
+      compilers(CompilerUnderTest.D8_AFTER_R8CF);
   // R8_COMPILER refers to R8 both in the standalone setting and after D8
   // R8_NOT_AFTER_D8_COMPILER and R8_AFTER_D8_COMPILER refers to the standalone and the combined
   // settings, respectively
   public static final CompilerSet R8_COMPILER =
+      compilers(
+          CompilerUnderTest.R8, CompilerUnderTest.R8_AFTER_D8, CompilerUnderTest.D8_AFTER_R8CF);
+  public static final CompilerSet R8DEX_COMPILER =
       compilers(CompilerUnderTest.R8, CompilerUnderTest.R8_AFTER_D8);
   public static final CompilerSet R8_AFTER_D8_COMPILER = compilers(CompilerUnderTest.R8_AFTER_D8);
-  public static final CompilerSet R8_NOT_AFTER_D8_COMPILER = compilers(CompilerUnderTest.R8);
+  public static final CompilerSet R8_NOT_AFTER_D8_COMPILER =
+      compilers(CompilerUnderTest.R8, CompilerUnderTest.D8_AFTER_R8CF);
+  public static final CompilerSet R8DEX_NOT_AFTER_D8_COMPILER = compilers(CompilerUnderTest.R8);
 
   public static final CompilationModeSet DEBUG_MODE =
       new CompilationModeSet(EnumSet.of(CompilationMode.DEBUG));
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 0c0cd84..5dcb059 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -91,7 +91,7 @@
   private static final AndroidApiLevel DEFAULT_MIN_SDK = AndroidApiLevel.I;
 
   private static final String PROGUARD5_2_1 = "third_party/proguard/proguard5.2.1/bin/proguard.sh";
-  private static final String PROGUARD6_0 = "third_party/proguard/proguard6.0/bin/proguard.sh";
+  private static final String PROGUARD6_0_1 = "third_party/proguard/proguard6.0.1/bin/proguard.sh";
   private static final String PROGUARD = PROGUARD5_2_1;
 
   public enum DexVm {
@@ -1119,7 +1119,8 @@
     }
   }
 
-  public static String runProguard(Path inJar, Path outJar, Path config) throws IOException {
+  public static String runProguard(Path inJar, Path outJar, Path config, Path map)
+      throws IOException {
     List<String> command = new ArrayList<>();
     command.add(PROGUARD);
     command.add("-forceprocessing");  // Proguard just checks the creation time on the in/out jars.
@@ -1131,6 +1132,9 @@
     command.add("-outjar");
     command.add(outJar.toString());
     command.add("-printmapping");
+    if (map != null) {
+      command.add(map.toString());
+    }
     ProcessBuilder builder = new ProcessBuilder(command);
     ToolHelper.ProcessResult result = ToolHelper.runProcess(builder);
     if (result.exitCode != 0) {
@@ -1139,7 +1143,6 @@
     return result.stdout;
   }
 
-
   public static class ProcessResult {
 
     public final int exitCode;
diff --git a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemDump.java b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemDump.java
new file mode 100644
index 0000000..9bc385a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemDump.java
@@ -0,0 +1,231 @@
+// 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.cf;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated with
+// tools/asmifier.py build/classes/test/com/android/tools/r8/cf/AlwaysNullGetItemTest.class
+// and edited to replace calls to get{Object,Typed}Array() with ACONST_NULL (without CHECKCAST).
+public class AlwaysNullGetItemDump implements Opcodes {
+  public static byte[] dump() throws Exception {
+
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+    AnnotationVisitor av0;
+
+    cw.visit(
+        V1_8,
+        ACC_PUBLIC + ACC_SUPER,
+        "com/android/tools/r8/cf/AlwaysNullGetItemTest",
+        null,
+        "java/lang/Object",
+        null);
+
+    cw.visitInnerClass(
+        "com/android/tools/r8/cf/AlwaysNullGetItemTest$A",
+        "com/android/tools/r8/cf/AlwaysNullGetItemTest",
+        "A",
+        ACC_PRIVATE + ACC_STATIC);
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+      mv.visitCode();
+      Label l0 = new Label();
+      Label l1 = new Label();
+      mv.visitTryCatchBlock(l0, l1, l1, "java/lang/NullPointerException");
+      mv.visitLabel(l0);
+      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      mv.visitMethodInsn(
+          INVOKESTATIC,
+          "com/android/tools/r8/cf/AlwaysNullGetItemTest",
+          "foo",
+          "()Ljava/lang/Object;",
+          false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
+      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      mv.visitMethodInsn(
+          INVOKESTATIC,
+          "com/android/tools/r8/cf/AlwaysNullGetItemTest",
+          "bar",
+          "()Ljava/lang/Object;",
+          false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
+      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      mv.visitMethodInsn(
+          INVOKESTATIC,
+          "com/android/tools/r8/cf/AlwaysNullGetItemTest",
+          "hello",
+          "()Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+          false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "com/android/tools/r8/cf/AlwaysNullGetItemTest$A",
+          "hello",
+          "()Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+          false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
+      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      mv.visitMethodInsn(
+          INVOKESTATIC,
+          "com/android/tools/r8/cf/AlwaysNullGetItemTest",
+          "goodbye",
+          "()Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+          false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "com/android/tools/r8/cf/AlwaysNullGetItemTest$A",
+          "hello",
+          "()Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+          false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
+      mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
+      mv.visitInsn(DUP);
+      mv.visitLdcInsn("Expected NullPointerException");
+      mv.visitMethodInsn(
+          INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V", false);
+      mv.visitInsn(ATHROW);
+      mv.visitLabel(l1);
+      mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/NullPointerException"});
+      mv.visitVarInsn(ASTORE, 1);
+      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      mv.visitLdcInsn("NullPointerException");
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(3, 2);
+      mv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, "foo", "()Ljava/lang/Object;", null, null);
+      mv.visitCode();
+      mv.visitInsn(ACONST_NULL);
+      // mv.visitTypeInsn(CHECKCAST, "[Ljava/lang/Object;");
+      mv.visitInsn(ICONST_0);
+      mv.visitInsn(AALOAD);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(2, 0);
+      mv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, "bar", "()Ljava/lang/Object;", null, null);
+      mv.visitCode();
+      // mv.visitMethodInsn(
+      //     INVOKESTATIC,
+      //     "com/android/tools/r8/cf/AlwaysNullGetItemTest",
+      //     "getObjectArray",
+      //     "()[Ljava/lang/Object;",
+      //     false);
+      mv.visitInsn(ACONST_NULL);
+      mv.visitInsn(ICONST_0);
+      mv.visitInsn(AALOAD);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(2, 0);
+      mv.visitEnd();
+    }
+    {
+      mv =
+          cw.visitMethod(
+              ACC_PRIVATE + ACC_STATIC,
+              "hello",
+              "()Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+              null,
+              null);
+      mv.visitCode();
+      // mv.visitMethodInsn(
+      //     INVOKESTATIC,
+      //     "com/android/tools/r8/cf/AlwaysNullGetItemTest",
+      //     "getTypedArray",
+      //     "()[Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+      //     false);
+      mv.visitInsn(ACONST_NULL);
+      mv.visitInsn(ICONST_0);
+      mv.visitInsn(AALOAD);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "com/android/tools/r8/cf/AlwaysNullGetItemTest$A",
+          "hello",
+          "()Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+          false);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(2, 0);
+      mv.visitEnd();
+    }
+    {
+      mv =
+          cw.visitMethod(
+              ACC_PRIVATE + ACC_STATIC,
+              "goodbye",
+              "()Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+              null,
+              null);
+      mv.visitCode();
+      // mv.visitMethodInsn(
+      //     INVOKESTATIC,
+      //     "com/android/tools/r8/cf/AlwaysNullGetItemTest",
+      //     "getTypedArray",
+      //     "()[Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+      //     false);
+      mv.visitInsn(ACONST_NULL);
+      mv.visitInsn(ICONST_0);
+      mv.visitInsn(AALOAD);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "com/android/tools/r8/cf/AlwaysNullGetItemTest$A",
+          "goodbye",
+          "()Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+          false);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(2, 0);
+      mv.visitEnd();
+    }
+    {
+      mv =
+          cw.visitMethod(
+              ACC_PRIVATE + ACC_STATIC, "getObjectArray", "()[Ljava/lang/Object;", null, null);
+      mv.visitCode();
+      mv.visitInsn(ACONST_NULL);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(1, 0);
+      mv.visitEnd();
+    }
+    {
+      mv =
+          cw.visitMethod(
+              ACC_PRIVATE + ACC_STATIC,
+              "getTypedArray",
+              "()[Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+              null,
+              null);
+      mv.visitCode();
+      mv.visitInsn(ACONST_NULL);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(1, 0);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTest.java b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTest.java
new file mode 100644
index 0000000..8a0a3b7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTest.java
@@ -0,0 +1,56 @@
+// 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.cf;
+
+import java.util.*;
+import org.objectweb.asm.*;
+
+public class AlwaysNullGetItemTest {
+  public static void main(String[] args) {
+    try {
+      System.out.println(foo());
+      System.out.println(bar());
+      System.out.println(hello().hello());
+      System.out.println(goodbye().hello());
+      throw new RuntimeException("Expected NullPointerException");
+    } catch (NullPointerException e) {
+      System.out.println("NullPointerException");
+    }
+  }
+
+  private static Object foo() {
+    return ((Object[]) null)[0];
+  }
+
+  private static Object bar() {
+    return getObjectArray()[0];
+  }
+
+  private static A hello() {
+    return getTypedArray()[0].hello();
+  }
+
+  private static A goodbye() {
+    return getTypedArray()[0].goodbye();
+  }
+
+  private static Object[] getObjectArray() {
+    return null;
+  }
+
+  private static A[] getTypedArray() {
+    return null;
+  }
+
+  private static class A {
+    A hello() {
+      return this;
+    }
+
+    A goodbye() {
+      return null;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java
new file mode 100644
index 0000000..3aa8f4a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java
@@ -0,0 +1,73 @@
+// 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.cf;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+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.DescriptorUtils;
+import java.nio.file.Path;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class AlwaysNullGetItemTestRunner {
+  static final Class CLASS = AlwaysNullGetItemTest.class;
+
+  @Rule
+  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Test
+  public void test() throws Exception {
+    ProcessResult runInput =
+        ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
+    assertEquals(0, runInput.exitCode);
+    Path outCf = temp.getRoot().toPath().resolve("cf.jar");
+    Path outDex = temp.getRoot().toPath().resolve("dex.zip");
+    R8.run(
+        R8Command.builder()
+            .setMode(CompilationMode.DEBUG)
+            .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
+            .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+            .setProgramConsumer(new DexIndexedConsumer.ArchiveConsumer(outDex))
+            .build());
+    R8.run(
+        R8Command.builder()
+            .setMode(CompilationMode.DEBUG)
+            .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
+            .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+            .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outCf))
+            .build());
+    ProcessResult runCf = ToolHelper.runJava(outCf, CLASS.getCanonicalName());
+    ProcessResult runDex = ToolHelper.runArtRaw(outDex.toString(), CLASS.getCanonicalName());
+    assertEquals(runInput.toString(), runCf.toString());
+    // Only compare stdout and exitCode since dex2oat prints to stderr.
+    assertEquals(runInput.stdout, runDex.stdout);
+    assertEquals(runInput.exitCode, runDex.exitCode);
+  }
+
+  @Test
+  public void testNoCheckCast() throws Exception {
+    // Test that JVM accepts javac output when method calls have been replaced by ACONST_NULL.
+    Path out = temp.getRoot().toPath().resolve("aaload-null.jar");
+    ClassFileConsumer.ArchiveConsumer archiveConsumer = new ClassFileConsumer.ArchiveConsumer(out);
+    archiveConsumer.accept(
+        AlwaysNullGetItemDump.dump(),
+        DescriptorUtils.javaTypeToDescriptor(CLASS.getCanonicalName()),
+        null);
+    archiveConsumer.finished(null);
+    ProcessResult processResult = ToolHelper.runJava(out, CLASS.getCanonicalName());
+    if (processResult.exitCode != 0) {
+      System.out.println(processResult);
+    }
+    assertEquals(0, processResult.exitCode);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/AnnotationTestRunner.java b/src/test/java/com/android/tools/r8/cf/AnnotationTestRunner.java
index 5c48073..aeddc54 100644
--- a/src/test/java/com/android/tools/r8/cf/AnnotationTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/AnnotationTestRunner.java
@@ -13,17 +13,17 @@
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.origin.Origin;
 import java.nio.file.Path;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 
 public class AnnotationTestRunner {
   static final Class CLASS = AnnotationTest.class;
-  @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Rule
+  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
   @Test
-  @Ignore
   public void test() throws Exception {
     ProcessResult runInput =
         ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
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 20acb9f..7565dee 100644
--- a/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
@@ -6,6 +6,8 @@
 
 package com.android.tools.r8.cf;
 
+import static org.junit.Assert.assertFalse;
+
 import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
@@ -53,7 +55,6 @@
                 insn ->
                     insn.getOpcode() == Opcodes.MONITORENTER
                         || insn.getOpcode() == Opcodes.MONITOREXIT);
-    // TODO(b/73921688): Should not have monitor instruction here
-    assert hasMonitor;
+    assertFalse(hasMonitor);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
new file mode 100644
index 0000000..40d8ad0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
@@ -0,0 +1,63 @@
+// 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.desugaring.interfacemethods;
+
+import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.desugaring.interfacemethods.test0.InterfaceWithDefaults;
+import com.android.tools.r8.desugaring.interfacemethods.test0.TestMain;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(VmTestRunner.class)
+public class InterfaceMethodDesugaringTests extends AsmTestBase {
+
+  @Test
+  public void testInvokeSpecialToDefaultMethod() throws Exception {
+    ensureSameOutput(TestMain.class.getCanonicalName(),
+        ToolHelper.getMinApiLevelForDexVm(),
+        ToolHelper.getClassAsBytes(TestMain.class),
+        introduceInvokeSpecial(ToolHelper.getClassAsBytes(InterfaceWithDefaults.class)));
+  }
+
+  private byte[] introduceInvokeSpecial(byte[] classBytes) throws IOException {
+    try (InputStream input = new ByteArrayInputStream(classBytes)) {
+      ClassReader cr = new ClassReader(input);
+      ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
+      cr.accept(
+          new ClassVisitor(Opcodes.ASM6, cw) {
+            @Override
+            public MethodVisitor visitMethod(int access, String name,
+                String desc, String signature, String[] exceptions) {
+              MethodVisitor visitor = super.visitMethod(access, name, desc, signature, exceptions);
+              return new MethodVisitor(Opcodes.ASM6, visitor) {
+                @Override
+                public void visitMethodInsn(
+                    int opcode, String owner, String name, String desc, boolean itf) {
+                  if (opcode == Opcodes.INVOKEINTERFACE &&
+                      owner.endsWith("test0/InterfaceWithDefaults") &&
+                      name.equals("foo")) {
+                    super.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, itf);
+
+                  } else {
+                    super.visitMethodInsn(opcode, owner, name, desc, itf);
+                  }
+                }
+              };
+            }
+          }, 0);
+      return cw.toByteArray();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/InterfaceWithDefaults.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/InterfaceWithDefaults.java
new file mode 100644
index 0000000..fc5ab9e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/InterfaceWithDefaults.java
@@ -0,0 +1,14 @@
+package com.android.tools.r8.desugaring.interfacemethods.test0;
+
+public interface InterfaceWithDefaults {
+  default void foo() {
+    System.out.println("InterfaceWithDefaults::foo()");
+  }
+
+  default void bar() {
+    System.out.println("InterfaceWithDefaults::bar()");
+    this.foo();
+  }
+
+  void test();
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/TestMain.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/TestMain.java
new file mode 100644
index 0000000..b57cbf3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/TestMain.java
@@ -0,0 +1,19 @@
+package com.android.tools.r8.desugaring.interfacemethods.test0;
+
+public class TestMain implements InterfaceWithDefaults {
+  @Override
+  public void test() {
+    System.out.println("TestMain::test()");
+    this.foo();
+    this.bar();
+  }
+
+  @Override
+  public void foo() {
+    System.out.println("TestMain::foo()");
+  }
+
+  public static void main(String[] args) {
+    new TestMain().test();
+  }
+}
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 fbb14f1..60ed129 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -233,7 +233,7 @@
     }
 
     AndroidApp app = compileWithR8(jasminBuilder.build(),
-        keepMainProguardConfiguration("Foo") + "\ndontobfuscate");
+        keepMainProguardConfiguration("Foo") + "\n-dontobfuscate");
     String artOutput = runOnArt(app, "Foo");
     System.out.println(artOutput);
   }
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 51523f2..a510cee 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
@@ -128,14 +128,13 @@
     FieldSubject f2 = clazz.field("java.lang.Object", "same");
     assertTrue(f2.isPresent());
     assertFalse(f2.isRenamed());
-    assertEquals(f1.getField().field.name, f2.getField().field.name);
+    assertEquals(f1.getFinalName(), f2.getFinalName());
 
     ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
     assertEquals(0, artOutput.exitCode);
     assertEquals(javaOutput.stdout, artOutput.stdout);
   }
 
-
   @Test
   public void remainFieldNameConflict_useuniqueclassmembernames() throws Exception {
     Assume.assumeTrue(ToolHelper.artSupported());
@@ -158,7 +157,7 @@
     FieldSubject f2 = clazz.field("java.lang.Object", "same");
     assertTrue(f2.isPresent());
     assertTrue(f2.isRenamed());
-    assertEquals(f1.getField().field.name, f2.getField().field.name);
+    assertEquals(f1.getFinalName(), f2.getFinalName());
 
     ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
     assertEquals(0, artOutput.exitCode);
@@ -189,7 +188,7 @@
     FieldSubject f2 = clazz.field("java.lang.Object", "same");
     assertTrue(f2.isPresent());
     assertTrue(f2.isRenamed());
-    assertEquals(f1.getField().field.name, f2.getField().field.name);
+    assertEquals(f1.getFinalName(), f2.getFinalName());
 
     ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
     assertEquals(0, artOutput.exitCode);
@@ -217,7 +216,7 @@
     FieldSubject f2 = clazz.field("java.lang.Object", "same");
     assertTrue(f2.isPresent());
     assertTrue(f2.isRenamed());
-    assertNotEquals(f1.getField().field.name, f2.getField().field.name);
+    assertNotEquals(f1.getFinalName(), f2.getFinalName());
 
     ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
     assertEquals(0, artOutput.exitCode);
@@ -246,7 +245,7 @@
     FieldSubject f2 = clazz.field("java.lang.Object", "same");
     assertTrue(f2.isPresent());
     assertTrue(f2.isRenamed());
-    assertEquals(f1.getField().field.name, f2.getField().field.name);
+    assertEquals(f1.getFinalName(), f2.getFinalName());
 
     ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
     assertEquals(0, artOutput.exitCode);
@@ -291,7 +290,6 @@
             + "  static <methods>;"
             + "}\n",
         keepMainProguardConfiguration(CLASS_NAME),
-        "-useuniqueclassmembernames",
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
@@ -304,7 +302,7 @@
     MethodSubject m2 = clazz.method("java.lang.Object", "same", ImmutableList.of());
     assertTrue(m2.isPresent());
     assertFalse(m2.isRenamed());
-    assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+    assertEquals(m1.getFinalName(), m2.getFinalName());
 
     ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
     assertEquals(0, artOutput.exitCode);
@@ -333,7 +331,7 @@
     MethodSubject m2 = clazz.method("java.lang.Object", "same", ImmutableList.of());
     assertTrue(m2.isPresent());
     assertTrue(m2.isRenamed());
-    assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+    assertEquals(m1.getFinalName(), m2.getFinalName());
 
     ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
     assertEquals(0, artOutput.exitCode);
@@ -364,14 +362,13 @@
     MethodSubject m2 = clazz.method("java.lang.Object", "same", ImmutableList.of());
     assertTrue(m2.isPresent());
     assertTrue(m2.isRenamed());
-    assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+    assertEquals(m1.getFinalName(), m2.getFinalName());
 
     ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
     assertEquals(0, artOutput.exitCode);
     assertEquals(javaOutput.stdout, artOutput.stdout);
   }
 
-
   @Test
   public void resolveMethodNameConflict_no_options() throws Exception {
     Assume.assumeTrue(ToolHelper.artSupported());
@@ -393,8 +390,7 @@
     MethodSubject m2 = clazz.method("java.lang.Object", "same", ImmutableList.of());
     assertTrue(m2.isPresent());
     assertTrue(m2.isRenamed());
-    // TODO(b/73149686): R8 should be able to fix this conflict w/o -overloadaggressively.
-    assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+    assertNotEquals(m1.getFinalName(), m2.getFinalName());
 
     ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
     assertEquals(0, artOutput.exitCode);
@@ -423,7 +419,7 @@
     MethodSubject m2 = clazz.method("java.lang.Object", "same", ImmutableList.of());
     assertTrue(m2.isPresent());
     assertTrue(m2.isRenamed());
-    assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+    assertEquals(m1.getFinalName(), m2.getFinalName());
 
     ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
     assertEquals(0, artOutput.exitCode);
@@ -481,7 +477,6 @@
             + "  <methods>;"
             + "}\n",
         keepMainProguardConfiguration(CLASS_NAME),
-        "-useuniqueclassmembernames",
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
@@ -494,7 +489,7 @@
     MethodSubject m2 = sup.method("java.lang.Object", "same", ImmutableList.of());
     assertTrue(m2.isPresent());
     assertFalse(m2.isRenamed());
-    assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+    assertEquals(m1.getFinalName(), m2.getFinalName());
 
     ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
     assertTrue(sub.isPresent());
@@ -504,11 +499,11 @@
     MethodSubject subM2 = sub.method("java.lang.Object", "same", ImmutableList.of());
     assertTrue(subM2.isPresent());
     assertFalse(subM2.isRenamed());
-    assertEquals(subM1.getMethod().method.name, subM2.getMethod().method.name);
+    assertEquals(subM1.getFinalName(), subM2.getFinalName());
 
     // No matter what, overloading methods should be renamed to the same name.
-    assertEquals(m1.getMethod().method.name, subM1.getMethod().method.name);
-    assertEquals(m2.getMethod().method.name, subM2.getMethod().method.name);
+    assertEquals(m1.getFinalName(), subM1.getFinalName());
+    assertEquals(m2.getFinalName(), subM2.getFinalName());
 
     ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
     assertEquals(0, artOutput.exitCode);
@@ -537,7 +532,7 @@
     MethodSubject m2 = sup.method("java.lang.Object", "same", ImmutableList.of());
     assertTrue(m2.isPresent());
     assertTrue(m2.isRenamed());
-    assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+    assertEquals(m1.getFinalName(), m2.getFinalName());
 
     ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
     assertTrue(sub.isPresent());
@@ -547,11 +542,11 @@
     MethodSubject subM2 = sub.method("java.lang.Object", "same", ImmutableList.of());
     assertTrue(subM2.isPresent());
     assertTrue(subM2.isRenamed());
-    assertEquals(subM1.getMethod().method.name, subM2.getMethod().method.name);
+    assertEquals(subM1.getFinalName(), subM2.getFinalName());
 
     // No matter what, overloading methods should be renamed to the same name.
-    assertEquals(m1.getMethod().method.name, subM1.getMethod().method.name);
-    assertEquals(m2.getMethod().method.name, subM2.getMethod().method.name);
+    assertEquals(m1.getFinalName(), subM1.getFinalName());
+    assertEquals(m2.getFinalName(), subM2.getFinalName());
 
     ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
     assertEquals(0, artOutput.exitCode);
@@ -582,7 +577,7 @@
     MethodSubject m2 = sup.method("java.lang.Object", "same", ImmutableList.of());
     assertTrue(m2.isPresent());
     assertTrue(m2.isRenamed());
-    assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+    assertEquals(m1.getFinalName(), m2.getFinalName());
 
     ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
     assertTrue(sub.isPresent());
@@ -592,18 +587,17 @@
     MethodSubject subM2 = sub.method("java.lang.Object", "same", ImmutableList.of());
     assertTrue(subM2.isPresent());
     assertTrue(subM2.isRenamed());
-    assertEquals(subM1.getMethod().method.name, subM2.getMethod().method.name);
+    assertEquals(subM1.getFinalName(), subM2.getFinalName());
 
     // No matter what, overloading methods should be renamed to the same name.
-    assertEquals(m1.getMethod().method.name, subM1.getMethod().method.name);
-    assertEquals(m2.getMethod().method.name, subM2.getMethod().method.name);
+    assertEquals(m1.getFinalName(), subM1.getFinalName());
+    assertEquals(m2.getFinalName(), subM2.getFinalName());
 
     ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
     assertEquals(0, artOutput.exitCode);
     assertEquals(javaOutput.stdout, artOutput.stdout);
   }
 
-
   @Test
   public void resolveMethodNameConflictInHierarchy_no_options() throws Exception {
     Assume.assumeTrue(ToolHelper.artSupported());
@@ -625,8 +619,7 @@
     MethodSubject m2 = sup.method("java.lang.Object", "same", ImmutableList.of());
     assertTrue(m2.isPresent());
     assertTrue(m2.isRenamed());
-    // TODO(b/73149686): R8 should be able to fix this conflict w/o -overloadaggressively.
-    assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+    assertNotEquals(m1.getFinalName(), m2.getFinalName());
 
     ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
     assertTrue(sub.isPresent());
@@ -636,12 +629,11 @@
     MethodSubject subM2 = sub.method("java.lang.Object", "same", ImmutableList.of());
     assertTrue(subM2.isPresent());
     assertTrue(subM2.isRenamed());
-    // TODO(b/73149686): R8 should be able to fix this conflict w/o -overloadaggressively.
-    assertEquals(subM1.getMethod().method.name, subM2.getMethod().method.name);
+    assertNotEquals(subM1.getFinalName(), subM2.getFinalName());
 
     // No matter what, overloading methods should be renamed to the same name.
-    assertEquals(m1.getMethod().method.name, subM1.getMethod().method.name);
-    assertEquals(m2.getMethod().method.name, subM2.getMethod().method.name);
+    assertEquals(m1.getFinalName(), subM1.getFinalName());
+    assertEquals(m2.getFinalName(), subM2.getFinalName());
 
     ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
     assertEquals(0, artOutput.exitCode);
@@ -671,7 +663,7 @@
     MethodSubject m2 = sup.method("java.lang.Object", "same", ImmutableList.of());
     assertTrue(m2.isPresent());
     assertTrue(m2.isRenamed());
-    assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+    assertEquals(m1.getFinalName(), m2.getFinalName());
 
     ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
     assertTrue(sub.isPresent());
@@ -681,11 +673,11 @@
     MethodSubject subM2 = sub.method("java.lang.Object", "same", ImmutableList.of());
     assertTrue(subM2.isPresent());
     assertTrue(subM2.isRenamed());
-    assertEquals(subM1.getMethod().method.name, subM2.getMethod().method.name);
+    assertEquals(subM1.getFinalName(), subM2.getFinalName());
 
     // No matter what, overloading methods should be renamed to the same name.
-    assertEquals(m1.getMethod().method.name, subM1.getMethod().method.name);
-    assertEquals(m2.getMethod().method.name, subM2.getMethod().method.name);
+    assertEquals(m1.getFinalName(), subM1.getFinalName());
+    assertEquals(m2.getFinalName(), subM2.getFinalName());
 
     ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
     assertEquals(0, artOutput.exitCode);
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 cfbfa84..434f213 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
@@ -47,9 +47,6 @@
 import org.junit.Test;
 
 public class ForceProguardCompatibilityTest extends TestBase {
-  // Actually running Proguard should only be during development.
-  private final boolean RUN_PROGUARD = false;
-
   private void test(Class mainClass, Class mentionedClass, boolean forceProguardCompatibility)
       throws Exception {
     String proguardConfig = keepMainProguardConfiguration(mainClass, true, false);
@@ -160,11 +157,13 @@
       assertEquals(0, configuration.getRules().size());
     }
 
-    if (RUN_PROGUARD) {
+    if (isRunProguard()) {
       Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath();
       Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
+      Path proguardMapFile = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
       FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
-      ToolHelper.runProguard(jarTestClasses(testClass), proguardedJar, proguardConfigFile);
+      ToolHelper.runProguard(jarTestClasses(testClass),
+          proguardedJar, proguardConfigFile, proguardMapFile);
     }
   }
 
@@ -199,16 +198,16 @@
       assertEquals(forceProguardCompatibility && containsCheckCast, !clazz.isAbstract());
     }
 
-    if (RUN_PROGUARD) {
+    if (isRunProguard()) {
       Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath();
       Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
       FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
       ToolHelper.runProguard(jarTestClasses(ImmutableList.of(mainClass, instantiatedClass)),
-          proguardedJar, proguardConfigFile);
-      Set<String> classesAfterProguard = readClassesInJar(proguardedJar);
-      assertTrue(classesAfterProguard.contains(mainClass.getCanonicalName()));
+          proguardedJar, proguardConfigFile, null);
+      DexInspector proguardInspector = new DexInspector(readJar(proguardedJar));
+      assertTrue(proguardInspector.clazz(mainClass).isPresent());
       assertEquals(
-          containsCheckCast, classesAfterProguard.contains(instantiatedClass.getCanonicalName()));
+          containsCheckCast, proguardInspector.clazz(instantiatedClass).isPresent());
     }
   }
 
@@ -288,19 +287,20 @@
       assertEquals(0, configuration.getRules().size());
     }
 
-    if (RUN_PROGUARD) {
+    if (isRunProguard()) {
       Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath();
       Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
+      Path proguardMapFile = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
       FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
       ToolHelper.runProguard(jarTestClasses(
           ImmutableList.of(mainClass, forNameClass1, forNameClass2)),
-          proguardedJar, proguardConfigFile);
-      Set<String> classesAfterProguard = readClassesInJar(proguardedJar);
-      assertEquals(3, classesAfterProguard.size());
-      assertTrue(classesAfterProguard.contains(mainClass.getCanonicalName()));
-      if (!allowObfuscation) {
-        assertTrue(classesAfterProguard.contains(forNameClass1.getCanonicalName()));
-        assertTrue(classesAfterProguard.contains(forNameClass2.getCanonicalName()));
+          proguardedJar, proguardConfigFile, proguardMapFile);
+      DexInspector proguardedInspector = new DexInspector(readJar(proguardedJar), proguardMapFile);
+      assertEquals(3, proguardedInspector.allClasses().size());
+      assertTrue(proguardedInspector.clazz(mainClass).isPresent());
+      for (Class clazz : ImmutableList.of(forNameClass1, forNameClass2)) {
+        assertTrue(proguardedInspector.clazz(clazz).isPresent());
+        assertEquals(allowObfuscation, proguardedInspector.clazz(clazz).isRenamed());
       }
     }
   }
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
new file mode 100644
index 0000000..d098ef7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.forceproguardcompatibility;
+
+import com.android.tools.r8.CompatProguardCommandBuilder;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.nio.file.Path;
+import java.util.List;
+
+public class ProguardCompatabilityTestBase extends TestBase {
+
+  protected DexInspector runR8Compat(
+      List<Class> programClasses, String proguardConfig) throws Exception {
+    CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder(true);
+    builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
+    programClasses.forEach(
+        clazz -> builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz)));
+    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+    return new DexInspector(ToolHelper.runR8(builder.build()));
+  }
+
+  protected DexInspector runR8CompatKeepingMain(Class mainClass, List<Class> programClasses)
+      throws Exception {
+    return runR8Compat(programClasses, keepMainProguardConfiguration(mainClass));
+  }
+
+  protected DexInspector runProguard(
+      List<Class> programClasses, String proguardConfig) throws Exception {
+    Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath();
+    Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
+    FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
+    ToolHelper.runProguard(jarTestClasses(programClasses), proguardedJar, proguardConfigFile, null);
+    return new DexInspector(readJar(proguardedJar));
+  }
+
+  protected DexInspector runProguardAndD8(
+      List<Class> programClasses, String proguardConfig) throws Exception {
+    Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath();
+    Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
+    FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
+    ToolHelper.runProguard(jarTestClasses(programClasses), proguardedJar, proguardConfigFile, null);
+    AndroidApp app = ToolHelper.runD8(readJar(proguardedJar));
+    return new DexInspector(app);
+  }
+
+  protected DexInspector runProguardKeepingMain(Class mainClass, List<Class> programClasses)
+      throws Exception {
+    return runProguardAndD8(programClasses, keepMainProguardConfiguration(mainClass));
+  }
+}
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
new file mode 100644
index 0000000..4cb7750
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
@@ -0,0 +1,319 @@
+// 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.utils.DexInspectorMatchers.hasDefaultConstructor;
+import static com.android.tools.r8.utils.DexInspectorMatchers.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.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+
+class SuperClass {
+
+}
+
+class SubClass extends SuperClass {
+
+}
+
+class MainInstantiationSubClass {
+
+  public static void main(String[] args) {
+    new SubClass();
+  }
+}
+
+class MainGetClassSubClass {
+
+  public static void main(String[] args) {
+    System.out.println(SubClass.class);
+  }
+}
+
+class MainCheckCastSubClass {
+
+  public static void main(String[] args) {
+    System.out.println((SubClass) null);
+  }
+}
+
+class MainClassForNameSubClass {
+
+  public static void main(String[] args) throws Exception{
+    System.out.println(Class.forName(
+        "com.android.tools.r8.shaking.forceproguardcompatibility.defaultctor.SubClass"));
+  }
+}
+
+class StaticFieldNotInitialized {
+  public static int field;
+}
+
+class MainGetStaticFieldNotInitialized {
+
+  public static void main(String[] args) {
+    System.out.println(StaticFieldNotInitialized.field);
+  }
+}
+
+class StaticMethod {
+  public static int method() {
+    return 1;
+  };
+}
+
+class MainCallStaticMethod {
+
+  public static void main(String[] args) {
+    System.out.println(StaticMethod.method());
+  }
+}
+
+class StaticFieldInitialized {
+  public static int field = 1;
+}
+
+class MainGetStaticFieldInitialized {
+
+  public static void main(String[] args) {
+    System.out.println(StaticFieldInitialized.field);
+  }
+}
+
+public class ImplicitlyKeptDefaultConstructorTest extends ProguardCompatabilityTestBase {
+
+  private void checkPresentWithDefaultConstructor(ClassSubject clazz) {
+    assertThat(clazz, isPresent());
+    assertThat(clazz, hasDefaultConstructor());
+  }
+
+  private void checkPresentWithoutDefaultConstructor(ClassSubject clazz) {
+    assertThat(clazz, isPresent());
+    assertThat(clazz, not(hasDefaultConstructor()));
+  }
+
+  private void checkAllClassesPresentWithDefaultConstructor(
+      Class mainClass, List<Class> programClasses, DexInspector inspector) {
+    assert programClasses.contains(mainClass);
+    assertEquals(programClasses.size(), inspector.allClasses().size());
+    inspector.forAllClasses(this::checkPresentWithDefaultConstructor);
+  }
+
+  private void checkAllClassesPresentOnlyMainWithDefaultConstructor(
+      Class mainClass, List<Class> programClasses, DexInspector inspector) {
+    assert programClasses.contains(mainClass);
+    assertEquals(programClasses.size(), inspector.allClasses().size());
+    checkPresentWithDefaultConstructor(inspector.clazz(mainClass));
+    inspector.allClasses()
+        .stream()
+        .filter(subject -> !subject.getOriginalName().equals(mainClass.getCanonicalName()))
+        .forEach(this::checkPresentWithoutDefaultConstructor);
+  }
+
+  private void checkOnlyMainPresent(
+      Class mainClass, List<Class> programClasses, DexInspector inspector) {
+    assert programClasses.contains(mainClass);
+    assertEquals(1, inspector.allClasses().size());
+    inspector.forAllClasses(this::checkPresentWithDefaultConstructor);
+  }
+
+  private void runTest(
+      Class mainClass, List<Class> programClasses, String proguardConfiguration,
+      TriConsumer<Class, List<Class>, DexInspector> r8Checker,
+      TriConsumer<Class, List<Class>, DexInspector> proguardChecker) throws Exception {
+    DexInspector inspector = runR8Compat(programClasses, proguardConfiguration);
+    r8Checker.accept(mainClass, programClasses, inspector);
+
+    if (isRunProguard()) {
+      inspector = runProguard(programClasses, proguardConfiguration);
+      proguardChecker.accept(mainClass, programClasses, inspector);
+      inspector = runProguardAndD8(programClasses, proguardConfiguration);
+      proguardChecker.accept(mainClass, programClasses, inspector);
+    }
+  }
+
+  private void runTest(
+      Class mainClass, List<Class> programClasses, String proguardConfiguration,
+      TriConsumer<Class, List<Class>, DexInspector> checker) throws Exception {
+    runTest(mainClass, programClasses, proguardConfiguration, checker, checker);
+  }
+
+  @Test
+  public void testInstantiation() throws Exception {
+    // A new instance call keeps the default constructor.
+    Class mainClass = MainInstantiationSubClass.class;
+    runTest(
+        mainClass,
+        ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
+        keepMainProguardConfiguration(mainClass),
+        this::checkAllClassesPresentWithDefaultConstructor);
+  }
+
+  @Test
+  public void testGetClass() throws Exception {
+    // Reference to the class constant keeps the default constructor.
+    Class mainClass = MainGetClassSubClass.class;
+    runTest(
+        mainClass,
+        ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
+        keepMainProguardConfiguration(mainClass),
+        this::checkAllClassesPresentWithDefaultConstructor);
+  }
+
+  @Test
+  public void testCheckCast() throws Exception {
+    // Reference to the class constant keeps the default constructor.
+    Class mainClass = MainCheckCastSubClass.class;
+    runTest(
+        mainClass,
+        ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
+        keepMainProguardConfiguration(mainClass),
+        // TODO(74423424): Proguard eliminates the check-cast on null.
+        this::checkAllClassesPresentWithDefaultConstructor,
+        this::checkOnlyMainPresent);
+  }
+
+
+  @Test
+  public void testCheckCastWithoutInlining() throws Exception {
+    // Reference to the class constant keeps the default constructor.
+    Class mainClass = MainCheckCastSubClass.class;
+    runTest(
+        mainClass,
+        ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
+        keepMainProguardConfiguration(mainClass, ImmutableList.of("-dontoptimize")),
+        this::checkAllClassesPresentWithDefaultConstructor);
+  }
+
+  @Test
+  public void testClassForName() throws Exception {
+    // Class.forName with a constant string keeps the default constructor.
+    Class mainClass = MainClassForNameSubClass.class;
+    runTest(
+        mainClass,
+        ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
+        keepMainProguardConfiguration(mainClass),
+        this::checkAllClassesPresentWithDefaultConstructor);
+  }
+
+  @Test
+  public void testStaticFieldWithoutInitializationStaticClassKept() throws Exception {
+    // An explicit keep rule keeps the default constructor.
+    Class mainClass = MainGetStaticFieldNotInitialized.class;
+    String proguardConfiguration = keepMainProguardConfiguration(
+        mainClass,
+        ImmutableList.of(
+            "-keep class " + getJavacGeneratedClassName(StaticFieldNotInitialized.class) + " {",
+            "}"));
+    runTest(
+        mainClass,
+        ImmutableList.of(mainClass, StaticFieldNotInitialized.class),
+        proguardConfiguration,
+        this::checkAllClassesPresentWithDefaultConstructor);
+  }
+
+  @Test
+  public void testStaticFieldWithInitializationStaticClassKept() throws Exception {
+    // An explicit keep rule keeps the default constructor.
+    Class mainClass = MainGetStaticFieldInitialized.class;
+    String proguardConfiguration = keepMainProguardConfiguration(
+        mainClass,
+        ImmutableList.of(
+            "-keep class " + getJavacGeneratedClassName(StaticFieldInitialized.class) + " {",
+            "}"));
+    runTest(
+        mainClass,
+        ImmutableList.of(mainClass, StaticFieldInitialized.class),
+        proguardConfiguration,
+        this::checkAllClassesPresentWithDefaultConstructor);
+  }
+
+  @Test
+  public void testStaticMethodStaticClassKept() throws Exception {
+    // An explicit keep rule keeps the default constructor.
+    Class mainClass = MainCallStaticMethod.class;
+    String proguardConfiguration = keepMainProguardConfiguration(
+        mainClass,
+        ImmutableList.of(
+            "-keep class " + getJavacGeneratedClassName(StaticMethod.class) + " {",
+            "}"));
+    runTest(
+        mainClass,
+        ImmutableList.of(mainClass, StaticMethod.class),
+        proguardConfiguration,
+        this::checkAllClassesPresentWithDefaultConstructor);
+  }
+
+  @Test
+  public void testStaticFieldWithoutInitialization() throws Exception {
+    Class mainClass = MainGetStaticFieldNotInitialized.class;
+    runTest(
+        mainClass,
+        ImmutableList.of(mainClass, StaticFieldNotInitialized.class),
+        keepMainProguardConfiguration(mainClass),
+        // TODO(74379749): Proguard does not keep the class with the un-initialized static field.
+        this::checkAllClassesPresentOnlyMainWithDefaultConstructor,
+        this::checkOnlyMainPresent);
+  }
+
+  @Test
+  public void testStaticFieldWithInitialization() throws Exception {
+    Class mainClass = MainGetStaticFieldInitialized.class;
+    runTest(
+        mainClass,
+        ImmutableList.of(mainClass, StaticFieldInitialized.class),
+        keepMainProguardConfiguration(mainClass),
+        this::checkAllClassesPresentOnlyMainWithDefaultConstructor);
+  }
+
+  @Test
+  public void testStaticMethodStaticClassNotKept() throws Exception {
+    // Due to inlining only the main method is left.
+    Class mainClass = MainCallStaticMethod.class;
+    runTest(
+        mainClass,
+        ImmutableList.of(mainClass, StaticMethod.class),
+        keepMainProguardConfiguration(mainClass),
+        this::checkOnlyMainPresent);
+  }
+
+  @Test
+  public void testStaticFieldWithoutInitializationWithoutInlining() throws Exception {
+    Class mainClass = MainGetStaticFieldNotInitialized.class;
+    runTest(
+        mainClass,
+        ImmutableList.of(mainClass, StaticFieldNotInitialized.class),
+        keepMainProguardConfiguration(mainClass, ImmutableList.of("-dontoptimize")),
+        this::checkAllClassesPresentOnlyMainWithDefaultConstructor);
+  }
+
+  @Test
+  public void testStaticFieldWithInitializationWithoutInlining() throws Exception {
+    Class mainClass = MainGetStaticFieldInitialized.class;
+    runTest(
+        mainClass,
+        ImmutableList.of(mainClass, StaticFieldInitialized.class),
+        keepMainProguardConfiguration(mainClass, ImmutableList.of("-dontoptimize")),
+        this::checkAllClassesPresentOnlyMainWithDefaultConstructor);
+  }
+
+  @Test
+  public void testStaticMethodStaticWithoutInlining() throws Exception {
+    Class mainClass = MainCallStaticMethod.class;
+    runTest(
+        mainClass,
+        ImmutableList.of(mainClass, StaticMethod.class),
+        keepMainProguardConfiguration(mainClass, ImmutableList.of("-dontoptimize")),
+        this::checkAllClassesPresentOnlyMainWithDefaultConstructor);
+  }
+}
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 ca3fd2a..d578815 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
@@ -14,48 +14,42 @@
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Set;
 import org.junit.Test;
 
 public class IncludeDescriptorClassesTest extends TestBase {
-  // Actually running Proguard should only be during development.
-  private final boolean RUN_PROGUARD = false;
-
   private class Result {
     final DexInspector inspector;
-    final Set<String> classesAfterProguard;
+    final DexInspector proguardedInspector;
 
-    Result(DexInspector inspector, Set<String> classesAfterProguard) {
+    Result(DexInspector inspector, DexInspector proguardedInspector) {
       this.inspector = inspector;
-      this.classesAfterProguard = classesAfterProguard;
+      this.proguardedInspector = proguardedInspector;
     }
 
     void assertKept(Class clazz) {
       assertTrue(inspector.clazz(clazz.getCanonicalName()).isPresent());
       assertFalse(inspector.clazz(clazz.getCanonicalName()).isRenamed());
-      if (classesAfterProguard != null) {
-        assertTrue(classesAfterProguard.contains(clazz.getCanonicalName()));
+      if (proguardedInspector != null) {
+        assertTrue(proguardedInspector.clazz(clazz).isPresent());
       }
     }
 
     // NOTE: 'synchronized' is supposed to disable inlining of this method.
     synchronized void assertRemoved(Class clazz) {
       assertFalse(inspector.clazz(clazz.getCanonicalName()).isPresent());
-      // TODO(sgjesse): Also check that it was not just renamed...
-      if (classesAfterProguard != null) {
-        assertFalse(classesAfterProguard.contains(clazz.getCanonicalName()));
+      if (proguardedInspector != null) {
+        assertFalse(proguardedInspector.clazz(clazz).isPresent());
       }
     }
 
     void assertRenamed(Class clazz) {
       assertTrue(inspector.clazz(clazz.getCanonicalName()).isPresent());
       assertTrue(inspector.clazz(clazz.getCanonicalName()).isRenamed());
-      // TODO(sgjesse): Also check that it was actually renamed...
-      if (classesAfterProguard != null) {
-        assertFalse(classesAfterProguard.contains(clazz.getCanonicalName()));
+      if (proguardedInspector != null) {
+        assertTrue(proguardedInspector.clazz(clazz).isPresent());
+        assertTrue(proguardedInspector.clazz(clazz).isRenamed());
       }
     }
-
   }
 
   private List<Class> applicationClasses = ImmutableList.of(
@@ -70,15 +64,16 @@
 
     DexInspector inspector = new DexInspector(compileWithR8(classes, proguardConfig));
 
-    Set<String> classesAfterProguard = null;
+    DexInspector proguardedInspector = null;
     // Actually running Proguard should only be during development.
-    if (RUN_PROGUARD) {
+    if (isRunProguard()) {
       Path proguardedJar = temp.newFolder().toPath().resolve("proguarded.jar");
-      ToolHelper.runProguard(jarTestClasses(classes), proguardedJar, proguardConfig);
-      classesAfterProguard = readClassesInJar(proguardedJar);
+      Path proguardedMap = temp.newFolder().toPath().resolve("proguarded.map");
+      ToolHelper.runProguard(jarTestClasses(classes), proguardedJar, proguardConfig, proguardedMap);
+      proguardedInspector = new DexInspector(readJar(proguardedJar), proguardedMap);
     }
 
-    return new Result(inspector, classesAfterProguard);
+    return new Result(inspector, proguardedInspector);
   }
 
   @Test
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 7904400..7f8e5fb 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,26 +4,61 @@
 
 package com.android.tools.r8.shaking.keepclassmembers;
 
-import static org.junit.Assert.assertEquals;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isAbstract;
+import static com.android.tools.r8.utils.DexInspectorMatchers.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.TestBase;
+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.google.common.collect.ImmutableList;
 import org.junit.Test;
 
-public class KeepClassMembersTest extends TestBase {
+public class KeepClassMembersTest extends ProguardCompatabilityTestBase {
 
-  public void runTest(Class mainClass, Class<?> staticClass,
-      boolean forceProguardCompatibility) throws Exception {
-    boolean staticClassHasDefaultConstructor = true;
-    try {
-      staticClass.getDeclaredConstructor();
-    } catch (NoSuchMethodException e) {
-      staticClassHasDefaultConstructor = false;
+  private void check(DexInspector inspector, Class mainClass, Class<?> staticClass,
+      boolean forceProguardCompatibility, boolean fromProguard) {
+    assertTrue(inspector.clazz(mainClass).isPresent());
+    ClassSubject staticClassSubject = inspector.clazz(staticClass);
+    assertThat(staticClassSubject, isPresent());
+    assertThat(staticClassSubject.method("int", "getA", ImmutableList.of()), isPresent());
+    assertThat(staticClassSubject.method("int", "getB", ImmutableList.of()), not(isPresent()));
+    assertThat(staticClassSubject.field("int", "a"), isPresent());
+    assertThat(staticClassSubject.field("int", "b"), isPresent());
+    assertThat(staticClassSubject.field("int", "c"), not(isPresent()));
+    // Neither Proguard not R8 keeps any constructors.
+    staticClassSubject.forAllMethods(method -> assertFalse(method.isInstanceInitializer()));
+    assertThat(staticClassSubject.init(ImmutableList.of()), not(isPresent()));
+    MethodSubject getIMethod = staticClassSubject.method("int", "getI", ImmutableList.of());
+    FieldSubject iField = staticClassSubject.field("int", "i");
+    if (forceProguardCompatibility) {
+      if (fromProguard) {
+        // Proguard keeps the instance method and it code even though the class does not have a
+        // constructor, and therefore cannot be instantiated.
+        assertThat(getIMethod, isPresent());
+        assertThat(iField, isPresent());
+      } else {
+        // Force Proguard compatibility keeps the instance method, but makes it abstract as
+        // the class does not have a constructor, and therefore cannot be instantiated.
+        assertThat(getIMethod, isAbstract());
+        // As the method is abstract the referenced field is not present.
+        assertThat(iField, not(isPresent()));
+      }
+    } else {
+      assertThat(getIMethod, not(isPresent()));
+      assertThat(iField, not(isPresent()));
     }
+    assertThat(staticClassSubject.method("int", "getJ", ImmutableList.of()), not(isPresent()));
+    assertThat(staticClassSubject.field("int", "j"), not(isPresent()));
+  }
+
+  private void runTest(Class mainClass, Class<?> staticClass,
+      boolean forceProguardCompatibility) throws Exception {
     String proguardConfig = String.join("\n", ImmutableList.of(
         "-keepclassmembers class **.PureStatic* {",
         "  public static int b;",
@@ -35,27 +70,16 @@
         "}",
         "-dontoptimize", "-dontobfuscate"
     ));
-    DexInspector inspector = new DexInspector(
-        compileWithR8(ImmutableList.of(mainClass, staticClass), proguardConfig,
-            options -> options.forceProguardCompatibility = forceProguardCompatibility));
-    assertTrue(inspector.clazz(mainClass).isPresent());
-    ClassSubject staticClassSubject = inspector.clazz(staticClass);
-    assertTrue(staticClassSubject.isPresent());
-    assertTrue(staticClassSubject.method("int", "getA", ImmutableList.of()).isPresent());
-    assertFalse(staticClassSubject.method("int", "getB", ImmutableList.of()).isPresent());
-    assertTrue(staticClassSubject.field("int", "a").isPresent());
-    assertTrue(staticClassSubject.field("int", "b").isPresent());
-    assertFalse(staticClassSubject.field("int", "c").isPresent());
-    // Force Proguard compatibility keeps the default constructor if present and then assumes
-    // instantiated, hence keeps the instance method as well.
-    assertEquals(forceProguardCompatibility && staticClassHasDefaultConstructor,
-        staticClassSubject.init(ImmutableList.of()).isPresent());
-    assertEquals(forceProguardCompatibility && staticClassHasDefaultConstructor,
-        staticClassSubject.method("int", "getI", ImmutableList.of()).isPresent());
-    assertEquals(forceProguardCompatibility && staticClassHasDefaultConstructor,
-        staticClassSubject.field("int", "i").isPresent());
-    assertFalse(staticClassSubject.method("int", "getJ", ImmutableList.of()).isPresent());
-    assertFalse(staticClassSubject.field("int", "j").isPresent());
+    DexInspector inspector;
+      inspector = new DexInspector(
+          compileWithR8(ImmutableList.of(mainClass, staticClass), proguardConfig,
+              options -> options.forceProguardCompatibility = forceProguardCompatibility));
+    check(inspector, mainClass, staticClass, forceProguardCompatibility, false);
+
+    if (isRunProguard()) {
+      inspector = runProguard(ImmutableList.of(mainClass, staticClass), proguardConfig);
+      check(inspector, mainClass, staticClass, true, true);
+    }
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 95ecd4f..f0d5d37 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+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;
@@ -135,6 +136,12 @@
             .read(app.getProguardMapOutputData()));
   }
 
+  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;
@@ -614,6 +621,16 @@
     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 {
@@ -905,6 +922,11 @@
     public DexEncodedField getField() {
       return dexField;
     }
+
+    @Override
+    public String toString() {
+      return dexField.toSourceString();
+    }
   }
 
   public class TypeSubject extends Subject {
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java b/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java
new file mode 100644
index 0000000..fc9bc29
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java
@@ -0,0 +1,101 @@
+// 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;
+
+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 static Matcher<Subject> isPresent() {
+    return new TypeSafeMatcher<Subject>() {
+      private String type(Subject subject) {
+        String type = "<unknown subject type>";
+        if (subject instanceof ClassSubject) {
+          type = "class";
+        } else if (subject instanceof MethodSubject) {
+          type = "method";
+        } else if (subject instanceof FieldSubject) {
+          type = "field";
+        }
+        return type;
+      }
+
+      private String name(Subject subject) {
+        String name = "<unknown>";
+        if (subject instanceof ClassSubject) {
+          name = ((ClassSubject) subject).getOriginalName();
+        } else if (subject instanceof MethodSubject) {
+          name = ((MethodSubject) subject).getOriginalName();
+        } else if (subject instanceof FieldSubject) {
+          name = ((FieldSubject) subject).getOriginalName();
+        }
+        return name;
+      }
+
+      @Override
+      public boolean matchesSafely(final Subject clazz) {
+        return clazz.isPresent();
+      }
+
+      @Override
+      public void describeTo(final Description description) {
+        description.appendText(" present");
+      }
+
+      @Override
+      public void describeMismatchSafely(final Subject subject, Description description) {
+        description
+            .appendText(type(subject) + " ").appendValue(name(subject)).appendText(" was not");
+      }
+    };
+  }
+
+  public static Matcher<ClassSubject> hasDefaultConstructor() {
+    return new TypeSafeMatcher<ClassSubject>() {
+      @Override
+      public boolean matchesSafely(final ClassSubject clazz) {
+        return clazz.init(ImmutableList.of()).isPresent();
+      }
+
+      @Override
+      public void describeTo(final Description description) {
+        description.appendText("class having default constructor");
+      }
+
+      @Override
+      public void describeMismatchSafely(final ClassSubject clazz, Description description) {
+        description
+            .appendText("class ").appendValue(clazz.getOriginalName()).appendText(" did not");
+      }
+    };
+  }
+
+  public static Matcher<MethodSubject> isAbstract() {
+    return new TypeSafeMatcher<MethodSubject>() {
+      @Override
+      public boolean matchesSafely(final MethodSubject method) {
+        return method.isPresent() && method.isAbstract();
+      }
+
+      @Override
+      public void describeTo(final Description description) {
+        description.appendText("method abstract");
+      }
+
+      @Override
+      public void describeMismatchSafely(final MethodSubject method, Description description) {
+        description
+            .appendText("method ").appendValue(method.getOriginalName()).appendText(" was not");
+      }
+    };
+  }
+}
diff --git a/third_party/benchmarks/santa-tracker.tar.gz.sha1 b/third_party/benchmarks/santa-tracker.tar.gz.sha1
new file mode 100644
index 0000000..d37a000
--- /dev/null
+++ b/third_party/benchmarks/santa-tracker.tar.gz.sha1
@@ -0,0 +1 @@
+88150a9a2215f1f821ea9321e61b5fc8276dffd3
\ No newline at end of file
diff --git a/third_party/gradle-plugin.tar.gz.sha1 b/third_party/gradle-plugin.tar.gz.sha1
new file mode 100644
index 0000000..2fd1633
--- /dev/null
+++ b/third_party/gradle-plugin.tar.gz.sha1
@@ -0,0 +1 @@
+a0bea68a3b71d485296fe6aec54f995cdb6dd300
\ No newline at end of file
diff --git a/third_party/kotlin.tar.gz.sha1 b/third_party/kotlin.tar.gz.sha1
index e55712d..40f26fa 100644
--- a/third_party/kotlin.tar.gz.sha1
+++ b/third_party/kotlin.tar.gz.sha1
@@ -1 +1 @@
-6dc49791e5fcf4318ae5246eacc73718180508ce
\ No newline at end of file
+4b18d827485f53990ad47b81db2a025abaa325d1
\ No newline at end of file
diff --git a/third_party/proguard/README.google b/third_party/proguard/README.google
index 4f85601..abe0568 100644
--- a/third_party/proguard/README.google
+++ b/third_party/proguard/README.google
@@ -1,8 +1,8 @@
 URL: https://sourceforge.net/projects/proguard/files/proguard/5.2/
 URL: https://sourceforge.net/projects/proguard/files/proguard/6.0/
-Version: 5.2.1, 6.0
+Version: 5.2.1, 6.0.1
 License: GPL
-License File: proguard5.2.1/docs/license.html, proguard6.0/docs/license.html
+License File: proguard5.2.1/docs/license.html, proguard6.0.1/docs/license.html
 
 Description:
 ProGuard Java Optimizer and Obfuscator
diff --git a/third_party/proguard/proguard6.0.1.tar.gz.sha1 b/third_party/proguard/proguard6.0.1.tar.gz.sha1
new file mode 100644
index 0000000..e5e8e3c
--- /dev/null
+++ b/third_party/proguard/proguard6.0.1.tar.gz.sha1
@@ -0,0 +1 @@
+ef075c414299327dae5f96cce539422dc9088946
\ No newline at end of file
diff --git a/third_party/proguard/proguard6.0.tar.gz.sha1 b/third_party/proguard/proguard6.0.tar.gz.sha1
deleted file mode 100644
index 4596bc8..0000000
--- a/third_party/proguard/proguard6.0.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-57d0702f38196c81ff506d2e34a4a5569c3af583
\ No newline at end of file
diff --git a/tools/create_art_tests.py b/tools/create_art_tests.py
index feb36a2..13757a9 100755
--- a/tools/create_art_tests.py
+++ b/tools/create_art_tests.py
@@ -16,7 +16,7 @@
 JACK_TEST = os.path.join('tests', '2016-12-19', 'art')
 TEST_DIR = os.path.join('tests', '2017-10-04', 'art')
 TOOLCHAINS = ["dx", "jack", "none"]
-TOOLS = ["r8", "d8"]
+TOOLS = ["r8", "d8", "r8cf"]
 TEMPLATE = Template(
 """// 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
@@ -54,6 +54,7 @@
     rmtree(toolchain_dir)
   makedirs(join(toolchain_dir, "d8"))
   makedirs(join(toolchain_dir, "r8"))
+  makedirs(join(toolchain_dir, "r8cf"))
 
 def write_file(toolchain, tool, class_name, contents):
   file_name = join(OUTPUT_DIR, toolchain, tool, class_name + ".java")
@@ -74,6 +75,10 @@
         tool_enum = 'R8_AFTER_D8'
       else:
         tool_enum = upper(tool)
+      if tool == "r8cf":
+        if toolchain != "none":
+          continue
+        tool_enum = 'D8_AFTER_R8CF'
       contents = TEMPLATE.substitute(
           name=dir,
           compilerUnderTestEnum=tool_enum,