Merge "Add santa-tracker benchmark app"
diff --git a/build.gradle b/build.gradle
index f1abcb7..64c13e3 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",
@@ -357,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/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 42acb7c..d6b15eb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -146,6 +146,46 @@
     return result;
   }
 
+  /**
+   * For all annotations on the class and all annotations on its methods and fields apply the
+   * specified consumer.
+   */
+  public void forEachAnnotation(Consumer<DexAnnotation> consumer) {
+    for (DexAnnotation annotation : annotations.annotations) {
+      consumer.accept(annotation);
+    }
+    for (DexEncodedMethod method : directMethods()) {
+      for (DexAnnotation annotation : method.annotations.annotations) {
+        consumer.accept(annotation);
+      }
+      for (DexAnnotationSet parameterAnnotations : method.parameterAnnotations.values) {
+        for (DexAnnotation annotation : parameterAnnotations.annotations) {
+          consumer.accept(annotation);
+        }
+      }
+    }
+    for (DexEncodedMethod method : virtualMethods()) {
+      for (DexAnnotation annotation : method.annotations.annotations) {
+        consumer.accept(annotation);
+      }
+      for (DexAnnotationSet parameterAnnotations : method.parameterAnnotations.values) {
+        for (DexAnnotation annotation : parameterAnnotations.annotations) {
+          consumer.accept(annotation);
+        }
+      }
+    }
+    for (DexEncodedField field : instanceFields()) {
+      for (DexAnnotation annotation : field.annotations.annotations) {
+        consumer.accept(annotation);
+      }
+    }
+    for (DexEncodedField field : staticFields()) {
+      for (DexAnnotation annotation : field.annotations.annotations) {
+        consumer.accept(annotation);
+      }
+    }
+  }
+
   public void forEachField(Consumer<DexEncodedField> consumer) {
     for (DexEncodedField field : staticFields()) {
       consumer.accept(field);
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/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/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/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/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index de21187..5c51786 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -85,13 +85,13 @@
         addMainDexType(dexType);
         continue;
       }
-      for (DexAnnotation annotation : clazz.annotations.annotations) {
-        if (annotation.visibility == DexAnnotation.VISIBILITY_RUNTIME
+      clazz.forEachAnnotation(annotation -> {
+        if (!mainDexTypes.contains(dexType)
+            && annotation.visibility == DexAnnotation.VISIBILITY_RUNTIME
             && isAnnotationWithEnum(annotation.annotation.type)) {
           addMainDexType(dexType);
-          break;
         }
-      }
+      });
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index b526973..e96de6f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -535,7 +535,7 @@
     private ProguardIfRule parseIfRule(TextPosition optionStart)
         throws ProguardRuleParserException {
       ProguardIfRule.Builder ifRuleBuilder = ProguardIfRule.builder();
-      parseClassSpec(ifRuleBuilder, true);
+      parseClassSpec(ifRuleBuilder, false);
 
       // Required a subsequent keep rule.
       skipWhitespace();
@@ -1086,15 +1086,52 @@
       return Integer.parseInt(s);
     }
 
+    private final Predicate<Character> CLASS_NAME_PREDICATE =
+        character -> IdentifierUtils.isDexIdentifierPart(character)
+            || character == '.'
+            || character == '*'
+            || character == '?'
+            || character == '%'
+            || character == '['
+            || character == ']';
+
     private String acceptClassName() {
-      return acceptString(character ->
-          IdentifierUtils.isDexIdentifierPart(character)
-              || character == '.'
-              || character == '*'
-              || character == '?'
-              || character == '%'
-              || character == '['
-              || character == ']');
+      return acceptString(CLASS_NAME_PREDICATE);
+    }
+
+    private String acceptClassNameWithNthWildcard() {
+      StringBuilder nthWildcard = null;
+      skipWhitespace();
+      int start = position;
+      int end = position;
+      while (!eof(end)) {
+        char current = contents.charAt(end);
+        if (nthWildcard != null) {
+          if (current == '>') {
+            try {
+              Integer.parseUnsignedInt(nthWildcard.toString());
+            } catch (NullPointerException | NumberFormatException e) {
+              return null;
+            }
+            nthWildcard = null;
+          } else {
+            nthWildcard.append(current);
+          }
+          end++;
+        } else if (CLASS_NAME_PREDICATE.test(current)) {
+          end++;
+        } else if (current == '<') {
+          nthWildcard = new StringBuilder();
+          end++;
+        } else {
+          break;
+        }
+      }
+      if (start == end) {
+        return null;
+      }
+      position = end;
+      return contents.substring(start, end);
     }
 
     private String acceptFieldNameOrIntegerForReturn() {
@@ -1194,7 +1231,7 @@
     }
 
     private String parseClassName() throws ProguardRuleParserException {
-      String name = acceptClassName();
+      String name = acceptClassNameWithNthWildcard();
       if (name == null) {
         throw parseError("Class name expected");
       }
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/examples/multidex002/AnnotatedDirectMethod.java b/src/test/examples/multidex002/AnnotatedDirectMethod.java
new file mode 100644
index 0000000..761ea9a
--- /dev/null
+++ b/src/test/examples/multidex002/AnnotatedDirectMethod.java
@@ -0,0 +1,12 @@
+// 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 multidex002;
+
+public class AnnotatedDirectMethod {
+
+  @AnnotationWithEnum3(AnnotationWithEnum3.Value3.VAL3_2)
+  private void foo() {
+  }
+}
diff --git a/src/test/examples/multidex002/AnnotatedInstanceField.java b/src/test/examples/multidex002/AnnotatedInstanceField.java
new file mode 100644
index 0000000..0209f44
--- /dev/null
+++ b/src/test/examples/multidex002/AnnotatedInstanceField.java
@@ -0,0 +1,11 @@
+// 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 multidex002;
+
+public class AnnotatedInstanceField {
+
+  @AnnotationWithEnum3(AnnotationWithEnum3.Value3.VAL3_2)
+  public String value = "myValue";
+}
diff --git a/src/test/examples/multidex002/AnnotatedMethodParameter.java b/src/test/examples/multidex002/AnnotatedMethodParameter.java
new file mode 100644
index 0000000..49a9e6d
--- /dev/null
+++ b/src/test/examples/multidex002/AnnotatedMethodParameter.java
@@ -0,0 +1,11 @@
+// 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 multidex002;
+
+public class AnnotatedMethodParameter {
+
+  public void foo(@AnnotationWithEnum3(AnnotationWithEnum3.Value3.VAL3_2) String val) {
+  }
+}
diff --git a/src/test/examples/multidex002/AnnotatedNotKept.java b/src/test/examples/multidex002/AnnotatedNotKept.java
new file mode 100644
index 0000000..500e7f0
--- /dev/null
+++ b/src/test/examples/multidex002/AnnotatedNotKept.java
@@ -0,0 +1,10 @@
+// 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 multidex002;
+
+@AnnotationWithoutEnum
+public class AnnotatedNotKept {
+
+}
diff --git a/src/test/examples/multidex002/AnnotatedStaticField.java b/src/test/examples/multidex002/AnnotatedStaticField.java
new file mode 100644
index 0000000..385d1d9
--- /dev/null
+++ b/src/test/examples/multidex002/AnnotatedStaticField.java
@@ -0,0 +1,9 @@
+// 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 multidex002;
+
+public class AnnotatedStaticField {
+  @AnnotationWithEnum3(AnnotationWithEnum3.Value3.VAL3_2) public static String sValue = "myValue";
+}
diff --git a/src/test/examples/multidex002/AnnotatedVirtualMethod.java b/src/test/examples/multidex002/AnnotatedVirtualMethod.java
new file mode 100644
index 0000000..569bf84
--- /dev/null
+++ b/src/test/examples/multidex002/AnnotatedVirtualMethod.java
@@ -0,0 +1,13 @@
+// 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 multidex002;
+
+public class AnnotatedVirtualMethod {
+
+  @AnnotationWithEnum3(AnnotationWithEnum3.Value3.VAL3_2)
+  public void foo() {
+
+  }
+}
diff --git a/src/test/examples/multidex002/AnnotationWithEnum3.java b/src/test/examples/multidex002/AnnotationWithEnum3.java
new file mode 100644
index 0000000..a8f8344
--- /dev/null
+++ b/src/test/examples/multidex002/AnnotationWithEnum3.java
@@ -0,0 +1,19 @@
+// 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 multidex002;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AnnotationWithEnum3 {
+
+  enum Value3 {
+    VAL3_1,
+    VAL3_2,
+  }
+
+  Value3 value();
+}
diff --git a/src/test/examples/multidex002/AnnotationWithoutEnum.java b/src/test/examples/multidex002/AnnotationWithoutEnum.java
new file mode 100644
index 0000000..5244971
--- /dev/null
+++ b/src/test/examples/multidex002/AnnotationWithoutEnum.java
@@ -0,0 +1,13 @@
+// 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 multidex002;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AnnotationWithoutEnum {
+
+}
diff --git a/src/test/examples/multidex002/ref-list-1.txt b/src/test/examples/multidex002/ref-list-1.txt
index 26dae34..922d417 100644
--- a/src/test/examples/multidex002/ref-list-1.txt
+++ b/src/test/examples/multidex002/ref-list-1.txt
@@ -1,8 +1,15 @@
 Lmultidex002/Annotated2;
 Lmultidex002/Annotated;
+Lmultidex002/AnnotatedDirectMethod;
+Lmultidex002/AnnotatedInstanceField;
+Lmultidex002/AnnotatedMethodParameter;
+Lmultidex002/AnnotatedStaticField;
+Lmultidex002/AnnotatedVirtualMethod;
 Lmultidex002/AnnotationWithClass;
 Lmultidex002/AnnotationWithEnum2;
+Lmultidex002/AnnotationWithEnum3;
 Lmultidex002/AnnotationWithEnum;
+Lmultidex002/AnnotationWithoutEnum;
 Lmultidex002/InterfaceWithEnum;
 Lmultidex002/IntermediateClass;
 Lmultidex002/MainActivity;
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 bf409d8..4b6b962 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -67,6 +67,7 @@
 
   @Rule
   public ExpectedException thrown = ExpectedException.none();
+  private boolean expectedException = false;
 
   public enum DexTool {
     JACK,
@@ -145,10 +146,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.
@@ -612,11 +609,6 @@
               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)))
           // VFY: tried to get class from non-ref register.
           .put("506-verify-aput",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
@@ -679,6 +671,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();
@@ -766,12 +759,6 @@
               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))
           // 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.
@@ -780,12 +767,6 @@
               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))
           // Class not found.
           .put("529-checker-unresolved", TestCondition.any())
@@ -1112,18 +1093,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;
@@ -1134,10 +1125,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) {
@@ -1192,7 +1203,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()) {
@@ -1252,12 +1264,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.
@@ -1300,7 +1311,8 @@
                 expectedToFailWithCompilerSet.contains(name),
                 outputMayDiffer.contains(name),
                 requireInliningToBeDisabled.contains(name),
-                hasMissingClasses.contains(name)));
+                hasMissingClasses.contains(name),
+                dexVm));
       }
     }
     return data;
@@ -1353,11 +1365,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);
@@ -1493,12 +1500,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,
@@ -1515,9 +1527,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;
@@ -1600,9 +1616,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;
       }
@@ -1658,7 +1678,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()) {
@@ -1668,7 +1688,7 @@
     builder.appendProgramArgument(fullClassName);
 
     if (specification.failsWithArt) {
-      thrown.expect(AssertionError.class);
+      expectException(AssertionError.class);
     }
 
     try {
@@ -1684,8 +1704,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
@@ -1694,11 +1713,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 {
@@ -1712,7 +1731,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;
     }
@@ -1743,12 +1762,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) {
@@ -1768,7 +1792,7 @@
       resultDir = temp.newFolder("r8-output");
 
       runArtTestDoRunOnArt(
-          version, CompilerUnderTest.R8, specification, fileNames, resultDir, compilationMode);
+          dexVm, CompilerUnderTest.R8, specification, fileNames, resultDir, compilationMode);
     }
   }
 
@@ -1793,7 +1817,7 @@
       CompilationMode compilationMode)
       throws Throwable {
     if (specification.expectedToFailWithX8) {
-      thrown.expect(CompilationError.class);
+      expectException(CompilationError.class);
       try {
         executeCompilerUnderTest(
             compilerUnderTest, fileNames, resultDir.getCanonicalPath(), compilationMode,
@@ -1806,7 +1830,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);
@@ -1839,7 +1863,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);
@@ -1876,7 +1900,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);
@@ -1886,7 +1910,7 @@
           }
         }
         if (specification.failsWithArtOutput) {
-          thrown.expect(ComparisonFailure.class);
+          expectException(ComparisonFailure.class);
         }
         if (!specification.outputMayDiffer) {
           assertEquals(expected, output);
@@ -1895,6 +1919,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/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/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/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 5f6a5cc..84f6a08 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -37,6 +37,7 @@
 import java.util.Collections;
 import java.util.List;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class ProguardConfigurationParserTest extends TestBase {
@@ -544,7 +545,7 @@
     assertTrue(
         config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
     assertTrue(
-        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobar;")));
     assertTrue(
         config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoobar;")));
   }
@@ -561,12 +562,47 @@
     assertTrue(
         config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
     assertTrue(
-        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobar;")));
     assertTrue(
         config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoobar;")));
   }
 
   @Test
+  public void testAdaptClassStringsNthWildcard() throws Exception {
+    DexItemFactory dexItemFactory = new DexItemFactory();
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(dexItemFactory, reporter);
+    String wildcard = "-adaptclassstrings *foo<1>";
+    parser.parse(createConfigurationForTesting(ImmutableList.of(wildcard)));
+    verifyParserEndsCleanly();
+    ProguardConfiguration config = parser.getConfig();
+    assertFalse(
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoobar;")));
+    assertFalse(
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboofoobar;")));
+    // TODO(b/73800755): Use <n> while matching class name list.
+    //assertTrue(
+    //    config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboofooboo;")));
+  }
+
+  @Ignore("b/73800755: verify the range of <n>")
+  @Test
+  public void testAdaptClassStringsNthWildcard_outOfRange() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-adaptclassstrings *foo<2>"
+    );
+    try {
+      ProguardConfigurationParser parser =
+          new ProguardConfigurationParser(new DexItemFactory(), reporter);
+      parser.parse(proguardConfig);
+      fail();
+    } catch (AbortException e) {
+      checkDiagnostic(handler.errors, proguardConfig, 1, 1,
+          "wildcard", "out", "range");
+    }
+  }
+
+  @Test
   public void testIdentifierNameString() throws Exception {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
@@ -1173,6 +1209,43 @@
   }
 
   @Test
+  public void parse_if_nthWildcard() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-if class **$R**",
+        "-keep class **$D<2>"  // <2> corresponds to the 2nd ** in -if rule.
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+        "Ignoring", "-if");
+    ProguardConfiguration config = parser.getConfig();
+    assertEquals(1, config.getRules().size());
+    ProguardIfRule if0 = (ProguardIfRule) config.getRules().get(0);
+    assertEquals("**$R**", if0.getClassNames().toString());
+    assertEquals(ProguardKeepRuleType.KEEP, if0.subsequentRule.getType());
+    assertEquals("**$D<2>", if0.subsequentRule.getClassNames().toString());
+  }
+
+  @Ignore("b/73800755: verify the range of <n>")
+  @Test
+  public void parse_if_nthWildcard_outOfRange() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-if class **$R**",
+        "-keep class **D<4>"  // There are 3 ** in this rule.
+    );
+    try {
+      ProguardConfigurationParser parser =
+          new ProguardConfigurationParser(new DexItemFactory(), reporter);
+      parser.parse(proguardConfig);
+      fail();
+    } catch (AbortException e) {
+      checkDiagnostic(handler.errors, proguardConfig, 1, 1,
+          "wildcard", "out", "range");
+    }
+  }
+
+  @Test
   public void parse_if_if() throws Exception {
     Path proguardConfig = writeTextToTempFile(
         "-if   class **$$ModuleAdapter",
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..39e804a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
@@ -0,0 +1,299 @@
+// 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 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 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::checkAllClassesPresentWithDefaultConstructor,
+        this::checkOnlyMainPresent);
+  }
+
+  @Test
+  public void testStaticFieldWithInitialization() throws Exception {
+    Class mainClass = MainGetStaticFieldInitialized.class;
+    runTest(
+        mainClass,
+        ImmutableList.of(mainClass, StaticFieldInitialized.class),
+        keepMainProguardConfiguration(mainClass),
+        // TODO(74233021): Proguard does not keep the default constructor for the class with the
+        // un-initialized static field.
+        this::checkAllClassesPresentWithDefaultConstructor,
+        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")),
+        // TODO(74233021): Proguard does not keep the default constructor for the class with the
+        // initialized static field.
+        this::checkAllClassesPresentWithDefaultConstructor,
+        this::checkAllClassesPresentOnlyMainWithDefaultConstructor);
+  }
+
+  @Test
+  public void testStaticFieldWithInitializationWithoutInlining() throws Exception {
+    Class mainClass = MainGetStaticFieldInitialized.class;
+    runTest(
+        mainClass,
+        ImmutableList.of(mainClass, StaticFieldInitialized.class),
+        keepMainProguardConfiguration(mainClass, ImmutableList.of("-dontoptimize")),
+        // TODO(74233021): Proguard does not keep the default constructor for the class with the
+        // un-initialized static field.
+        this::checkAllClassesPresentWithDefaultConstructor,
+        this::checkAllClassesPresentOnlyMainWithDefaultConstructor);
+  }
+
+  @Test
+  public void testStaticMethodStaticWithoutInlining() throws Exception {
+    Class mainClass = MainCallStaticMethod.class;
+    runTest(
+        mainClass,
+        ImmutableList.of(mainClass, StaticMethod.class),
+        keepMainProguardConfiguration(mainClass, ImmutableList.of("-dontoptimize")),
+        // TODO(74233021): Proguard does not keep the default constructor for the class with the
+        // initialized static field.
+        this::checkAllClassesPresentWithDefaultConstructor,
+        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/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..8e1db2c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java
@@ -0,0 +1,54 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public class DexInspectorMatchers {
+
+  public static Matcher<ClassSubject> isPresent() {
+    return new TypeSafeMatcher<ClassSubject>() {
+      @Override
+      public boolean matchesSafely(final ClassSubject clazz) {
+        return clazz.isPresent();
+      }
+
+      @Override
+      public void describeTo(final Description description) {
+        description.appendText("class present");
+      }
+
+      @Override
+      public void describeMismatchSafely(final ClassSubject clazz, Description description) {
+        description
+            .appendText("class ").appendValue(clazz.getOriginalName()).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");
+      }
+    };
+  }
+}
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