Merge "Keep synthesized '$r8$twr$utility' utility class source method."
diff --git a/build.gradle b/build.gradle
index 5631d94..41f4390 100644
--- a/build.gradle
+++ b/build.gradle
@@ -254,8 +254,10 @@
     apt "com.google.auto.value:auto-value:$autoValueVersion"
 }
 
-def r8LibPath = "build/libs/r8lib.jar"
-def r8LibClassesPath = "build/classes/r8lib"
+def r8LibPath = "$buildDir/libs/r8lib.jar"
+def r8LibClassesPath = "$buildDir/classes/r8lib"
+def r8LibGeneratedKeepRulesPath = "$buildDir/generated/keep.txt"
+def r8LibTestPath = "$buildDir/classes/r8libtest"
 
 def osString = OperatingSystem.current().isLinux() ? "linux" :
         OperatingSystem.current().isMacOsX() ? "mac" : "windows"
@@ -276,6 +278,8 @@
                 "android_jar/lib-v24",
                 "android_jar/lib-v25",
                 "android_jar/lib-v26",
+                "android_jar/lib-v27",
+                "android_jar/lib-v28",
                 "core-lambda-stubs",
                 "dart-sdk",
                 "ddmlib",
@@ -635,21 +639,24 @@
     }
 }
 
+def baseR8CommandLine(args = []) {
+    return ["java", "-ea", "-jar", R8.outputs.files[0]] + args
+}
+
 def r8CfCommandLine(input, output, pgconf, args = [], libs = []) {
-    return [
-        "java", "-ea", "-jar", R8.outputs.files[0],
-        "--classfile", "--release",
-        input,
-        "--output", output,
-        "--pg-conf", pgconf,
-        "--pg-map-output", output + ".map",
-        "--lib", "third_party/openjdk/openjdk-rt-1.8/rt.jar"
-    ] + args + libs.collectMany { ["--lib", it] }
+    return baseR8CommandLine([
+            "--classfile", "--release",
+            input,
+            "--output", output,
+            "--pg-conf", pgconf,
+            "--pg-map-output", output + ".map",
+            "--lib", "third_party/openjdk/openjdk-rt-1.8/rt.jar"
+        ] + args + libs.collectMany { ["--lib", it] })
 }
 
 def r8LibCreateTask(name, pgConf, r8Task, output, args = [], libs = []) {
     return tasks.create("r8Lib${name}", Exec) {
-        inputs.files ([pgConf, R8.outputs] + [r8Task.outputs])
+        inputs.files ([pgConf, R8.outputs, r8Task.outputs])
         outputs.file output
         dependsOn downloadOpenJDKrt
         commandLine r8CfCommandLine(r8Task.outputs.files[0], output, pgConf, args, libs)
@@ -657,19 +664,48 @@
     }
 }
 
+task testJar(type: Jar, dependsOn: testClasses) {
+    baseName = "r8tests"
+    from sourceSets.test.output
+}
+
+task generateR8LibKeepRules(type: Exec) {
+    doFirst {
+        standardOutput new FileOutputStream(r8LibGeneratedKeepRulesPath)
+    }
+    dependsOn R8
+    dependsOn r8WithoutDeps
+    dependsOn testJar
+    dependsOn downloadOpenJDKrt
+    inputs.files ([R8.outputs, r8WithoutDeps.outputs, testJar.outputs])
+    outputs.file r8LibGeneratedKeepRulesPath
+    commandLine baseR8CommandLine([
+            "printuses",
+            "--keeprules",
+            "third_party/openjdk/openjdk-rt-1.8/rt.jar",
+            r8WithoutDeps.outputs.files[0],
+            testJar.outputs.files[0]])
+    workingDir = projectDir
+    // TODO(b/121350534) Keep this until we have proper generation of keep rules for r8lib.
+    doLast {
+        new File(r8LibGeneratedKeepRulesPath).append("\n-keep class ** { *; }\n")
+    }
+}
+
 task R8LibNoDeps {
     dependsOn r8LibCreateTask(
             "NoDeps",
             "src/main/keep.txt",
             r8WithoutDeps,
             r8LibPath,
-            ["--no-tree-shaking", "--no-minification"],
+            ["--pg-conf", generateR8LibKeepRules.outputs.files[0]],
             repackageDepsNoRelocate.outputs.files
-    ).dependsOn(repackageDepsNoRelocate, r8WithoutDeps)
+    ).dependsOn(repackageDepsNoRelocate, r8WithoutDeps, generateR8LibKeepRules)
 }
 
 task R8Lib {
     dependsOn r8LibCreateTask("Main", "src/main/keep.txt", R8NoManifest, r8LibPath)
+    outputs.file r8LibPath
 }
 
 task CompatDxLib {
@@ -1344,24 +1380,37 @@
     workingDir = projectDir
 }
 
-task configureTestForR8Lib(type: Copy) {
-    def r8LibTask;
+def getR8LibTask() {
     if (project.hasProperty('r8lib')) {
-        r8LibTask = R8Lib
+        return R8Lib
     } else if (project.hasProperty('r8lib_no_deps')) {
-        r8LibTask = R8LibNoDeps
+        return R8LibNoDeps
     }
+    return null
+}
+
+task copyR8LibForTests(type: Copy) {
+    def r8LibTask = getR8LibTask()
     if (r8LibTask != null) {
         dependsOn r8LibTask
         from(zipTree(r8LibPath))
         into(r8LibClassesPath)
-        outputs.file r8LibClassesPath
-        doLast {
-            test {
-                classpath = (classpath - sourceSets.main.output) + files(r8LibClassesPath)
-            }
-        }
     }
+    outputs.dir r8LibClassesPath
+}
+
+task configureTestForR8Lib(type: Copy) {
+    dependsOn testClasses
+    dependsOn copyR8LibForTests
+    // Setting classpath triggers a scan for test files in $buildDir/classes/test that finds all
+    // tests and not just the ones under $test/com/android/tools/r8. That is generally not
+    // something we want so we just copy the desired test files to $r8LibTestPath.
+    if (getR8LibTask() != null) {
+        // Cannot use sourceSets.test.output here since it will copy all tests.
+        from("$buildDir/classes/test/com/android/tools/r8")
+        into(r8LibTestPath + "/com/android/tools/r8")
+    }
+    outputs.dir r8LibTestPath
 }
 
 test {
@@ -1459,7 +1508,13 @@
     if (project.hasProperty('aosp_jar')) {
         dependsOn AospJarTest
     }
-
+    if (project.hasProperty('r8lib') || project.hasProperty('r8lib_no_deps')) {
+        dependsOn configureTestForR8Lib
+        classpath =
+                (sourceSets.test.runtimeClasspath - sourceSets.main.output) +
+                        files(r8LibClassesPath)
+        testClassesDir = new File(r8LibTestPath)
+    }
     if (OperatingSystem.current().isLinux()
             || OperatingSystem.current().isMacOsX()
             || OperatingSystem.current().isWindows()) {
@@ -1486,9 +1541,6 @@
         logger.lifecycle("WARNING: Testing in not supported on your platform. Testing is only fully supported on " +
             "Linux and partially supported on Mac OS and Windows. Art does not run on other platforms.")
     }
-    if (project.hasProperty('r8lib') || project.hasProperty('r8lib_no_deps')) {
-        dependsOn configureTestForR8Lib
-    }
 }
 
 // The Art tests we use for R8 are pre-build and downloaded from Google Cloud Storage.
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 48c67fd..48c2cde 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -4,12 +4,12 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.graphinfo.GraphConsumer;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.MainDexClasses;
@@ -44,7 +44,8 @@
       DexApplication application =
           new ApplicationReader(app, options, timing).read(executor).toDirect();
       AppView<? extends AppInfoWithSubtyping> appView =
-          new AppView<>(new AppInfoWithSubtyping(application), GraphLense.getIdentityLense());
+          new AppView<>(
+              new AppInfoWithSubtyping(application), GraphLense.getIdentityLense(), options);
       RootSet mainDexRootSet =
           new RootSetBuilder(appView, application, options.mainDexKeepRules, options).run(executor);
 
@@ -73,7 +74,6 @@
 
       // Print -whyareyoukeeping results if any.
       if (whyAreYouKeepingConsumer != null) {
-        // TODO(b/120959039): This should be ordered!
         for (DexDefinition definition : mainDexRootSet.reasonAsked) {
           whyAreYouKeepingConsumer.printWhyAreYouKeeping(
               enqueuer.getGraphNode(definition), System.out);
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
index a1a2837..a55033a 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -3,8 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graphinfo.GraphConsumer;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index 0d8524b..475ca70 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -84,15 +84,16 @@
       DexApplication application =
           new ApplicationReader(command.getInputApp(), options, timing).read(executor).toDirect();
       AppView<? extends AppInfoWithSubtyping> appView =
-          new AppView<>(new AppInfoWithSubtyping(application), GraphLense.getIdentityLense());
+          new AppView<>(
+              new AppInfoWithSubtyping(application), GraphLense.getIdentityLense(), options);
       RootSet rootSet =
           new RootSetBuilder(
-                  appView, application, options.proguardConfiguration.getRules(), options)
+                  appView, application, options.getProguardConfiguration().getRules(), options)
               .run(executor);
       Enqueuer enqueuer = new Enqueuer(appView, options, null);
       AppInfoWithLiveness appInfo =
           enqueuer.traceApplication(
-              rootSet, options.proguardConfiguration.getDontWarnPatterns(), executor, timing);
+              rootSet, options.getProguardConfiguration().getDontWarnPatterns(), executor, timing);
       RootSetBuilder.writeSeeds(
           appInfo, System.out, type -> descriptors.contains(type.toDescriptorString()));
     } catch (ExecutionException e) {
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 42fed08..d5f0156 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfo.ResolutionResult;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -15,6 +16,9 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
@@ -22,7 +26,6 @@
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import java.io.IOException;
-import java.io.PrintStream;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
@@ -46,19 +49,20 @@
 public class PrintUses {
 
   private static final String USAGE =
-      "Arguments: <rt.jar> <r8.jar> <sample.jar>\n"
+      "Arguments: [--keeprules] <rt.jar> <r8.jar> <sample.jar>\n"
           + "\n"
           + "PrintUses prints the classes, interfaces, methods and fields used by <sample.jar>,\n"
           + "restricted to classes and interfaces in <r8.jar> that are not in <sample.jar>.\n"
           + "<rt.jar> and <r8.jar> should point to libraries used by <sample.jar>.\n"
           + "\n"
           + "The output is in the same format as what is printed when specifying -printseeds in\n"
-          + "a ProGuard configuration file. See also the "
+          + "a ProGuard configuration file. Use --keeprules for outputting proguard keep rules. "
+          + "See also the "
           + PrintSeeds.class.getSimpleName()
           + " program in R8.";
 
   private final Set<String> descriptors;
-  private final PrintStream out;
+  private final Printer printer;
   private Set<DexType> types = Sets.newIdentityHashSet();
   private Map<DexType, Set<DexMethod>> methods = Maps.newIdentityHashMap();
   private Map<DexType, Set<DexField>> fields = Maps.newIdentityHashMap();
@@ -115,13 +119,13 @@
 
     @Override
     public boolean registerInstanceFieldWrite(DexField field) {
-      addField(field);
+      addField(field, false);
       return false;
     }
 
     @Override
     public boolean registerInstanceFieldRead(DexField field) {
-      addField(field);
+      addField(field, false);
       return false;
     }
 
@@ -133,13 +137,13 @@
 
     @Override
     public boolean registerStaticFieldRead(DexField field) {
-      addField(field);
+      addField(field, true);
       return false;
     }
 
     @Override
     public boolean registerStaticFieldWrite(DexField field) {
-      addField(field);
+      addField(field, true);
       return false;
     }
 
@@ -160,8 +164,15 @@
       return descriptors.contains(type.toDescriptorString());
     }
 
-    private void addField(DexField field) {
+    private void addField(DexField field, boolean isStatic) {
       addType(field.type);
+      DexEncodedField baseField =
+          isStatic
+              ? appInfo.lookupStaticTarget(field.clazz, field)
+              : appInfo.lookupInstanceTarget(field.clazz, field);
+      if (baseField != null && baseField.field.clazz != field.clazz) {
+        field = baseField.field;
+      }
       addType(field.clazz);
       Set<DexField> typeFields = fields.get(field.clazz);
       if (typeFields != null) {
@@ -193,6 +204,14 @@
       for (DexType type : method.method.proto.parameters.values) {
         registerTypeReference(type);
       }
+      for (DexAnnotation annotation : method.annotations.annotations) {
+        if (annotation.annotation.type == appInfo.dexItemFactory.annotationThrows) {
+          DexValueArray dexValues = (DexValueArray) annotation.annotation.elements[0].value;
+          for (DexValue dexValType : dexValues.getValues()) {
+            registerTypeReference(((DexValueType) dexValType).value);
+          }
+        }
+      }
       registerTypeReference(method.method.proto.returnType);
       method.registerCodeReferences(this);
     }
@@ -210,21 +229,28 @@
     }
   }
 
-  public static void main(String[] args) throws Exception {
-    if (args.length != 3) {
+  public static void main(String... args) throws Exception {
+    if (args.length != 3 && args.length != 4) {
       System.out.println(USAGE.replace("\n", System.lineSeparator()));
       return;
     }
+    int argumentIndex = 0;
+    boolean printKeep = false;
+    if (args[0].equals("--keeprules")) {
+      printKeep = true;
+      argumentIndex++;
+    }
     AndroidApp.Builder builder = AndroidApp.builder();
-    Path rtJar = Paths.get(args[0]);
+    Path rtJar = Paths.get(args[argumentIndex++]);
     builder.addLibraryFile(rtJar);
-    Path r8Jar = Paths.get(args[1]);
+    Path r8Jar = Paths.get(args[argumentIndex++]);
     builder.addLibraryFile(r8Jar);
-    Path sampleJar = Paths.get(args[2]);
+    Path sampleJar = Paths.get(args[argumentIndex++]);
     builder.addProgramFile(sampleJar);
     Set<String> descriptors = new HashSet<>(getDescriptors(r8Jar));
     descriptors.removeAll(getDescriptors(sampleJar));
-    PrintUses printUses = new PrintUses(descriptors, builder.build(), System.out);
+    Printer printer = printKeep ? new KeepPrinter() : new DefaultPrinter();
+    PrintUses printUses = new PrintUses(descriptors, builder.build(), printer);
     printUses.analyze();
     printUses.print();
     if (printUses.errors > 0) {
@@ -237,10 +263,10 @@
     return new ArchiveClassFileProvider(path).getClassDescriptors();
   }
 
-  private PrintUses(Set<String> descriptors, AndroidApp inputApp, PrintStream out)
+  private PrintUses(Set<String> descriptors, AndroidApp inputApp, Printer printer)
       throws Exception {
     this.descriptors = descriptors;
-    this.out = out;
+    this.printer = printer;
     InternalOptions options = new InternalOptions();
     application =
         new ApplicationReader(inputApp, options, new Timing("PrintUses")).read().toDirect();
@@ -260,69 +286,182 @@
   }
 
   private void print() {
-    List<DexType> types = new ArrayList<>(this.types);
-    types.sort(Comparator.comparing(DexType::toSourceString));
-    for (DexType type : types) {
-      String typeName = type.toSourceString();
-      DexClass dexClass = application.definitionFor(type);
-      if (dexClass == null) {
-        error("Could not find definition for type " + type.toSourceString());
-        continue;
+    errors = printer.print(application, types, methods, fields);
+  }
+
+  private abstract static class Printer {
+
+    void append(String string) {
+      System.out.print(string);
+    }
+
+    void appendLine(String string) {
+      System.out.println(string);
+    }
+
+    void printArguments(DexMethod method) {
+      append("(");
+      for (int i = 0; i < method.getArity(); i++) {
+        if (i != 0) {
+          append(",");
+        }
+        append(method.proto.parameters.values[i].toSourceString());
       }
-      out.println(typeName);
-      List<DexMethod> methods = new ArrayList<>(this.methods.get(type));
-      List<String> methodDefinitions = new ArrayList<>(methods.size());
-      for (DexMethod method : methods) {
-        DexEncodedMethod encodedMethod = dexClass.lookupMethod(method);
-        if (encodedMethod == null) {
-          error("Could not find definition for method " + method.toSourceString());
+      append(")");
+    }
+
+    abstract void printConstructorName(DexEncodedMethod encodedMethod);
+
+    void printError(String message) {
+      appendLine("# Error: " + message);
+    }
+
+    abstract void printField(DexClass dexClass, DexField field);
+
+    abstract void printMethod(DexEncodedMethod encodedMethod, String typeName);
+
+    void printNameAndReturn(DexEncodedMethod encodedMethod) {
+      if (encodedMethod.accessFlags.isConstructor()) {
+        printConstructorName(encodedMethod);
+      } else {
+        DexMethod method = encodedMethod.method;
+        append(method.proto.returnType.toSourceString());
+        append(" ");
+        append(method.name.toSourceString());
+      }
+    }
+
+    abstract void printTypeHeader(DexClass dexClass);
+
+    abstract void printTypeFooter();
+
+    int print(
+        DexApplication application,
+        Set<DexType> types,
+        Map<DexType, Set<DexMethod>> methods,
+        Map<DexType, Set<DexField>> fields) {
+      int errors = 0;
+      List<DexType> sortedTypes = new ArrayList<>(types);
+      sortedTypes.sort(Comparator.comparing(DexType::toSourceString));
+      for (DexType type : sortedTypes) {
+        DexClass dexClass = application.definitionFor(type);
+        if (dexClass == null) {
+          printError("Could not find definition for type " + type.toSourceString());
+          errors++;
           continue;
         }
-        methodDefinitions.add(getMethodSourceString(encodedMethod));
+        printTypeHeader(dexClass);
+        List<DexEncodedMethod> methodDefinitions = new ArrayList<>(methods.size());
+        for (DexMethod method : methods.get(type)) {
+          DexEncodedMethod encodedMethod = dexClass.lookupMethod(method);
+          if (encodedMethod == null) {
+            printError("Could not find definition for method " + method.toSourceString());
+            errors++;
+            continue;
+          }
+          methodDefinitions.add(encodedMethod);
+        }
+        methodDefinitions.sort(Comparator.comparing(x -> x.method.name.toSourceString()));
+        for (DexEncodedMethod encodedMethod : methodDefinitions) {
+          printMethod(encodedMethod, dexClass.type.toSourceString());
+        }
+        List<DexField> sortedFields = new ArrayList<>(fields.get(type));
+        sortedFields.sort(Comparator.comparing(DexField::toSourceString));
+        for (DexField field : sortedFields) {
+          printField(dexClass, field);
+        }
+        printTypeFooter();
       }
-      methodDefinitions.sort(Comparator.naturalOrder());
-      for (String encodedMethod : methodDefinitions) {
-        out.println(typeName + ": " + encodedMethod);
-      }
-      List<DexField> fields = new ArrayList<>(this.fields.get(type));
-      fields.sort(Comparator.comparing(DexField::toSourceString));
-      for (DexField field : fields) {
-        out.println(
-            typeName + ": " + field.type.toSourceString() + " " + field.name.toSourceString());
-      }
+      return errors;
     }
   }
 
-  private void error(String message) {
-    out.println("# Error: " + message);
-    errors += 1;
-  }
+  private static class DefaultPrinter extends Printer {
 
-  private static String getMethodSourceString(DexEncodedMethod encodedMethod) {
-    DexMethod method = encodedMethod.method;
-    StringBuilder builder = new StringBuilder();
-    if (encodedMethod.accessFlags.isConstructor()) {
+    @Override
+    public void printConstructorName(DexEncodedMethod encodedMethod) {
       if (encodedMethod.accessFlags.isStatic()) {
-        builder.append("<clinit>");
+        append("<clinit>");
       } else {
-        String holderName = method.holder.toSourceString();
+        String holderName = encodedMethod.method.holder.toSourceString();
         String constructorName = holderName.substring(holderName.lastIndexOf('.') + 1);
-        builder.append(constructorName);
+        append(constructorName);
       }
-    } else {
-      builder
-          .append(method.proto.returnType.toSourceString())
-          .append(" ")
-          .append(method.name.toSourceString());
     }
-    builder.append("(");
-    for (int i = 0; i < method.getArity(); i++) {
-      if (i != 0) {
-        builder.append(",");
+
+    @Override
+    void printMethod(DexEncodedMethod encodedMethod, String typeName) {
+      append(typeName + ": ");
+      printNameAndReturn(encodedMethod);
+      printArguments(encodedMethod.method);
+      appendLine("");
+    }
+
+    @Override
+    void printTypeHeader(DexClass dexClass) {
+      appendLine(dexClass.type.toSourceString());
+    }
+
+    @Override
+    void printTypeFooter() {}
+
+    @Override
+    void printField(DexClass dexClass, DexField field) {
+      appendLine(
+          dexClass.type.toSourceString()
+              + ": "
+              + field.type.toSourceString()
+              + " "
+              + field.name.toString());
+    }
+  }
+
+  private static class KeepPrinter extends Printer {
+
+    @Override
+    public void printTypeHeader(DexClass dexClass) {
+      if (dexClass.isInterface()) {
+        append("-keep interface " + dexClass.type.toSourceString() + " {\n");
+      } else if (dexClass.accessFlags.isEnum()) {
+        append("-keep enum " + dexClass.type.toSourceString() + " {\n");
+      } else {
+        append("-keep class " + dexClass.type.toSourceString() + " {\n");
       }
-      builder.append(method.proto.parameters.values[i].toSourceString());
     }
-    builder.append(")");
-    return builder.toString();
+
+    @Override
+    public void printConstructorName(DexEncodedMethod encodedMethod) {
+      append("<init>");
+    }
+
+    @Override
+    public void printField(DexClass dexClass, DexField field) {
+      append("  " + field.type.toSourceString() + " " + field.name.toString() + ";\n");
+    }
+
+    @Override
+    public void printMethod(DexEncodedMethod encodedMethod, String typeName) {
+      // Static initializers do not require keep rules - it is kept by keeping the class.
+      if (encodedMethod.accessFlags.isConstructor() && encodedMethod.accessFlags.isStatic()) {
+        return;
+      }
+      append("  ");
+      if (encodedMethod.isPublicMethod()) {
+        append("public ");
+      } else if (encodedMethod.isPrivateMethod()) {
+        append("private ");
+      }
+      if (encodedMethod.isStatic()) {
+        append("static ");
+      }
+      printNameAndReturn(encodedMethod);
+      printArguments(encodedMethod.method);
+      appendLine(";");
+    }
+
+    @Override
+    public void printTypeFooter() {
+      appendLine("}");
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ae166d3..c1664a6 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
@@ -19,7 +20,6 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.graphinfo.GraphConsumer;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.optimize.EnumOrdinalMapCollector;
 import com.android.tools.r8.ir.optimize.MethodPoolCollection;
@@ -250,14 +250,15 @@
       inputApp.closeInternalArchiveProviders();
 
       AppView<AppInfoWithSubtyping> appView =
-          new AppView<>(new AppInfoWithSubtyping(application), GraphLense.getIdentityLense());
+          new AppView<>(
+              new AppInfoWithSubtyping(application), GraphLense.getIdentityLense(), options);
       RootSet rootSet;
       String proguardSeedsData = null;
       timing.begin("Strip unused code");
       try {
         Set<DexType> missingClasses = appView.appInfo().getMissingClasses();
         missingClasses = filterMissingClasses(
-            missingClasses, options.proguardConfiguration.getDontWarnPatterns());
+            missingClasses, options.getProguardConfiguration().getDontWarnPatterns());
         if (!missingClasses.isEmpty()) {
           missingClasses.forEach(
               clazz -> {
@@ -279,18 +280,21 @@
 
         rootSet =
             new RootSetBuilder(
-                    appView, application, options.proguardConfiguration.getRules(), options)
+                    appView, application, options.getProguardConfiguration().getRules(), options)
                 .run(executorService);
 
         Enqueuer enqueuer = new Enqueuer(appView, options, null, compatibility);
         appView.setAppInfo(
             enqueuer.traceApplication(
                 rootSet,
-                options.proguardConfiguration.getDontWarnPatterns(),
+                options.getProguardConfiguration().getDontWarnPatterns(),
                 executorService,
                 timing));
+        assert rootSet.verifyKeptFieldsAreAccessedAndLive(appView.appInfo().withLiveness());
+        assert rootSet.verifyKeptMethodsAreTargetedAndLive(appView.appInfo().withLiveness());
+        assert rootSet.verifyKeptTypesAreLive(appView.appInfo().withLiveness());
 
-        if (options.proguardConfiguration.isPrintSeeds()) {
+        if (options.getProguardConfiguration().isPrintSeeds()) {
           ByteArrayOutputStream bytes = new ByteArrayOutputStream();
           PrintStream out = new PrintStream(bytes);
           RootSetBuilder.writeSeeds(appView.appInfo().withLiveness(), out, type -> true);
@@ -301,16 +305,17 @@
           TreePruner pruner =
               new TreePruner(application, appView.appInfo().withLiveness(), options);
           application = pruner.run();
+
           // Recompute the subtyping information.
           appView.setAppInfo(
               appView
                   .appInfo()
                   .withLiveness()
                   .prunedCopyFrom(application, pruner.getRemovedClasses()));
-          new AbstractMethodRemover(appView.appInfo()).run();
+          new AbstractMethodRemover(appView.appInfo().withLiveness()).run();
         }
 
-        new AnnotationRemover(appView.appInfo().withLiveness(), options)
+        new AnnotationRemover(appView.appInfo().withLiveness(), appView.graphLense(), options)
             .ensureValid(compatibility)
             .run();
 
@@ -334,7 +339,7 @@
         timing.end();
       }
 
-      if (options.proguardConfiguration.isAccessModificationAllowed()) {
+      if (options.getProguardConfiguration().isAccessModificationAllowed()) {
         appView.setGraphLense(
             ClassAndMemberPublicizer.run(executorService, timing, application, appView, rootSet));
         // We can now remove visibility bridges. Note that we do not need to update the
@@ -343,12 +348,32 @@
         application = new VisibilityBridgeRemover(appView.appInfo(), application).run();
       }
 
+      // Build conservative main dex content before first round of tree shaking. This is used
+      // by certain optimizations to avoid introducing additional class references into main dex
+      // classes, as that can cause the final number of main dex methods to grow.
+      RootSet mainDexRootSet = null;
+      MainDexClasses mainDexClasses = MainDexClasses.NONE;
+      if (!options.mainDexKeepRules.isEmpty()) {
+        // Find classes which may have code executed before secondary dex files installation.
+        mainDexRootSet =
+            new RootSetBuilder(appView, application, options.mainDexKeepRules, options)
+                .run(executorService);
+        Enqueuer enqueuer = new Enqueuer(appView, options, null, true);
+        AppInfoWithLiveness mainDexAppInfo =
+            enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
+        // LiveTypes is the tracing result.
+        Set<DexType> mainDexBaseClasses = new HashSet<>(mainDexAppInfo.liveTypes);
+        // Calculate the automatic main dex list according to legacy multidex constraints.
+        mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
+      }
+
       if (appView.appInfo().hasLiveness()) {
         AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
 
-        if (options.proguardConfiguration.hasApplyMappingFile()) {
+        if (options.getProguardConfiguration().hasApplyMappingFile()) {
           SeedMapper seedMapper =
-              SeedMapper.seedMapperFromFile(options.proguardConfiguration.getApplyMappingFile());
+              SeedMapper.seedMapperFromFile(
+                  options.getProguardConfiguration().getApplyMappingFile());
           timing.begin("apply-mapping");
           appView.setGraphLense(
               new ProguardMapApplier(appView.withLiveness(), seedMapper).run(timing));
@@ -361,11 +386,9 @@
           timing.end();
         }
         appView.setGraphLense(new MemberRebindingAnalysis(appViewWithLiveness, options).run());
-        // TODO(117854943): Pass information on main dex classes to class merging.
-        if (options.enableHorizontalClassMerging
-            && options.mainDexKeepRules.isEmpty()
-            && application.mainDexList.isEmpty()) {
-          StaticClassMerger staticClassMerger = new StaticClassMerger(appViewWithLiveness, options);
+        if (options.enableHorizontalClassMerging) {
+          StaticClassMerger staticClassMerger =
+              new StaticClassMerger(appViewWithLiveness, options, mainDexClasses);
           appView.setGraphLense(staticClassMerger.run());
           appViewWithLiveness.setAppInfo(
               appViewWithLiveness
@@ -376,7 +399,12 @@
           timing.begin("ClassMerger");
           VerticalClassMerger verticalClassMerger =
               new VerticalClassMerger(
-                  application, appViewWithLiveness, executorService, options, timing);
+                  application,
+                  appViewWithLiveness,
+                  executorService,
+                  options,
+                  timing,
+                  mainDexClasses);
           appView.setGraphLense(verticalClassMerger.run());
           appView.setVerticallyMergedClasses(verticalClassMerger.getMergedClasses());
           timing.end();
@@ -425,7 +453,8 @@
       Set<DexCallSite> desugaredCallSites;
       CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
       try {
-        IRConverter converter = new IRConverter(appView, options, timing, printer, rootSet);
+        IRConverter converter =
+            new IRConverter(appView, options, timing, printer, mainDexClasses, rootSet);
         application = converter.optimize(application, executorService);
         desugaredCallSites = converter.getDesugaredCallSites();
       } finally {
@@ -449,14 +478,10 @@
       new SourceFileRewriter(appView.appInfo(), options).run();
       timing.end();
 
-      MainDexClasses mainDexClasses = MainDexClasses.NONE;
       if (!options.mainDexKeepRules.isEmpty()) {
         appView.setAppInfo(new AppInfoWithSubtyping(application));
-        // Lets find classes which may have code executed before secondary dex files installation.
-        RootSet mainDexRootSet =
-            new RootSetBuilder(appView, application, options.mainDexKeepRules, options)
-                .run(executorService);
-
+        // No need to build a new main dex root set
+        assert mainDexRootSet != null;
         GraphConsumer mainDexKeptGraphConsumer = options.mainDexKeptGraphConsumer;
         WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = null;
         if (!mainDexRootSet.reasonAsked.isEmpty()) {
@@ -465,9 +490,9 @@
         }
 
         Enqueuer enqueuer = new Enqueuer(appView, options, mainDexKeptGraphConsumer, true);
+        // Find classes which may have code executed before secondary dex files installation.
         AppInfoWithLiveness mainDexAppInfo =
             enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
-
         // LiveTypes is the tracing result.
         Set<DexType> mainDexBaseClasses = new HashSet<>(mainDexAppInfo.liveTypes);
         // Calculate the automatic main dex list according to legacy multidex constraints.
@@ -478,7 +503,6 @@
               .run();
         }
         if (whyAreYouKeepingConsumer != null) {
-          // TODO(b/120959039): Sort the set so the order is always the same print order!
           for (DexDefinition dexDefinition : mainDexRootSet.reasonAsked) {
             whyAreYouKeepingConsumer.printWhyAreYouKeeping(
                 enqueuer.getGraphNode(dexDefinition), System.out);
@@ -506,7 +530,7 @@
           appView.setAppInfo(
               enqueuer.traceApplication(
                   rootSet,
-                  options.proguardConfiguration.getDontWarnPatterns(),
+                  options.getProguardConfiguration().getDontWarnPatterns(),
                   executorService,
                   timing));
 
@@ -518,16 +542,17 @@
                 appViewWithLiveness
                     .appInfo()
                     .prunedCopyFrom(application, pruner.getRemovedClasses()));
+
             // Print reasons on the application after pruning, so that we reflect the actual result.
             if (whyAreYouKeepingConsumer != null) {
-              // TODO(b/120959039): Sort the set so the order is always the same print order!
               for (DexDefinition dexDefinition : rootSet.reasonAsked) {
                 whyAreYouKeepingConsumer.printWhyAreYouKeeping(
                     enqueuer.getGraphNode(dexDefinition), System.out);
               }
             }
             // Remove annotations that refer to types that no longer exist.
-            new AnnotationRemover(appView.appInfo().withLiveness(), options).run();
+            new AnnotationRemover(appView.appInfo().withLiveness(), appView.graphLense(), options)
+                .run();
             if (!mainDexClasses.isEmpty()) {
               // Remove types that no longer exists from the computed main dex list.
               mainDexClasses = mainDexClasses.prunedCopy(appView.appInfo().withLiveness());
@@ -577,11 +602,13 @@
       // If a method filter is present don't produce output since the application is likely partial.
       if (options.hasMethodsFilter()) {
         System.out.println("Finished compilation with method filter: ");
-        options.methodsFilter.forEach((m) -> System.out.println("  - " + m));
+        options.methodsFilter.forEach(m -> System.out.println("  - " + m));
         return;
       }
 
+      // Validity checks.
       assert application.classes().stream().allMatch(DexClass::isValid);
+      assert rootSet.verifyKeptItemsAreKept(application, appView.appInfo(), options);
 
       // Generate the resulting application resources.
       writeApplication(
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 9f25fbc..39630f3 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -5,8 +5,8 @@
 
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graphinfo.GraphConsumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.origin.StandardOutOrigin;
@@ -663,9 +663,9 @@
     assert internal.enableHorizontalClassMerging || !proguardConfiguration.isOptimizing();
     assert internal.enableVerticalClassMerging || !proguardConfiguration.isOptimizing();
     if (internal.debug) {
-      internal.proguardConfiguration.getKeepAttributes().lineNumberTable = true;
-      internal.proguardConfiguration.getKeepAttributes().localVariableTable = true;
-      internal.proguardConfiguration.getKeepAttributes().localVariableTypeTable = true;
+      internal.getProguardConfiguration().getKeepAttributes().lineNumberTable = true;
+      internal.getProguardConfiguration().getKeepAttributes().localVariableTable = true;
+      internal.getProguardConfiguration().getKeepAttributes().localVariableTypeTable = true;
       // TODO(zerny): Should we support inlining in debug mode? b/62937285
       internal.enableInlining = false;
       internal.enableClassInlining = false;
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 4db0374..4288e55 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.4.17-dev";
+  public static final String LABEL = "1.4.19-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 06d9af4..24e82a7 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -306,7 +306,7 @@
     if (options.configurationConsumer != null) {
       ExceptionUtils.withConsumeResourceHandler(
           options.reporter, options.configurationConsumer,
-          options.proguardConfiguration.getParsedConfiguration());
+          options.getProguardConfiguration().getParsedConfiguration());
     }
     if (options.usageInformationConsumer != null && deadCode != null) {
       ExceptionUtils.withConsumeResourceHandler(
diff --git a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
index e680f70..ae59576 100644
--- a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
+++ b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
@@ -46,7 +46,7 @@
   public DataEntryResource adaptIfNeeded(DataEntryResource file) {
     // Adapt name, if needed.
     ProguardPathFilter adaptResourceFileNamesFilter =
-        options.proguardConfiguration.getAdaptResourceFilenames();
+        options.getProguardConfiguration().getAdaptResourceFilenames();
     String name =
         adaptResourceFileNamesFilter.isEnabled()
                 && !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
@@ -56,7 +56,7 @@
     assert name != null;
     // Adapt contents, if needed.
     ProguardPathFilter adaptResourceFileContentsFilter =
-        options.proguardConfiguration.getAdaptResourceFileContents();
+        options.getProguardConfiguration().getAdaptResourceFileContents();
     byte[] contents =
         adaptResourceFileContentsFilter.isEnabled()
                 && !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
@@ -80,7 +80,8 @@
 
   public DataDirectoryResource adaptIfNeeded(DataDirectoryResource directory) {
     // First check if this directory should even be in the output.
-    ProguardPathFilter keepDirectoriesFilter = options.proguardConfiguration.getKeepDirectories();
+    ProguardPathFilter keepDirectoriesFilter =
+        options.getProguardConfiguration().getKeepDirectories();
     if (!keepDirectoriesFilter.matches(directory.getName())) {
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/AnnotationGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/AnnotationGraphNode.java
new file mode 100644
index 0000000..5242510
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/AnnotationGraphNode.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.graphinfo;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public final class AnnotationGraphNode extends GraphNode {
+
+  private final GraphNode annotatedNode;
+
+  public AnnotationGraphNode(GraphNode annotatedNode) {
+    super(annotatedNode.isLibraryNode());
+    this.annotatedNode = annotatedNode;
+  }
+
+  public GraphNode getAnnotatedNode() {
+    return annotatedNode;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return this == o
+        || (o instanceof AnnotationGraphNode
+            && ((AnnotationGraphNode) o).annotatedNode.equals(annotatedNode));
+  }
+
+  @Override
+  public int hashCode() {
+    return 7 * annotatedNode.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return "annotated " + annotatedNode.toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/ClassGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/ClassGraphNode.java
new file mode 100644
index 0000000..edbffbe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/ClassGraphNode.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.graphinfo;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.ClassReference;
+
+@Keep
+public final class ClassGraphNode extends GraphNode {
+
+  private final ClassReference reference;
+
+  public ClassGraphNode(boolean isLibraryNode, ClassReference reference) {
+    super(isLibraryNode);
+    assert reference != null;
+    this.reference = reference;
+  }
+
+  public ClassReference getReference() {
+    return reference;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return this == o
+        || (o instanceof ClassGraphNode && ((ClassGraphNode) o).reference == reference);
+  }
+
+  @Override
+  public int hashCode() {
+    return reference.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return reference.getDescriptor();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/FieldGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/FieldGraphNode.java
new file mode 100644
index 0000000..b091613
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/FieldGraphNode.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.graphinfo;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.FieldReference;
+
+@Keep
+public final class FieldGraphNode extends GraphNode {
+
+  private final FieldReference reference;
+
+  public FieldGraphNode(boolean isLibraryNode, FieldReference reference) {
+    super(isLibraryNode);
+    assert reference != null;
+    this.reference = reference;
+  }
+
+  public FieldReference getReference() {
+    return reference;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return this == o
+        || (o instanceof FieldGraphNode && ((FieldGraphNode) o).reference == reference);
+  }
+
+  @Override
+  public int hashCode() {
+    return reference.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return reference.toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graphinfo/GraphConsumer.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphConsumer.java
similarity index 91%
rename from src/main/java/com/android/tools/r8/graphinfo/GraphConsumer.java
rename to src/main/java/com/android/tools/r8/experimental/graphinfo/GraphConsumer.java
index 8c124e5..3c7bda1 100644
--- a/src/main/java/com/android/tools/r8/graphinfo/GraphConsumer.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphConsumer.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graphinfo;
+package com.android.tools.r8.experimental.graphinfo;
 
 import com.android.tools.r8.KeepForSubclassing;
 
diff --git a/src/main/java/com/android/tools/r8/graphinfo/GraphEdgeInfo.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
similarity index 95%
rename from src/main/java/com/android/tools/r8/graphinfo/GraphEdgeInfo.java
rename to src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
index c0b3f3b..0c838ac 100644
--- a/src/main/java/com/android/tools/r8/graphinfo/GraphEdgeInfo.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graphinfo;
+package com.android.tools.r8.experimental.graphinfo;
 
 public class GraphEdgeInfo {
 
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphNode.java
new file mode 100644
index 0000000..b99485a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphNode.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.graphinfo;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public abstract class GraphNode {
+
+  private final boolean isLibraryNode;
+
+  public GraphNode(boolean isLibraryNode) {
+    this.isLibraryNode = isLibraryNode;
+  }
+
+  public boolean isLibraryNode() {
+    return isLibraryNode;
+  }
+
+  @Override
+  public abstract boolean equals(Object o);
+
+  @Override
+  public abstract int hashCode();
+
+  @Override
+  public abstract String toString();
+}
diff --git a/src/main/java/com/android/tools/r8/graphinfo/KeepRuleGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/KeepRuleGraphNode.java
similarity index 94%
rename from src/main/java/com/android/tools/r8/graphinfo/KeepRuleGraphNode.java
rename to src/main/java/com/android/tools/r8/experimental/graphinfo/KeepRuleGraphNode.java
index 2f4d5e1..7eb55a8 100644
--- a/src/main/java/com/android/tools/r8/graphinfo/KeepRuleGraphNode.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/KeepRuleGraphNode.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graphinfo;
+package com.android.tools.r8.experimental.graphinfo;
 
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.origin.Origin;
@@ -16,6 +16,7 @@
   private final ProguardKeepRule rule;
 
   public KeepRuleGraphNode(ProguardKeepRule rule) {
+    super(false);
     assert rule != null;
     this.rule = rule;
   }
@@ -49,7 +50,7 @@
    * {@code <keep-rule-file>:<keep-rule-start-line>:<keep-rule-start-column>}.
    */
   @Override
-  public String identity() {
+  public String toString() {
     return (getOrigin() == Origin.unknown() ? getContent() : getOrigin())
         + ":"
         + shortPositionInfo(getPosition());
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/MethodGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/MethodGraphNode.java
new file mode 100644
index 0000000..94fe8e7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/MethodGraphNode.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.experimental.graphinfo;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.MethodReference;
+
+@Keep
+public final class MethodGraphNode extends GraphNode {
+
+  private final MethodReference reference;
+
+  public MethodGraphNode(boolean isLibraryNode, MethodReference reference) {
+    super(isLibraryNode);
+    assert reference != null;
+    this.reference = reference;
+  }
+
+  public MethodReference getReference() {
+    return reference;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return this == o
+        || (o instanceof MethodGraphNode && ((MethodGraphNode) o).reference == reference);
+  }
+
+  @Override
+  public int hashCode() {
+    return reference.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    return reference.toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 2354918..f9a9dde 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -6,18 +6,21 @@
 
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.VerticalClassMerger.VerticallyMergedClasses;
+import com.android.tools.r8.utils.InternalOptions;
 
 public class AppView<T extends AppInfo> {
 
   private T appInfo;
   private final DexItemFactory dexItemFactory;
   private GraphLense graphLense;
+  private final InternalOptions options;
   private VerticallyMergedClasses verticallyMergedClasses;
 
-  public AppView(T appInfo, GraphLense graphLense) {
+  public AppView(T appInfo, GraphLense graphLense, InternalOptions options) {
     this.appInfo = appInfo;
     this.dexItemFactory = appInfo != null ? appInfo.dexItemFactory : null;
     this.graphLense = graphLense;
+    this.options = options;
   }
 
   public T appInfo() {
@@ -46,6 +49,10 @@
     this.graphLense = graphLense;
   }
 
+  public InternalOptions options() {
+    return options;
+  }
+
   // Get the result of vertical class merging. Returns null if vertical class merging has not been
   // run.
   public VerticallyMergedClasses verticallyMergedClasses() {
@@ -63,7 +70,7 @@
   private class AppViewWithLiveness extends AppView<AppInfoWithLiveness> {
 
     private AppViewWithLiveness() {
-      super(null, null);
+      super(null, null, null);
     }
 
     @Override
@@ -94,6 +101,11 @@
     }
 
     @Override
+    public InternalOptions options() {
+      return AppView.this.options();
+    }
+
+    @Override
     public AppView<AppInfoWithLiveness> withLiveness() {
       return this;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 6258e98..755402d 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -157,7 +157,7 @@
       if (instruction instanceof CfFrame
           && (classFileVersion <= 49
               || (classFileVersion == 50
-                  && !options.proguardConfiguration.getKeepAttributes().stackMapTable))) {
+                  && !options.getProguardConfiguration().getKeepAttributes().stackMapTable))) {
         continue;
       }
       instruction.write(visitor, namingLens);
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index eda695d..1168c45 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -19,6 +19,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.TreeSet;
+import java.util.function.Function;
 
 public class DexAnnotation extends DexItem {
   public static final int VISIBILITY_BUILD = 0x00;
@@ -373,4 +374,15 @@
       DexItemFactory factory) {
     return annotation.annotation.type == factory.annotationSynthesizedClassMap;
   }
+
+  public DexAnnotation rewrite(Function<DexEncodedAnnotation, DexEncodedAnnotation> rewriter) {
+    DexEncodedAnnotation rewritten = rewriter.apply(annotation);
+    if (rewritten == annotation) {
+      return this;
+    }
+    if (rewritten == null) {
+      return null;
+    }
+    return new DexAnnotation(visibility, rewritten);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
index d093a20..44b09b0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
@@ -5,8 +5,9 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
-import java.util.ArrayList;
+import com.android.tools.r8.utils.ArrayUtils;
 import java.util.Arrays;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 public class DexAnnotationSet extends CachedHashValueDexItem {
@@ -119,28 +120,20 @@
   }
 
   public DexAnnotationSet keepIf(Predicate<DexAnnotation> filter) {
-    ArrayList<DexAnnotation> filtered = null;
-    for (int i = 0; i < annotations.length; i++) {
-      DexAnnotation annotation = annotations[i];
-      if (filter.test(annotation)) {
-        if (filtered != null) {
-          filtered.add(annotation);
-        }
-      } else {
-        if (filtered == null) {
-          filtered = new ArrayList<>(annotations.length);
-          for (int j = 0; j < i; j++) {
-            filtered.add(annotations[j]);
-          }
-        }
-      }
-    }
-    if (filtered == null) {
+    return rewrite(anno -> filter.test(anno) ? anno : null);
+  }
+
+  public DexAnnotationSet rewrite(Function<DexAnnotation, DexAnnotation> rewriter) {
+    if (isEmpty()) {
       return this;
-    } else if (filtered.isEmpty()) {
-      return DexAnnotationSet.empty();
-    } else {
-      return new DexAnnotationSet(filtered.toArray(new DexAnnotation[filtered.size()]));
     }
+    DexAnnotation[] rewritten = ArrayUtils.map(DexAnnotation[].class, annotations, rewriter);
+    if (rewritten == annotations) {
+      return this;
+    }
+    if (rewritten.length == 0) {
+      return DexAnnotationSet.empty();
+    }
+    return new DexAnnotationSet(rewritten);
   }
 }
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 5a4b204..c76865e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -321,6 +321,10 @@
     return accessFlags.isInterface();
   }
 
+  public boolean isEnum() {
+    return accessFlags.isEnum();
+  }
+
   public abstract void addDependencies(MixedSectionCollection collector);
 
   @Override
@@ -478,6 +482,12 @@
     enclosingMethod = null;
   }
 
+  public void removeEnclosingMethod(Predicate<EnclosingMethodAttribute> predicate) {
+    if (enclosingMethod != null && predicate.test(enclosingMethod)) {
+      enclosingMethod = null;
+    }
+  }
+
   public void clearInnerClasses() {
     innerClasses.clear();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
index c47e72b..882ffb3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
@@ -5,7 +5,9 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.utils.ArrayUtils;
 import java.util.Arrays;
+import java.util.function.Function;
 
 public class DexEncodedAnnotation extends DexItem {
 
@@ -72,4 +74,19 @@
     int hashCode = hashCode();
     return hashCode == UNSORTED ? 1 : hashCode;
   }
+
+  public DexEncodedAnnotation rewrite(
+      Function<DexType, DexType> typeRewriter,
+      Function<DexAnnotationElement, DexAnnotationElement> elementRewriter) {
+    DexType rewrittenType = typeRewriter.apply(type);
+    DexAnnotationElement[] rewrittenElements =
+        ArrayUtils.map(DexAnnotationElement[].class, elements, elementRewriter);
+    if (rewrittenType == type && rewrittenElements == elements) {
+      return this;
+    }
+    if (rewrittenElements.length == 0) {
+      return null;
+    }
+    return new DexEncodedAnnotation(rewrittenType, rewrittenElements);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index e4f1c8c..012c4a4 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -7,6 +7,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.graph.LazyLoadedDexApplication.AllClasses;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.ProgramClassCollection;
 import com.android.tools.r8.utils.Timing;
@@ -21,9 +22,11 @@
 
 public class DirectMappedDexApplication extends DexApplication {
 
+  private final AllClasses allClasses;
   private final ImmutableMap<DexType, DexLibraryClass> libraryClasses;
 
   private DirectMappedDexApplication(ClassNameMapper proguardMap,
+      AllClasses allClasses,
       ProgramClassCollection programClasses,
       ImmutableList<ProgramResourceProvider> programResourceProviders,
       ImmutableMap<DexType, DexLibraryClass> libraryClasses,
@@ -32,6 +35,7 @@
       Timing timing) {
     super(proguardMap, programClasses, programResourceProviders, mainDexList, deadCode,
         dexItemFactory, highestSortingString, timing);
+    this.allClasses = allClasses;
     this.libraryClasses = libraryClasses;
   }
 
@@ -92,17 +96,21 @@
 
   public static class Builder extends DexApplication.Builder<Builder> {
 
+    private final AllClasses allClasses;
     private final List<DexLibraryClass> libraryClasses = new ArrayList<>();
 
     Builder(LazyLoadedDexApplication application) {
       super(application);
       // As a side-effect, this will force-load all classes.
-      Map<DexType, DexClass> allClasses = application.getFullClassMap();
+      this.allClasses = application.loadAllClasses();
+      Map<DexType, DexClass> allClasses = this.allClasses.getClasses();
+      // TODO(120884788): This filter will only add library classes which are not program classes.
       Iterables.filter(allClasses.values(), DexLibraryClass.class).forEach(libraryClasses::add);
     }
 
     private Builder(DirectMappedDexApplication application) {
       super(application);
+      this.allClasses = application.allClasses;
       this.libraryClasses.addAll(application.libraryClasses.values());
     }
 
@@ -116,6 +124,7 @@
       // Rebuild the map. This will fail if keys are not unique.
       return new DirectMappedDexApplication(
           proguardMap,
+          allClasses,
           ProgramClassCollection.create(
               programClasses, ProgramClassCollection::resolveClassConflictImpl),
           ImmutableList.copyOf(programResourceProviders),
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 1c74f79..247a5fe 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -424,14 +424,30 @@
     return this == getIdentityLense();
   }
 
-  public <T extends DexDefinition> boolean assertDefinitionNotModified(Iterable<T> items) {
-    for (DexDefinition item : items) {
-      DexReference dexReference = item.toReference();
-      DexReference lookupedReference = lookupReference(dexReference);
+  public <T extends DexDefinition> boolean assertDefinitionsNotModified(Iterable<T> definitions) {
+    for (DexDefinition definition : definitions) {
+      DexReference reference = definition.toReference();
       // We allow changes to bridge methods as these get retargeted even if they are kept.
       boolean isBridge =
-          item.isDexEncodedMethod() && item.asDexEncodedMethod().accessFlags.isBridge();
-      assert isBridge || dexReference == lookupedReference;
+          definition.isDexEncodedMethod() && definition.asDexEncodedMethod().accessFlags.isBridge();
+      assert isBridge || lookupReference(reference) == reference;
+    }
+    return true;
+  }
+
+  public <T extends DexReference> boolean assertReferencesNotModified(Iterable<T> references) {
+    for (DexReference reference : references) {
+      if (reference.isDexField()) {
+        DexField field = reference.asDexField();
+        assert getRenamedFieldSignature(field) == field;
+      } else if (reference.isDexMethod()) {
+        DexMethod method = reference.asDexMethod();
+        assert getRenamedMethodSignature(method) == method;
+      } else {
+        assert reference.isDexType();
+        DexType type = reference.asDexType();
+        assert lookupType(type) == type;
+      }
     }
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index a25bae0..55ece50 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -100,9 +100,12 @@
 
     // If the source-file and source-debug-extension attributes are not kept we can skip all debug
     // related attributes when parsing the class structure.
-    ProguardKeepAttributes keep = application.options.proguardConfiguration.getKeepAttributes();
-    if (!keep.sourceFile && !keep.sourceDebugExtension) {
-      parsingOptions |= SKIP_DEBUG;
+    if (application.options.getProguardConfiguration() != null) {
+      ProguardKeepAttributes keep =
+          application.options.getProguardConfiguration().getKeepAttributes();
+      if (!keep.sourceFile && !keep.sourceDebugExtension) {
+        parsingOptions |= SKIP_DEBUG;
+      }
     }
     reader.accept(
         new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer),
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 2bc029c..04e6516 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OffOrAuto;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.Iterator;
@@ -171,6 +172,20 @@
     }
   }
 
+  private boolean keepLocals(DexEncodedMethod encodedMethod, InternalOptions options) {
+    if (options.testing.noLocalsTableOnInput) {
+      return false;
+    }
+    if (options.debug) {
+      return true;
+    }
+    if (options.getProguardConfiguration() != null
+        && options.getProguardConfiguration().getKeepAttributes().localVariableTable) {
+      return true;
+    }
+    return encodedMethod.getOptimizationInfo().isReachabilitySensitive();
+  }
+
   private IRCode internalBuild(
       DexEncodedMethod context,
       DexEncodedMethod encodedMethod,
@@ -179,9 +194,7 @@
       InternalOptions options,
       ValueNumberGenerator generator,
       Position callerPosition) {
-    if (!(encodedMethod.getOptimizationInfo().isReachabilitySensitive()
-        || (options.debug
-            && options.proguardConfiguration.getKeepAttributes().localVariableTable))) {
+    if (!keepLocals(encodedMethod, options)) {
       node.localVariables.clear();
     }
     JarSourceCode source =
@@ -208,17 +221,29 @@
 
   @Override
   public void registerArgumentReferences(ArgumentUse registry) {
+    triggerDelayedParsingIfNeccessary();
     node.instructions.accept(new JarArgumentUseVisitor(getOwner(), registry));
   }
 
   public ConstraintWithTarget computeInliningConstraint(
       DexEncodedMethod encodedMethod,
-      AppInfoWithLiveness appInfo,
+      AppView<? extends AppInfoWithLiveness> appView,
       GraphLense graphLense,
       DexType invocationContext) {
     InliningConstraintVisitor visitor =
         new InliningConstraintVisitor(
-            application, appInfo, graphLense, encodedMethod, invocationContext);
+            application, appView.appInfo(), graphLense, encodedMethod, invocationContext);
+
+    if (appView.options().enableDesugaring
+        && appView.options().interfaceMethodDesugaring == OffOrAuto.Auto
+        && !appView.options().canUseDefaultAndStaticInterfaceMethods()) {
+      // TODO(b/120130831): Conservatively need to say "no" at this point if there are invocations
+      // to static interface methods. This should be fixed by making sure that the desugared
+      // versions of default and static interface methods are present in the application during
+      // IR processing.
+      visitor.disallowStaticInterfaceMethodCalls();
+    }
+
     AbstractInsnNode insn = node.instructions.getFirst();
     while (insn != null) {
       insn.accept(visitor);
@@ -272,13 +297,16 @@
     // to get locals information which we need to extend the live ranges of locals for their
     // entire scope.
     int parsingOptions = ClassReader.SKIP_FRAMES;
-    ProguardKeepAttributes keep = application.options.proguardConfiguration.getKeepAttributes();
 
-    if (!keep.localVariableTable
-        && !keep.localVariableTypeTable
-        && !keep.lineNumberTable
-        && !reachabilitySensitive) {
-      parsingOptions |= ClassReader.SKIP_DEBUG;
+    if (application.options.getProguardConfiguration() != null) {
+      ProguardKeepAttributes keep =
+          application.options.getProguardConfiguration().getKeepAttributes();
+      if (!keep.localVariableTable
+          && !keep.localVariableTypeTable
+          && !keep.lineNumberTable
+          && !reachabilitySensitive) {
+        parsingOptions |= ClassReader.SKIP_DEBUG;
+      }
     }
     SecondVisitor classVisitor = new SecondVisitor(createCodeLocator(context), useJsrInliner);
     try {
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index b648e41..6dea825 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -55,31 +55,67 @@
     return clazz;
   }
 
-  private Map<DexType, DexClass> forceLoadAllClasses() {
-    Map<DexType, DexClass> loaded = new IdentityHashMap<>();
+  static class AllClasses {
+    private Map<DexType, DexClass> libraryClasses;
+    private Map<DexType, DexClass> classpathClasses;
+    private Map<DexType, DexClass> programClasses;
+    private Map<DexType, DexClass> classes;
 
-    // Program classes are supposed to be loaded, but force-loading them is no-op.
-    programClasses.forceLoad(type -> true);
-    programClasses.getAllClasses().forEach(clazz -> loaded.put(clazz.type, clazz));
+    AllClasses(
+        LibraryClassCollection libraryClasses,
+        ClasspathClassCollection classpathClasses,
+        ProgramClassCollection programClasses) {
+      load(libraryClasses, classpathClasses, programClasses);
 
-    if (classpathClasses != null) {
-      classpathClasses.forceLoad(type -> !loaded.containsKey(type));
-      classpathClasses.getAllClasses().forEach(clazz -> loaded.putIfAbsent(clazz.type, clazz));
+      // Collect loaded classes in the precedence order program classes, class path classes and
+      // library classes.
+      // TODO(b/120884788): Change this.
+      classes = new IdentityHashMap<>();
+      classes.putAll(this.programClasses);
+      if (classpathClasses != null) {
+        classpathClasses.getAllClasses().forEach(clazz -> classes.putIfAbsent(clazz.type, clazz));
+      }
+      if (libraryClasses != null) {
+        libraryClasses.getAllClasses().forEach(clazz -> classes.putIfAbsent(clazz.type, clazz));
+      }
     }
 
-    if (libraryClasses != null) {
-      libraryClasses.forceLoad(type -> !loaded.containsKey(type));
-      libraryClasses.getAllClasses().forEach(clazz -> loaded.putIfAbsent(clazz.type, clazz));
+    public Map<DexType, DexClass> getLibraryClasses() {
+      return libraryClasses;
     }
 
-    return loaded;
+    public Map<DexType, DexClass> getClasspathClasses() {
+      return classpathClasses;
+    }
+
+    public Map<DexType, DexClass> getClasses() {
+      return classes;
+    }
+
+    private void load(
+        LibraryClassCollection libraryClasses,
+        ClasspathClassCollection classpathClasses,
+        ProgramClassCollection programClasses) {
+      if (libraryClasses != null) {
+        libraryClasses.forceLoad(type -> true);
+        this.libraryClasses = libraryClasses.getAllClassesInMap();
+      }
+      if (classpathClasses != null) {
+        classpathClasses.forceLoad(type -> true);
+        this.classpathClasses = classpathClasses.getAllClassesInMap();
+      }
+      assert programClasses != null;
+      // Program classes are supposed to be loaded, but force-loading them is no-op.
+      programClasses.forceLoad(type -> true);
+      this.programClasses = programClasses.getAllClassesInMap();
+    }
   }
 
   /**
    * Force load all classes and return type -> class map containing all the classes.
    */
-  public Map<DexType, DexClass> getFullClassMap() {
-    return forceLoadAllClasses();
+  public AllClasses loadAllClasses() {
+    return new AllClasses(libraryClasses, classpathClasses, programClasses);
   }
 
   public static class Builder extends DexApplication.Builder<Builder> {
diff --git a/src/main/java/com/android/tools/r8/graphinfo/AnnotationGraphNode.java b/src/main/java/com/android/tools/r8/graphinfo/AnnotationGraphNode.java
deleted file mode 100644
index 2c8482f..0000000
--- a/src/main/java/com/android/tools/r8/graphinfo/AnnotationGraphNode.java
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graphinfo;
-
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.graph.DexItem;
-
-@Keep
-public final class AnnotationGraphNode extends GraphNode {
-
-  private final DexItem annotatedItem;
-
-  public AnnotationGraphNode(DexItem annotatedItem) {
-    assert annotatedItem != null;
-    this.annotatedItem = annotatedItem;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    return this == o
-        || (o instanceof AnnotationGraphNode
-            && ((AnnotationGraphNode) o).annotatedItem == annotatedItem);
-  }
-
-  @Override
-  public int hashCode() {
-    return annotatedItem.hashCode();
-  }
-
-  public String getDescriptor() {
-    return annotatedItem.toSourceString();
-  }
-
-  /**
-   * Get a unique identity string determining this annotated-item node.
-   *
-   * <p>This is the descriptor of the concrete node type.
-   */
-  @Override
-  public String identity() {
-    return getDescriptor();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/graphinfo/ClassGraphNode.java b/src/main/java/com/android/tools/r8/graphinfo/ClassGraphNode.java
deleted file mode 100644
index 96bebdb..0000000
--- a/src/main/java/com/android/tools/r8/graphinfo/ClassGraphNode.java
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graphinfo;
-
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.graph.DexType;
-
-@Keep
-public final class ClassGraphNode extends GraphNode {
-
-  private final DexType clazz;
-
-  public ClassGraphNode(DexType clazz) {
-    assert clazz != null;
-    this.clazz = clazz;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    return this == o || (o instanceof ClassGraphNode && ((ClassGraphNode) o).clazz == clazz);
-  }
-
-  @Override
-  public int hashCode() {
-    return clazz.hashCode();
-  }
-
-  public String getDescriptor() {
-    return clazz.toDescriptorString();
-  }
-
-  /**
-   * Get a unique identity string determining this clazz node.
-   *
-   * <p>This is just the class descriptor.
-   */
-  @Override
-  public String identity() {
-    return getDescriptor();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/graphinfo/FieldGraphNode.java b/src/main/java/com/android/tools/r8/graphinfo/FieldGraphNode.java
deleted file mode 100644
index bb3b932..0000000
--- a/src/main/java/com/android/tools/r8/graphinfo/FieldGraphNode.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graphinfo;
-
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.graph.DexField;
-
-@Keep
-public final class FieldGraphNode extends GraphNode {
-
-  private final DexField field;
-
-  public FieldGraphNode(DexField field) {
-    assert field != null;
-    this.field = field;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    return this == o || (o instanceof FieldGraphNode && ((FieldGraphNode) o).field == field);
-  }
-
-  @Override
-  public int hashCode() {
-    return field.hashCode();
-  }
-
-  /**
-   * Get the class descriptor for the field holder as defined by the JVM specification.
-   *
-   * <p>For the field {@code x.y.Z a.b.C.foo}, this would be {@code La/b/C;}.
-   */
-  public String getHolderDescriptor() {
-    return field.clazz.toDescriptorString();
-  }
-
-  /**
-   * Get the field descriptor as defined by the JVM specification.
-   *
-   * <p>For the field {@code x.y.Z a.b.C.foo}, this would be {@code Lx/y/Z;}.
-   */
-  public String getFieldDescriptor() {
-    return field.type.toDescriptorString();
-  }
-
-  /**
-   * Get the (unqualified) field name.
-   *
-   * <p>For the field {@code x.y.Z a.b.C.foo} this would be {@code foo}.
-   */
-  public String getFieldName() {
-    return field.name.toString();
-  }
-
-  /**
-   * Get a unique identity string determining this field node.
-   *
-   * <p>The identity string follows the CF encoding of a field reference: {@code
-   * <holder-descriptor>.<field-name>:<field-descriptor>}, e.g., for {@code x.y.Z a.b.C.foo} this
-   * would be {@code La/b/C;foo:Lx/y/Z;}.
-   */
-  @Override
-  public String identity() {
-    return getHolderDescriptor() + getFieldName() + "\":" + getFieldDescriptor();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/graphinfo/GraphNode.java b/src/main/java/com/android/tools/r8/graphinfo/GraphNode.java
deleted file mode 100644
index 4f6251b..0000000
--- a/src/main/java/com/android/tools/r8/graphinfo/GraphNode.java
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graphinfo;
-
-import com.android.tools.r8.Keep;
-
-@Keep
-public abstract class GraphNode {
-
-  public abstract String identity();
-
-  @Override
-  public abstract boolean equals(Object o);
-
-  @Override
-  public abstract int hashCode();
-
-  @Override
-  public String toString() {
-    return identity();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/graphinfo/MethodGraphNode.java b/src/main/java/com/android/tools/r8/graphinfo/MethodGraphNode.java
deleted file mode 100644
index 2745518..0000000
--- a/src/main/java/com/android/tools/r8/graphinfo/MethodGraphNode.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graphinfo;
-
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.graph.DexMethod;
-
-@Keep
-public final class MethodGraphNode extends GraphNode {
-
-  private final DexMethod method;
-
-  public MethodGraphNode(DexMethod method) {
-    assert method != null;
-    this.method = method;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    return this == o || (o instanceof MethodGraphNode && ((MethodGraphNode) o).method == method);
-  }
-
-  @Override
-  public int hashCode() {
-    return method.hashCode();
-  }
-
-  /**
-   * Get the class descriptor for the method holder as defined by the JVM specification.
-   *
-   * <p>For the method {@code void a.b.C.foo(x.y.Z arg)}, this would be {@code La/b/C;}.
-   */
-  public String getHolderDescriptor() {
-    return method.holder.toDescriptorString();
-  }
-
-  /**
-   * Get the method descriptor as defined by the JVM specification.
-   *
-   * <p>For the method {@code void a.b.C.foo(x.y.Z arg)}, this would be {@code (Lx/y/Z;)V}.
-   */
-  public String getMethodDescriptor() {
-    return method.proto.toDescriptorString();
-  }
-
-  /**
-   * Get the (unqualified) method name.
-   *
-   * <p>For the method {@code void a.b.C.foo(x.y.Z arg)} this would be {@code foo}.
-   */
-  public String getMethodName() {
-    return method.name.toString();
-  }
-
-  /**
-   * Get a unique identity string determining this method node.
-   *
-   * <p>The identity string follows the CF encoding of a method reference:
-   * {@code <holder-descriptor><method-name><method-descriptor>}, e.g., for
-   * {@code void a.b.C.foo(x.y.Z arg)} this will be {@code La/b/C;foo(Lx/y/Z;)V}.
-   */
-  @Override
-  public String identity() {
-    return getHolderDescriptor() + getMethodName() + getMethodDescriptor();
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index c6ab4cd..99833f4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -40,6 +40,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.WeakHashMap;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -48,6 +49,12 @@
  */
 public class BasicBlock {
 
+  public interface BasicBlockChangeListener {
+    void onSuccessorsMayChange(BasicBlock block);
+
+    void onPredecessorsMayChange(BasicBlock block);
+  }
+
   private Int2ReferenceMap<DebugLocalInfo> localsAtEntry;
 
   public boolean consistentBlockInstructions(boolean argumentsAllowed, boolean debug) {
@@ -130,6 +137,8 @@
   private final List<BasicBlock> successors = new ArrayList<>();
   private final List<BasicBlock> predecessors = new ArrayList<>();
 
+  private Set<BasicBlockChangeListener> onControlFlowEdgesMayChangeListeners = null;
+
   // Catch handler information about which successors are catch handlers and what their guards are.
   private CatchHandlers<Integer> catchHandlers = CatchHandlers.EMPTY_INDICES;
 
@@ -159,14 +168,30 @@
   // Map of registers to current SSA value. Used during SSA numbering and cleared once filled.
   private Map<Integer, Value> currentDefinitions = new HashMap<>();
 
+  public void addControlFlowEdgesMayChangeListener(BasicBlockChangeListener listener) {
+    if (onControlFlowEdgesMayChangeListeners == null) {
+      // WeakSet to allow the listeners to be garbage collected.
+      onControlFlowEdgesMayChangeListeners = Collections.newSetFromMap(new WeakHashMap<>());
+    }
+    onControlFlowEdgesMayChangeListeners.add(listener);
+  }
+
   public List<BasicBlock> getSuccessors() {
     return Collections.unmodifiableList(successors);
   }
 
   public List<BasicBlock> getMutableSuccessors() {
+    assert notifySuccessorsMayChangeListeners();
     return successors;
   }
 
+  private boolean notifySuccessorsMayChangeListeners() {
+    if (onControlFlowEdgesMayChangeListeners != null) {
+      onControlFlowEdgesMayChangeListeners.forEach(l -> l.onSuccessorsMayChange(this));
+    }
+    return true;
+  }
+
   public List<BasicBlock> getNormalSuccessors() {
     if (!hasCatchHandlers()) {
       return successors;
@@ -186,9 +211,17 @@
   }
 
   public List<BasicBlock> getMutablePredecessors() {
+    assert notifyPredecessorsMayChangeListeners();
     return predecessors;
   }
 
+  private boolean notifyPredecessorsMayChangeListeners() {
+    if (onControlFlowEdgesMayChangeListeners != null) {
+      onControlFlowEdgesMayChangeListeners.forEach(l -> l.onPredecessorsMayChange(this));
+    }
+    return true;
+  }
+
   public List<BasicBlock> getNormalPredecessors() {
     ImmutableList.Builder<BasicBlock> normals = ImmutableList.builder();
     for (BasicBlock predecessor : predecessors) {
@@ -208,7 +241,7 @@
   public void removePredecessor(BasicBlock block) {
     int index = predecessors.indexOf(block);
     assert index >= 0 : "removePredecessor did not find the predecessor to remove";
-    predecessors.remove(index);
+    getMutablePredecessors().remove(index);
     if (phis != null) {
       for (Phi phi : getPhis()) {
         phi.removeOperand(index);
@@ -249,6 +282,7 @@
       }
       catchHandlers = new CatchHandlers<>(catchHandlers.getGuards(), targets);
     }
+    List<BasicBlock> successors = getMutableSuccessors();
     BasicBlock tmp = successors.get(index1);
     successors.set(index1, successors.get(index2));
     successors.set(index2, tmp);
@@ -335,14 +369,14 @@
       }
 
       // Remove the replaced successor.
-      boolean removed = successors.remove(block);
+      boolean removed = getMutableSuccessors().remove(block);
       assert removed;
     } else {
       // If the new block is not a successor we don't have to rewrite indices or instructions
       // and we can just replace the old successor with the new one.
       for (int i = 0; i < successors.size(); i++) {
         if (successors.get(i) == block) {
-          successors.set(i, newBlock);
+          getMutableSuccessors().set(i, newBlock);
           return;
         }
       }
@@ -366,7 +400,8 @@
   public void replacePredecessor(BasicBlock block, BasicBlock newBlock) {
     for (int i = 0; i < predecessors.size(); i++) {
       if (predecessors.get(i) == block) {
-        predecessors.set(i, newBlock);
+        assert notifyPredecessorsMayChangeListeners();
+        getMutablePredecessors().set(i, newBlock);
         return;
       }
     }
@@ -378,6 +413,7 @@
       return;
     }
     assert ListUtils.verifyListIsOrdered(successorsToRemove);
+    List<BasicBlock> successors = getMutableSuccessors();
     List<BasicBlock> copy = new ArrayList<>(successors);
     successors.clear();
     int current = 0;
@@ -426,6 +462,7 @@
     if (predecessorsToRemove.isEmpty()) {
       return;
     }
+    List<BasicBlock> predecessors = getMutablePredecessors();
     List<BasicBlock> copy = new ArrayList<>(predecessors);
     predecessors.clear();
     int current = 0;
@@ -592,24 +629,16 @@
   public void link(BasicBlock successor) {
     assert !successors.contains(successor);
     assert !successor.predecessors.contains(this);
-    successors.add(successor);
-    successor.predecessors.add(this);
-  }
-
-  private static boolean allPredecessorsDominated(BasicBlock block, DominatorTree dominator) {
-    for (BasicBlock pred : block.predecessors) {
-      if (!dominator.dominatedBy(pred, block)) {
-        return false;
-      }
-    }
-    return true;
+    getMutableSuccessors().add(successor);
+    successor.getMutablePredecessors().add(this);
   }
 
   private static boolean blocksClean(List<BasicBlock> blocks) {
-    blocks.forEach((b) -> {
-      assert b.predecessors.size() == 0;
-      assert b.successors.size() == 0;
-    });
+    blocks.forEach(
+        b -> {
+          assert b.predecessors.size() == 0;
+          assert b.successors.size() == 0;
+        });
     return true;
   }
 
@@ -622,8 +651,8 @@
     assert predecessors.size() == 1;
     assert predecessors.get(0).successors.size() == 1;
     BasicBlock unlinkedBlock = predecessors.get(0);
-    unlinkedBlock.successors.clear();
-    predecessors.clear();
+    unlinkedBlock.getMutableSuccessors().clear();
+    getMutablePredecessors().clear();
     return unlinkedBlock;
   }
 
@@ -631,8 +660,9 @@
   public void unlinkSinglePredecessorSiblingsAllowed() {
     assert predecessors.size() == 1; // There are no critical edges.
     assert predecessors.get(0).successors.contains(this);
-    predecessors.get(0).successors.remove(this);
-    predecessors.clear();
+    List<BasicBlock> predecessors = getMutablePredecessors();
+    predecessors.get(0).getMutableSuccessors().remove(this);
+    getMutablePredecessors().clear();
   }
 
   /**
@@ -645,8 +675,8 @@
     assert successors.size() == 1;
     assert successors.get(0).predecessors.size() == 1;
     BasicBlock unlinkedBlock = successors.get(0);
-    unlinkedBlock.predecessors.clear();
-    successors.clear();
+    unlinkedBlock.getMutablePredecessors().clear();
+    getMutableSuccessors().clear();
     return unlinkedBlock;
   }
 
@@ -659,7 +689,7 @@
   public void unlinkCatchHandler() {
     assert predecessors.size() == 1;
     predecessors.get(0).removeSuccessor(this);
-    predecessors.clear();
+    getMutablePredecessors().clear();
   }
 
   /**
@@ -691,7 +721,7 @@
       catchHandlers = catchHandlers.removeGuard(guard);
       if (getCatchHandlers().getAllTargets().stream()
           .noneMatch(target -> target == successors.get(successorIndex))) {
-        successors.remove(successorIndex);
+        getMutableSuccessors().remove(successorIndex);
       }
       assert consistentCatchHandlers();
     }
@@ -712,9 +742,9 @@
 
   public void detachAllSuccessors() {
     for (BasicBlock successor : successors) {
-      successor.predecessors.remove(this);
+      successor.getMutablePredecessors().remove(this);
     }
-    successors.clear();
+    getMutableSuccessors().clear();
   }
 
   public List<BasicBlock> unlink(BasicBlock successor, DominatorTree dominator) {
@@ -735,11 +765,11 @@
     for (BasicBlock block : successors) {
       block.removePredecessor(this);
     }
-    successors.clear();
+    getMutableSuccessors().clear();
     for (BasicBlock block : predecessors) {
       block.removeSuccessor(this);
     }
-    predecessors.clear();
+    getMutablePredecessors().clear();
     for (Phi phi : getPhis()) {
       affectValuesBuilder.addAll(phi.affectedValues());
       for (Value operand : phi.getOperands()) {
@@ -778,7 +808,7 @@
 
   public void addCatchHandler(BasicBlock rethrowBlock, DexType guard) {
     assert !hasCatchHandlers();
-    successors.add(0, rethrowBlock);
+    getMutableSuccessors().add(0, rethrowBlock);
     rethrowBlock.getMutablePredecessors().add(this);
     catchHandlers = new CatchHandlers<>(ImmutableList.of(guard), ImmutableList.of(0));
   }
@@ -1449,11 +1479,11 @@
     newBlock.setNumber(blockNumber);
 
     // Copy all successors including catch handlers to the new block, and update predecessors.
-    newBlock.successors.addAll(successors);
+    newBlock.getMutableSuccessors().addAll(successors);
     for (BasicBlock successor : newBlock.getSuccessors()) {
       successor.replacePredecessor(this, newBlock);
     }
-    successors.clear();
+    getMutableSuccessors().clear();
     newBlock.catchHandlers = catchHandlers;
     catchHandlers = CatchHandlers.EMPTY_INDICES;
 
@@ -1478,7 +1508,7 @@
   public void moveCatchHandlers(BasicBlock fromBlock) {
     List<BasicBlock> catchSuccessors = appendCatchHandlers(fromBlock);
     for (BasicBlock successor : catchSuccessors) {
-      fromBlock.successors.remove(successor);
+      fromBlock.getMutableSuccessors().remove(successor);
       successor.removePredecessor(fromBlock);
     }
     fromBlock.catchHandlers = CatchHandlers.EMPTY_INDICES;
@@ -1651,6 +1681,7 @@
     }
 
     // Create the new successors list and link things up.
+    List<BasicBlock> successors = getMutableSuccessors();
     List<BasicBlock> formerSuccessors = new ArrayList<>(successors);
     successors.clear();
     List<BasicBlock> sharedCatchSuccessors = new ArrayList<>();
diff --git a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
index 51fd38e..b8192be 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
@@ -6,11 +6,14 @@
 
 import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
 
+import com.android.tools.r8.ir.code.BasicBlock.BasicBlockChangeListener;
 import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
+import java.util.List;
 
-public class DominatorTree {
+public class DominatorTree implements BasicBlockChangeListener {
 
   public enum Assumption {
     NO_UNREACHABLE_BLOCKS,
@@ -23,6 +26,8 @@
 
   private final int unreachableStartIndex;
 
+  private boolean obsolete = false;
+
   public DominatorTree(IRCode code) {
     this(code, Assumption.NO_UNREACHABLE_BLOCKS);
   }
@@ -68,12 +73,17 @@
     }
     numberBlocks();
     build();
+
+    // This is intentionally implemented via an `assert` so that we do not attach listeners to all
+    // basic blocks when running without assertions.
+    assert recordChangesToControlFlowEdges(code.blocks);
   }
 
   /**
    * Get the immediate dominator block for a block.
    */
   public BasicBlock immediateDominator(BasicBlock block) {
+    assert !obsolete;
     return doms[block.getNumber()];
   }
 
@@ -85,6 +95,7 @@
    * @return wether {@code subject} is dominated by {@code dominator}
    */
   public boolean dominatedBy(BasicBlock subject, BasicBlock dominator) {
+    assert !obsolete;
     if (subject == dominator) {
       return true;
     }
@@ -99,6 +110,7 @@
    * @return wether {@code subject} is strictly dominated by {@code dominator}
    */
   public boolean strictlyDominatedBy(BasicBlock subject, BasicBlock dominator) {
+    assert !obsolete;
     if (subject.getNumber() == 0 || subject == normalExitBlock) {
       return false;
     }
@@ -121,6 +133,7 @@
    * @return the closest dominator for the collection of blocks
    */
   public BasicBlock closestDominator(Collection<BasicBlock> blocks) {
+    assert !obsolete;
     if (blocks.size() == 0) {
       return null;
     }
@@ -132,31 +145,17 @@
     return dominator;
   }
 
-  /** Returns an iterator over all blocks dominated by dominator, including dominator itself. */
-  public Iterable<BasicBlock> dominatedBlocks(BasicBlock dominator) {
-    return () ->
-        new Iterator<BasicBlock>() {
-          private int current = dominator.getNumber();
-
-          @Override
-          public boolean hasNext() {
-            boolean found = false;
-            while (current < unreachableStartIndex
-                && !(found = dominatedBy(sorted[current], dominator))) {
-              current++;
-            }
-            return found && current < unreachableStartIndex;
-          }
-
-          @Override
-          public BasicBlock next() {
-            if (!hasNext()) {
-              return null;
-            } else {
-              return sorted[current++];
-            }
-          }
-        };
+  /** Returns the blocks dominated by dominator, including dominator itself. */
+  public List<BasicBlock> dominatedBlocks(BasicBlock dominator) {
+    assert !obsolete;
+    List<BasicBlock> dominatedBlocks = new ArrayList<>();
+    for (int i = dominator.getNumber(); i < unreachableStartIndex; ++i) {
+      BasicBlock block = sorted[i];
+      if (dominatedBy(block, dominator)) {
+        dominatedBlocks.add(block);
+      }
+    }
+    return dominatedBlocks;
   }
 
   /**
@@ -166,6 +165,7 @@
    * iteration starts by returning <code>dominated</code>.
    */
   public Iterable<BasicBlock> dominatorBlocks(BasicBlock dominated) {
+    assert !obsolete;
     return () -> new Iterator<BasicBlock>() {
       private BasicBlock current = dominated;
 
@@ -193,6 +193,7 @@
   }
 
   public Iterable<BasicBlock> normalExitDominatorBlocks() {
+    assert !obsolete;
     return dominatorBlocks(normalExitBlock);
   }
 
@@ -279,4 +280,21 @@
     }
     return builder.toString();
   }
+
+  private boolean recordChangesToControlFlowEdges(List<BasicBlock> blocks) {
+    for (BasicBlock block : blocks) {
+      block.addControlFlowEdgesMayChangeListener(this);
+    }
+    return true;
+  }
+
+  @Override
+  public void onSuccessorsMayChange(BasicBlock block) {
+    obsolete = true;
+  }
+
+  @Override
+  public void onPredecessorsMayChange(BasicBlock block) {
+    obsolete = true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java b/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
index fd9afa8..4e1e0ed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
@@ -37,6 +37,10 @@
     this.to = to;
   }
 
+  public boolean isLongToIntConversion() {
+    return from == NumericType.LONG && to == NumericType.INT;
+  }
+
   @Override
   public void buildDex(DexBuilder builder) {
     com.android.tools.r8.code.Instruction instruction;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 13fb6cb..4b2fabb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -3,8 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
-import com.android.tools.r8.code.Const4;
-import com.android.tools.r8.code.ConstWide16;
 import com.android.tools.r8.code.FillArrayData;
 import com.android.tools.r8.code.FillArrayDataPayload;
 import com.android.tools.r8.code.Format31t;
@@ -35,7 +33,6 @@
 import com.android.tools.r8.code.MoveWideFrom16;
 import com.android.tools.r8.code.Nop;
 import com.android.tools.r8.code.ReturnVoid;
-import com.android.tools.r8.code.ReturnWide;
 import com.android.tools.r8.code.Throw;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
@@ -220,22 +217,14 @@
     if (options.canHaveTracingPastInstructionsStreamBug()
         && dexInstructions.get(dexInstructions.size() - 1) instanceof Throw
         && hasBackwardsBranch) {
-      List<Instruction> instructions = new ArrayList<>();
-      DexType returnType = ir.method.method.proto.returnType;
-      if (returnType.isVoidType()) {
-        instructions.add(new ReturnVoid());
-      } else if (returnType.isDoubleType() || returnType.isLongType()) {
-        instructions.add(new ConstWide16(0, 0));
-        instructions.add(new ReturnWide(0));
-      } else {
-        instructions.add(new Const4(0, 0));
-        instructions.add(new com.android.tools.r8.code.Return(0));
-      }
-      for (Instruction instruction : instructions) {
-        instruction.setOffset(offset);
-        offset += instruction.getSize();
-        dexInstructions.add(instruction);
-      }
+      // Generating a throw with the right type makes some Art constant propagation
+      // implementations crash. Therefore, since this is dead code anyway, we do not
+      // generate a return of the right type. Instead we just generate an unreachable
+      // return-void. See b/121355317.
+      Instruction returnVoid = new ReturnVoid();
+      returnVoid.setOffset(offset);
+      offset += returnVoid.getSize();
+      dexInstructions.add(returnVoid);
     }
 
     // Compute switch payloads.
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 c77be90..0434f66 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
@@ -68,6 +68,7 @@
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.IdentifierNameStringMarker;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -109,6 +110,7 @@
 
   public final AppInfo appInfo;
   public final AppView<? extends AppInfoWithSubtyping> appView;
+  public final Set<DexType> mainDexClasses;
   public final RootSet rootSet;
 
   private final Timing timing;
@@ -157,6 +159,7 @@
       Timing timing,
       CfgPrinter printer,
       AppView<? extends AppInfoWithSubtyping> appView,
+      MainDexClasses mainDexClasses,
       RootSet rootSet) {
     assert appInfo != null;
     assert options != null;
@@ -170,6 +173,7 @@
     this.rootSet = rootSet;
     this.options = options;
     this.printer = printer;
+    this.mainDexClasses = mainDexClasses.getClasses();
     this.codeRewriter = new CodeRewriter(this, libraryMethodsReturningReceiver(), options);
     this.stringConcatRewriter = new StringConcatRewriter(appInfo);
     this.lambdaRewriter = options.enableDesugaring ? new LambdaRewriter(this) : null;
@@ -194,7 +198,7 @@
       assert rootSet != null;
       this.nonNullTracker = new NonNullTracker(
           appInfoWithLiveness, libraryMethodsReturningNonNull(appInfo.dexItemFactory));
-      this.inliner = new Inliner(appViewWithLiveness, this, options);
+      this.inliner = new Inliner(appViewWithLiveness, this, options, mainDexClasses);
       this.outliner = new Outliner(appInfoWithLiveness, options, this);
       this.memberValuePropagation =
           options.enableValuePropagation ? new MemberValuePropagation(appInfoWithLiveness) : null;
@@ -253,14 +257,14 @@
    * Create an IR converter for processing methods with full program optimization disabled.
    */
   public IRConverter(AppInfo appInfo, InternalOptions options) {
-    this(appInfo, options, null, null, null, null);
+    this(appInfo, options, null, null, null, MainDexClasses.NONE, null);
   }
 
   /**
    * Create an IR converter for processing methods with full program optimization disabled.
    */
   public IRConverter(AppInfo appInfo, InternalOptions options, Timing timing, CfgPrinter printer) {
-    this(appInfo, options, timing, printer, null, null);
+    this(appInfo, options, timing, printer, null, MainDexClasses.NONE, null);
   }
 
   /**
@@ -271,8 +275,9 @@
       InternalOptions options,
       Timing timing,
       CfgPrinter printer,
+      MainDexClasses mainDexClasses,
       RootSet rootSet) {
-    this(appView.appInfo(), options, timing, printer, appView, rootSet);
+    this(appView.appInfo(), options, timing, printer, appView, mainDexClasses, rootSet);
   }
 
   private boolean enableInterfaceMethodDesugaring() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index b78f6df..bf36c20 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -517,6 +517,11 @@
       for (int i = 0; i < directMethods.length; i++) {
         DexEncodedMethod encodedMethod = directMethods[i];
         if (implMethod.match(encodedMethod)) {
+          // Check that this method is synthetic, since this implies that the method cannot be kept
+          // by an explicit -keep rule. This is necessary because we could otherwise be changing
+          // the signature of a kept method here, which is not allowed (see b/120971047).
+          assert encodedMethod.accessFlags.isSynthetic();
+
           // We need to create a new static method with the same code to be able to safely
           // relax its accessibility without making it virtual.
           MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy();
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 d10951a..2244653 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
@@ -2118,7 +2118,7 @@
     } else if (inType.lessThanOrEqual(instanceOfType, appInfo) && !inType.isNullable()) {
       result = InstanceOfResult.TRUE;
     } else if (!inValue.isPhi()
-        && inValue.definition.isNewInstance()
+        && inValue.definition.isCreatingInstanceOrArray()
         && instanceOfType.strictlyLessThan(inType, appInfo)) {
       result = InstanceOfResult.FALSE;
     } else if (appInfo.hasLiveness()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index dd7f85c..173de18 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.MainDexDirectReferenceTracer;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import java.util.BitSet;
@@ -29,7 +30,7 @@
 import java.util.ListIterator;
 import java.util.function.Predicate;
 
-final class DefaultInliningOracle implements InliningOracle, InliningStrategy {
+public final class DefaultInliningOracle implements InliningOracle, InliningStrategy {
 
   private final AppView<? extends AppInfoWithLiveness> appView;
   private final Inliner inliner;
@@ -187,6 +188,7 @@
     }
 
     DexClass holder = inliner.appView.appInfo().definitionFor(candidate.method.getHolder());
+
     if (holder.isInterface()) {
       // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception at
       // runtime.
@@ -233,6 +235,21 @@
         return false;
       }
     }
+
+    if (!inliner.mainDexClasses.isEmpty()) {
+      // Don't inline code with references beyond root main dex classes into a root main dex class.
+      // If we do this it can increase the size of the main dex dependent classes.
+      if (inliner.mainDexClasses.getRoots().contains(method.method.holder)
+          && MainDexDirectReferenceTracer.hasReferencesOutsideFromCode(
+          appView.appInfo(), candidate, inliner.mainDexClasses.getRoots())) {
+        if (info != null) {
+          info.exclude(invoke, "target has references beyond main dex");
+        }
+        return false;
+      }
+      // Allow inlining into the classes in the main dex dependent set without restrictions.
+    }
+
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 3cd19dc..2b0d831 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -30,6 +30,7 @@
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -46,6 +47,7 @@
   protected final AppView<? extends AppInfoWithLiveness> appView;
   private final IRConverter converter;
   final InternalOptions options;
+  final MainDexClasses mainDexClasses;
 
   // State for inlining methods which are known to be called twice.
   private boolean applyDoubleInlining = false;
@@ -58,10 +60,12 @@
   public Inliner(
       AppView<? extends AppInfoWithLiveness> appView,
       IRConverter converter,
-      InternalOptions options) {
+      InternalOptions options,
+      MainDexClasses mainDexClasses) {
     this.appView = appView;
     this.converter = converter;
     this.options = options;
+    this.mainDexClasses = mainDexClasses;
     fillInBlackList(appView.appInfo());
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 4e3f9be..3292704 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -24,6 +24,8 @@
 
   private AppInfoWithLiveness appInfo;
 
+  private boolean allowStaticInterfaceMethodCalls = true;
+
   // Currently used only by the vertical class merger (in all other cases this is the identity).
   //
   // When merging a type A into its subtype B we need to inline A.<init>() into B.<init>().
@@ -46,6 +48,10 @@
     this.graphLense = graphLense;
   }
 
+  public void disallowStaticInterfaceMethodCalls() {
+    allowStaticInterfaceMethodCalls = false;
+  }
+
   public ConstraintWithTarget forAlwaysMaterializingUser() {
     return ConstraintWithTarget.ALWAYS;
   }
@@ -280,6 +286,11 @@
       DexType methodHolder = graphLense.lookupType(target.method.holder);
       DexClass methodClass = appInfo.definitionFor(methodHolder);
       if (methodClass != null) {
+        if (!allowStaticInterfaceMethodCalls && methodClass.isInterface() && target.hasCode()) {
+          // See b/120121170.
+          return ConstraintWithTarget.NEVER;
+        }
+
         ConstraintWithTarget methodConstraintWithTarget =
             ConstraintWithTarget.deriveConstraint(
                 invocationContext, methodHolder, target.accessFlags, appInfo);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
index f906137..6957cb1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
@@ -19,7 +19,7 @@
   @Override
   LambdaGroupId validateAndCreate(Kotlin kotlin, DexClass lambda, InternalOptions options)
       throws LambdaStructureError {
-    boolean accessRelaxed = options.proguardConfiguration.isAccessModificationAllowed();
+    boolean accessRelaxed = options.getProguardConfiguration().isAccessModificationAllowed();
 
     assert lambda.hasKotlinInfo() && lambda.getKotlinInfo().isSyntheticClass();
     assert lambda.getKotlinInfo().asSyntheticClass().isJavaStyleLambda();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
index 36f9ecd..3d60e0f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
@@ -19,7 +19,7 @@
   @Override
   LambdaGroupId validateAndCreate(Kotlin kotlin, DexClass lambda, InternalOptions options)
       throws LambdaStructureError {
-    boolean accessRelaxed = options.proguardConfiguration.isAccessModificationAllowed();
+    boolean accessRelaxed = options.getProguardConfiguration().isAccessModificationAllowed();
 
     assert lambda.hasKotlinInfo() && lambda.getKotlinInfo().isSyntheticClass();
     assert lambda.getKotlinInfo().asSyntheticClass().isKotlinStyleLambda();
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 05c8e87..049b2e7 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
@@ -1442,7 +1442,7 @@
   }
 
   private boolean needsSingleResultOverlappingLongOperandsWorkaround(LiveIntervals intervals) {
-    if (!options.canHaveCmpLongBug()) {
+    if (!options.canHaveCmpLongBug() && !options.canHaveLongToIntBug()) {
       return false;
     }
     if (intervals.requiredRegisters() == 2) {
@@ -1459,7 +1459,11 @@
       return false;
     }
     Instruction definition = intervals.getValue().definition;
-    return definition.isCmp() && definition.asCmp().inValues().get(0).outType().isWide();
+    if (definition.isCmp()) {
+      return definition.inValues().get(0).outType().isWide();
+    }
+    return definition.isNumberConversion()
+        && definition.asNumberConversion().isLongToIntConversion();
   }
 
   private boolean singleOverlappingLong(int register1, int register2) {
@@ -1470,15 +1474,23 @@
   // allocating for the result?
   private boolean isSingleResultOverlappingLongOperands(LiveIntervals intervals, int register) {
     assert needsSingleResultOverlappingLongOperandsWorkaround(intervals);
-    Value left = intervals.getValue().definition.asCmp().leftValue();
-    Value right = intervals.getValue().definition.asCmp().rightValue();
-    int leftReg =
-        left.getLiveIntervals().getSplitCovering(intervals.getStart()).getRegister();
-    int rightReg =
-        right.getLiveIntervals().getSplitCovering(intervals.getStart()).getRegister();
-    assert leftReg != NO_REGISTER;
-    assert rightReg != NO_REGISTER;
-    return singleOverlappingLong(register, leftReg) || singleOverlappingLong(register, rightReg);
+    if (intervals.getValue().definition.isCmp()) {
+      Value left = intervals.getValue().definition.asCmp().leftValue();
+      Value right = intervals.getValue().definition.asCmp().rightValue();
+      int leftReg =
+          left.getLiveIntervals().getSplitCovering(intervals.getStart()).getRegister();
+      int rightReg =
+          right.getLiveIntervals().getSplitCovering(intervals.getStart()).getRegister();
+      assert leftReg != NO_REGISTER;
+      assert rightReg != NO_REGISTER;
+      return singleOverlappingLong(register, leftReg) || singleOverlappingLong(register, rightReg);
+    } else {
+      assert intervals.getValue().definition.isNumberConversion();
+      Value inputValue = intervals.getValue().definition.asNumberConversion().inValues().get(0);
+      int inputReg
+          = inputValue.getLiveIntervals().getSplitCovering(intervals.getStart()).getRegister();
+      return register == inputReg;
+    }
   }
 
   // The dalvik jit had a bug where the long operations add, sub, or, xor and and would write
diff --git a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
index c707968..18998c8 100644
--- a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
 import com.android.tools.r8.graph.JarApplicationReader;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.conversion.JarSourceCode;
@@ -62,6 +63,10 @@
         ? inliningConstraints.forMonitor() : ConstraintWithTarget.ALWAYS;
   }
 
+  public void disallowStaticInterfaceMethodCalls() {
+    inliningConstraints.disallowStaticInterfaceMethodCalls();
+  }
+
   public ConstraintWithTarget getConstraint() {
     return constraint;
   }
@@ -160,12 +165,15 @@
         }
         break;
 
-      case Opcodes.INVOKESTATIC:
-        type = Invoke.Type.STATIC;
-        assert noNeedToUseGraphLense(target, type);
+      case Opcodes.INVOKESTATIC: {
+        // Static invokes may have changed as a result of horizontal class merging.
+        GraphLenseLookupResult lookup = graphLense.lookupMethod(target, null, Invoke.Type.STATIC);
+        target = lookup.getMethod();
+        type = lookup.getType();
         break;
+      }
 
-      case Opcodes.INVOKEVIRTUAL:
+      case Opcodes.INVOKEVIRTUAL: {
         type = Invoke.Type.VIRTUAL;
         // Instructions that target a private method in the same class translates to invoke-direct.
         if (target.holder == method.method.holder) {
@@ -174,8 +182,13 @@
             type = Invoke.Type.DIRECT;
           }
         }
-        assert noNeedToUseGraphLense(target, type);
+
+        // Virtual invokes may have changed to interface invokes as a result of member rebinding.
+        GraphLenseLookupResult lookup = graphLense.lookupMethod(target, null, type);
+        target = lookup.getMethod();
+        type = lookup.getType();
         break;
+      }
 
       default:
         throw new Unreachable("Unexpected opcode " + opcode);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index d15ed5a..4f79660 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -6,6 +6,7 @@
 
 import static kotlinx.metadata.Flag.Property.IS_VAR;
 
+import com.android.tools.r8.graph.DexClass;
 import kotlinx.metadata.KmClassVisitor;
 import kotlinx.metadata.KmConstructorVisitor;
 import kotlinx.metadata.KmFunctionVisitor;
@@ -14,14 +15,15 @@
 
 public class KotlinClass extends KotlinInfo<KotlinClassMetadata.Class> {
 
-  static KotlinClass fromKotlinClassMetadata(KotlinClassMetadata kotlinClassMetadata) {
+  static KotlinClass fromKotlinClassMetadata(
+      KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
     assert kotlinClassMetadata instanceof KotlinClassMetadata.Class;
     KotlinClassMetadata.Class kClass = (KotlinClassMetadata.Class) kotlinClassMetadata;
-    return new KotlinClass(kClass);
+    return new KotlinClass(kClass, clazz);
   }
 
-  private KotlinClass(KotlinClassMetadata.Class metadata) {
-    super(metadata);
+  private KotlinClass(KotlinClassMetadata.Class metadata, DexClass clazz) {
+    super(metadata, clazz);
   }
 
   @Override
@@ -38,7 +40,7 @@
 
     @Override
     public KmConstructorVisitor visitConstructor(int ctorFlags) {
-      return new NonNullParameterHintCollector.ConstructorVisitor(nonNullparamHints);
+      return new NonNullParameterHintCollector.ConstructorVisitor(nonNullparamHints, clazz);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 6518bb8..6004201 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -77,7 +77,7 @@
     KotlinClassMetadata kMetadata = KotlinClassMetadata.read(header);
 
     if (kMetadata instanceof KotlinClassMetadata.Class) {
-      return KotlinClass.fromKotlinClassMetadata(kMetadata);
+      return KotlinClass.fromKotlinClassMetadata(kMetadata, clazz);
     } else if (kMetadata instanceof KotlinClassMetadata.FileFacade) {
       return KotlinFile.fromKotlinClassMetadata(kMetadata);
     } else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
index 7cc8aa1..ef90173 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.kotlin;
 
+import com.android.tools.r8.graph.DexClass;
 import com.google.common.collect.HashBasedTable;
 import java.util.BitSet;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -11,12 +12,18 @@
 // Provides access to kotlin information.
 public abstract class KotlinInfo<MetadataKind extends KotlinClassMetadata> {
   MetadataKind metadata;
+  DexClass clazz;
   final HashBasedTable<String, String, BitSet> nonNullparamHints = HashBasedTable.create();
 
   KotlinInfo() {
   }
 
   KotlinInfo(MetadataKind metadata) {
+    this(metadata, null);
+  }
+
+  KotlinInfo(MetadataKind metadata, DexClass clazz) {
+    this.clazz = clazz;
     processMetadata(metadata);
     this.metadata = metadata;
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/NonNullParameterHintCollector.java b/src/main/java/com/android/tools/r8/kotlin/NonNullParameterHintCollector.java
index 20621f9..079980f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/NonNullParameterHintCollector.java
+++ b/src/main/java/com/android/tools/r8/kotlin/NonNullParameterHintCollector.java
@@ -6,6 +6,7 @@
 
 import static kotlinx.metadata.Flag.Type.IS_NULLABLE;
 
+import com.android.tools.r8.graph.DexClass;
 import com.google.common.collect.HashBasedTable;
 import java.util.BitSet;
 import kotlinx.metadata.KmConstructorExtensionVisitor;
@@ -94,8 +95,18 @@
     private final String name = "<init>";
     private String descriptor = "";
 
-    ConstructorVisitor(HashBasedTable<String, String, BitSet> paramHints) {
+    ConstructorVisitor(HashBasedTable<String, String, BitSet> paramHints, DexClass clazz) {
       this.paramHints = paramHints;
+      // Enum constructor has two synthetic arguments to java.lang.Enum's sole constructor:
+      // https://docs.oracle.com/javase/8/docs/api/java/lang/Enum.html#Enum-java.lang.String-int-
+      // whereas Kotlin @Metadata is still based on constructor signature, not descriptor.
+      if (clazz != null && clazz.isEnum()) {
+        // name - The name of this enum constant, which is the identifier used to declare it.
+        paramIndex++;
+        // ordinal - The ordinal of this enumeration constant (its position in the enum declaration,
+        // where the initial constant is assigned an ordinal of zero).
+        paramIndex++;
+      }
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index ca54e6c..1f0e3de 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -80,11 +80,12 @@
       InternalOptions options) {
     this.appInfo = appInfo;
     this.reporter = options.reporter;
-    this.packageObfuscationMode = options.proguardConfiguration.getPackageObfuscationMode();
-    this.isAccessModificationAllowed = options.proguardConfiguration.isAccessModificationAllowed();
-    this.packageDictionary = options.proguardConfiguration.getPackageObfuscationDictionary();
-    this.classDictionary = options.proguardConfiguration.getClassObfuscationDictionary();
-    this.keepInnerClassStructure = options.proguardConfiguration.getKeepAttributes().signature;
+    this.packageObfuscationMode = options.getProguardConfiguration().getPackageObfuscationMode();
+    this.isAccessModificationAllowed =
+        options.getProguardConfiguration().isAccessModificationAllowed();
+    this.packageDictionary = options.getProguardConfiguration().getPackageObfuscationDictionary();
+    this.classDictionary = options.getProguardConfiguration().getClassObfuscationDictionary();
+    this.keepInnerClassStructure = options.getProguardConfiguration().getKeepAttributes().signature;
     this.noObfuscationTypes =
         DexReference.filterDexType(
             DexDefinition.mapToReference(rootSet.noObfuscation.stream()))
@@ -96,7 +97,7 @@
 
     // Initialize top-level naming state.
     topLevelState = new Namespace(
-        getPackageBinaryNameFromJavaType(options.proguardConfiguration.getPackagePrefix()));
+        getPackageBinaryNameFromJavaType(options.getProguardConfiguration().getPackagePrefix()));
     states.computeIfAbsent("", k -> topLevelState);
   }
 
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index 481f6c4..95fcfd2 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -304,7 +304,7 @@
       return;
     }
     // Undetermined identifiers matter only if minification is enabled.
-    if (!options.proguardConfiguration.isObfuscating()) {
+    if (!options.getProguardConfiguration().isObfuscating()) {
       return;
     }
     Origin origin = appInfo.originFor(originHolder);
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 43eab1d..312991e 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
@@ -31,10 +31,10 @@
     this.appInfo = appInfo;
     this.rootSet = rootSet;
     this.options = options;
-    this.dictionary = options.proguardConfiguration.getObfuscationDictionary();
-    this.useUniqueMemberNames = options.proguardConfiguration.isUseUniqueClassMemberNames();
+    this.dictionary = options.getProguardConfiguration().getObfuscationDictionary();
+    this.useUniqueMemberNames = options.getProguardConfiguration().isUseUniqueClassMemberNames();
     this.overloadAggressively =
-        options.proguardConfiguration.isOverloadAggressivelyWithoutUseUniqueClassMemberNames();
+        options.getProguardConfiguration().isOverloadAggressivelyWithoutUseUniqueClassMemberNames();
     this.globalState = NamingState.createRoot(
         appInfo.dexItemFactory, dictionary, getKeyTransform(), useUniqueMemberNames);
   }
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 992890b..e4ccbcd 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -72,8 +72,8 @@
             fieldRenaming,
             appInfo);
     timing.begin("MinifyIdentifiers");
-    new IdentifierMinifier(appInfo, options.proguardConfiguration.getAdaptClassStrings(), lens)
-        .run();
+    new IdentifierMinifier(
+        appInfo, options.getProguardConfiguration().getAdaptClassStrings(), lens).run();
     timing.end();
     return lens;
   }
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
index bbf8259..6232fb3 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
@@ -27,6 +27,7 @@
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Function;
 
 public class ProguardMapApplier {
 
@@ -385,32 +386,15 @@
     }
 
     private DexAnnotationSet substituteTypesIn(DexAnnotationSet annotations) {
-      if (annotations.isEmpty()) {
-        return annotations;
-      }
-      DexAnnotation[] result = substituteTypesIn(annotations.annotations);
-      return result == null ? annotations : new DexAnnotationSet(result);
-    }
-
-    private DexAnnotation[] substituteTypesIn(DexAnnotation[] annotations) {
-      Map<Integer, DexAnnotation> changed = new Int2ObjectArrayMap<>();
-      for (int i = 0; i < annotations.length; i++) {
-        DexAnnotation applied = substituteTypesIn(annotations[i]);
-        if (applied != annotations[i]) {
-          changed.put(i, applied);
-        }
-      }
-      return changed.isEmpty()
-          ? null
-          : ArrayUtils.copyWithSparseChanges(DexAnnotation[].class, annotations, changed);
+      return annotations.rewrite(this::substituteTypesIn);
     }
 
     private DexAnnotation substituteTypesIn(DexAnnotation annotation) {
-      return new DexAnnotation(annotation.visibility, substituteTypesIn(annotation.annotation));
+      return annotation.rewrite(this::substituteTypesIn);
     }
 
     private DexEncodedAnnotation substituteTypesIn(DexEncodedAnnotation annotation) {
-      return new DexEncodedAnnotation(substituteType(annotation.type, null), annotation.elements);
+      return annotation.rewrite(type -> substituteType(type, null), Function.identity());
     }
 
     private DexTypeList substituteTypesIn(DexTypeList types) {
diff --git a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
index fc28ab0..0f9b571 100644
--- a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
@@ -29,9 +29,10 @@
   }
 
   public void run() {
-    String renameSourceFile = options.proguardConfiguration.getRenameSourceFileAttribute();
+    String renameSourceFile = options.getProguardConfiguration().getRenameSourceFileAttribute();
     // Return early if a user wants to keep the current source file attribute as-is.
-    if (renameSourceFile == null && options.proguardConfiguration.getKeepAttributes().sourceFile) {
+    if (renameSourceFile == null
+        && options.getProguardConfiguration().getKeepAttributes().sourceFile) {
       return;
     }
     // Now, the user wants either to remove source file attribute or to rename it.
diff --git a/src/main/java/com/android/tools/r8/references/ArrayReference.java b/src/main/java/com/android/tools/r8/references/ArrayReference.java
index d6587fe..10d2a31 100644
--- a/src/main/java/com/android/tools/r8/references/ArrayReference.java
+++ b/src/main/java/com/android/tools/r8/references/ArrayReference.java
@@ -48,7 +48,7 @@
   }
 
   @Override
-  public String toDescriptor() {
+  public String getDescriptor() {
     return descriptor;
   }
 
diff --git a/src/main/java/com/android/tools/r8/references/ClassReference.java b/src/main/java/com/android/tools/r8/references/ClassReference.java
index 2ad6f40..50ce220 100644
--- a/src/main/java/com/android/tools/r8/references/ClassReference.java
+++ b/src/main/java/com/android/tools/r8/references/ClassReference.java
@@ -25,7 +25,7 @@
   }
 
   @Override
-  public String toDescriptor() {
+  public String getDescriptor() {
     return descriptor;
   }
 
@@ -38,4 +38,9 @@
   public int hashCode() {
     return System.identityHashCode(this);
   }
+
+  @Override
+  public String toString() {
+    return getDescriptor();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/references/MethodReference.java b/src/main/java/com/android/tools/r8/references/MethodReference.java
index 98abfa3..e071fe9 100644
--- a/src/main/java/com/android/tools/r8/references/MethodReference.java
+++ b/src/main/java/com/android/tools/r8/references/MethodReference.java
@@ -4,6 +4,9 @@
 package com.android.tools.r8.references;
 
 import com.android.tools.r8.Keep;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.StringUtils.BraceType;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import java.util.Objects;
@@ -29,7 +32,6 @@
     assert holderClass != null;
     assert methodName != null;
     assert formalTypes != null;
-    assert returnType != null;
     this.holderClass = holderClass;
     this.methodName = methodName;
     this.formalTypes = formalTypes;
@@ -74,4 +76,15 @@
   public int hashCode() {
     return Objects.hash(holderClass, methodName, formalTypes, returnType);
   }
+
+  public String toDescriptor() {
+    return StringUtils.join(
+            ListUtils.map(getFormalTypes(), TypeReference::getDescriptor), "", BraceType.PARENS)
+        + (getReturnType() == null ? "V" : getReturnType().getDescriptor());
+  }
+
+  @Override
+  public String toString() {
+    return getHolderClass().toString() + getMethodName() + toDescriptor();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/references/PrimitiveReference.java b/src/main/java/com/android/tools/r8/references/PrimitiveReference.java
index 6cd92b4..66c1ae7 100644
--- a/src/main/java/com/android/tools/r8/references/PrimitiveReference.java
+++ b/src/main/java/com/android/tools/r8/references/PrimitiveReference.java
@@ -12,7 +12,7 @@
   static final PrimitiveReference BOOL =
       new PrimitiveReference() {
         @Override
-        public String toDescriptor() {
+        public String getDescriptor() {
           return "Z";
         }
       };
@@ -20,7 +20,7 @@
   static final PrimitiveReference BYTE =
       new PrimitiveReference() {
         @Override
-        public String toDescriptor() {
+        public String getDescriptor() {
           return "B";
         }
       };
@@ -28,7 +28,7 @@
   static final PrimitiveReference CHAR =
       new PrimitiveReference() {
         @Override
-        public String toDescriptor() {
+        public String getDescriptor() {
           return "C";
         }
       };
@@ -36,7 +36,7 @@
   static final PrimitiveReference SHORT =
       new PrimitiveReference() {
         @Override
-        public String toDescriptor() {
+        public String getDescriptor() {
           return "S";
         }
       };
@@ -44,7 +44,7 @@
   static final PrimitiveReference INT =
       new PrimitiveReference() {
         @Override
-        public String toDescriptor() {
+        public String getDescriptor() {
           return "I";
         }
       };
@@ -52,7 +52,7 @@
   static final PrimitiveReference FLOAT =
       new PrimitiveReference() {
         @Override
-        public String toDescriptor() {
+        public String getDescriptor() {
           return "F";
         }
       };
@@ -60,7 +60,7 @@
   static final PrimitiveReference LONG =
       new PrimitiveReference() {
         @Override
-        public String toDescriptor() {
+        public String getDescriptor() {
           return "J";
         }
       };
@@ -68,7 +68,7 @@
   static final PrimitiveReference DOUBLE =
       new PrimitiveReference() {
         @Override
-        public String toDescriptor() {
+        public String getDescriptor() {
           return "D";
         }
       };
@@ -88,6 +88,8 @@
         return SHORT;
       case 'I':
         return INT;
+      case 'F':
+        return FLOAT;
       case 'J':
         return LONG;
       case 'D':
@@ -103,7 +105,7 @@
   }
 
   @Override
-  public abstract String toDescriptor();
+  public abstract String getDescriptor();
 
   @Override
   public boolean equals(Object o) {
diff --git a/src/main/java/com/android/tools/r8/references/Reference.java b/src/main/java/com/android/tools/r8/references/Reference.java
index a354d31..bb8e57e 100644
--- a/src/main/java/com/android/tools/r8/references/Reference.java
+++ b/src/main/java/com/android/tools/r8/references/Reference.java
@@ -43,13 +43,13 @@
   private final ConcurrentMap<String, ArrayReference> arrays =
       new MapMaker().weakValues().makeMap();
 
-  // Weak set (via map) of method references. Both keys and values are weak.
+  // Weak map of method references. Keys are strong and must not be identical to the value.
   private final ConcurrentMap<MethodReference, MethodReference> methods =
-      new MapMaker().weakKeys().weakValues().makeMap();
+      new MapMaker().weakValues().makeMap();
 
-  // Weak set (via map) of field references. Both keys and values are weak.
+  // Weak map of field references. Keys are strong and must not be identical to the value.
   private final ConcurrentMap<FieldReference, FieldReference> fields =
-      new MapMaker().weakKeys().weakValues().makeMap();
+      new MapMaker().weakValues().makeMap();
 
   private Reference() {
     // Intentionally hidden.
@@ -122,12 +122,13 @@
       ImmutableList<TypeReference> formalTypes,
       TypeReference returnType) {
     MethodReference key = new MethodReference(holderClass, methodName, formalTypes, returnType);
-    MethodReference reference = getInstance().methods.get(key);
-    if (reference != null) {
-      return reference;
-    }
-    getInstance().methods.put(key, key);
-    return key;
+    return getInstance().methods.computeIfAbsent(key,
+        // Allocate a distinct value for the canonical reference so the key is not a strong pointer.
+        k -> new MethodReference(
+            k.getHolderClass(),
+            k.getMethodName(),
+            (ImmutableList<TypeReference>) k.getFormalTypes(),
+            k.getReturnType()));
   }
 
   /** Get a method reference from a Java reflection method. */
@@ -141,19 +142,19 @@
       builder.add(typeFromClass(parameterType));
     }
     return method(
-        classFromClass(holderClass), methodName, builder.build(), typeFromClass(returnType));
+        classFromClass(holderClass),
+        methodName,
+        builder.build(),
+        returnType == Void.TYPE ? null : typeFromClass(returnType));
   }
 
   /** Get a field reference from its full reference specification. */
   public static FieldReference field(
       ClassReference holderClass, String fieldName, TypeReference fieldType) {
     FieldReference key = new FieldReference(holderClass, fieldName, fieldType);
-    FieldReference reference = getInstance().fields.get(key);
-    if (reference != null) {
-      return reference;
-    }
-    getInstance().fields.put(key, key);
-    return key;
+    return getInstance().fields.computeIfAbsent(key,
+        // Allocate a distinct value for the canonical reference so the key is not a strong pointer.
+        k -> new FieldReference(k.getHolderClass(), k.getFieldName(), k.getFieldType()));
   }
 
   /** Get a field reference from a Java reflection field. */
diff --git a/src/main/java/com/android/tools/r8/references/TypeReference.java b/src/main/java/com/android/tools/r8/references/TypeReference.java
index df0a502..44fb54a 100644
--- a/src/main/java/com/android/tools/r8/references/TypeReference.java
+++ b/src/main/java/com/android/tools/r8/references/TypeReference.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.references;
 
 import com.android.tools.r8.Keep;
+import com.android.tools.r8.utils.DescriptorUtils;
 
 @Keep
 public interface TypeReference {
@@ -13,7 +14,7 @@
    *
    * @return The descriptor for the type.
    */
-  String toDescriptor();
+  String getDescriptor();
 
   /** Predicate that is true iff the TypeReference is an instance of ClassTypeReference. */
   default boolean isClass() {
@@ -29,4 +30,8 @@
   default boolean isPrimitive() {
     return false;
   }
+
+  default String getTypeName() {
+    return DescriptorUtils.descriptorToJavaType(getDescriptor());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
index 610bfd3..ae88ba7 100644
--- a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -22,10 +22,10 @@
  */
 public class AbstractMethodRemover {
 
-  private final AppInfoWithSubtyping appInfo;
+  private final AppInfoWithLiveness appInfo;
   private ScopedDexMethodSet scope = new ScopedDexMethodSet();
 
-  public AbstractMethodRemover(AppInfoWithSubtyping appInfo) {
+  public AbstractMethodRemover(AppInfoWithLiveness appInfo) {
     this.appInfo = appInfo;
   }
 
@@ -52,7 +52,9 @@
     List<DexEncodedMethod> methods = null;
     for (int i = 0; i < virtualMethods.length; i++) {
       DexEncodedMethod method = virtualMethods[i];
-      if (scope.addMethodIfMoreVisible(method) || !method.accessFlags.isAbstract()) {
+      if (scope.addMethodIfMoreVisible(method)
+          || !method.accessFlags.isAbstract()
+          || appInfo.isPinned(method.method)) {
         if (methods != null) {
           methods.add(method);
         }
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index d626407..8ce5cb0 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -5,26 +5,33 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
 import java.util.List;
 
 public class AnnotationRemover {
 
   private final AppInfoWithLiveness appInfo;
+  private final GraphLense lense;
   private final ProguardKeepAttributes keep;
   private final InternalOptions options;
 
-  public AnnotationRemover(AppInfoWithLiveness appInfo, InternalOptions options) {
+  public AnnotationRemover(AppInfoWithLiveness appInfo, GraphLense lense, InternalOptions options) {
     this.appInfo = appInfo;
-    this.keep = options.proguardConfiguration.getKeepAttributes();
+    this.lense = lense;
+    this.keep = options.getProguardConfiguration().getKeepAttributes();
     this.options = options;
   }
 
@@ -36,12 +43,16 @@
         annotation, isAnnotationTypeLive(annotation), appInfo.dexItemFactory, options);
   }
 
-  public static boolean shouldKeepAnnotation(
+  static boolean shouldKeepAnnotation(
       DexAnnotation annotation,
       boolean isAnnotationTypeLive,
       DexItemFactory dexItemFactory,
       InternalOptions options) {
-    ProguardKeepAttributes config = options.proguardConfiguration.getKeepAttributes();
+    ProguardKeepAttributes config =
+        options.getProguardConfiguration() != null
+            ? options.getProguardConfiguration().getKeepAttributes()
+            : ProguardKeepAttributes.fromPatterns(ImmutableList.of());
+
     switch (annotation.visibility) {
       case DexAnnotation.VISIBILITY_SYSTEM:
         // InnerClass and EnclosingMember are represented in class attributes, not annotations.
@@ -94,7 +105,7 @@
   private boolean isAnnotationTypeLive(DexAnnotation annotation) {
     DexType annotationType = annotation.annotation.type.toBaseType(appInfo.dexItemFactory);
     DexClass definition = appInfo.definitionFor(annotationType);
-    // TODO(73102187): How to handle annotations without definition.
+    // TODO(b/73102187): How to handle annotations without definition.
     if (options.enableTreeShaking && definition == null) {
       return false;
     }
@@ -133,20 +144,49 @@
   public void run() {
     for (DexProgramClass clazz : appInfo.classes()) {
       stripAttributes(clazz);
-      clazz.annotations = clazz.annotations.keepIf(this::filterAnnotations);
+      clazz.annotations = clazz.annotations.rewrite(this::rewriteAnnotation);
       clazz.forEachMethod(this::processMethod);
       clazz.forEachField(this::processField);
     }
   }
 
   private void processMethod(DexEncodedMethod method) {
-    method.annotations = method.annotations.keepIf(this::filterAnnotations);
+    method.annotations = method.annotations.rewrite(this::rewriteAnnotation);
     method.parameterAnnotationsList =
         method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations);
   }
 
   private void processField(DexEncodedField field) {
-    field.annotations = field.annotations.keepIf(this::filterAnnotations);
+    field.annotations = field.annotations.rewrite(this::rewriteAnnotation);
+  }
+
+  private DexAnnotation rewriteAnnotation(DexAnnotation original) {
+    // Check if we should keep this annotation first.
+    if (!filterAnnotations(original)) {
+      return null;
+    }
+    // Then, filter out values that refer to dead definitions.
+    return original.rewrite(this::rewriteEncodedAnnotation);
+  }
+
+  private DexEncodedAnnotation rewriteEncodedAnnotation(DexEncodedAnnotation original) {
+    DexType annotationType = original.type.toBaseType(appInfo.dexItemFactory);
+    return original.rewrite(
+        lense::lookupType,
+        element -> rewriteAnnotationElement(lense.lookupType(annotationType), element));
+  }
+
+  private DexAnnotationElement rewriteAnnotationElement(
+      DexType annotationType, DexAnnotationElement original) {
+    DexClass definition = appInfo.definitionFor(annotationType);
+    // TODO(b/73102187): How to handle annotations without definition.
+    if (definition == null) {
+      return original;
+    }
+    assert definition.isInterface();
+    boolean liveGetter = Arrays.stream(definition.virtualMethods())
+        .anyMatch(method -> method.method.name == original.name);
+    return liveGetter ? original : null;
   }
 
   private boolean enclosingMethodPinned(DexClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/shaking/CollectingGraphConsumer.java b/src/main/java/com/android/tools/r8/shaking/CollectingGraphConsumer.java
new file mode 100644
index 0000000..27bdd95
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/CollectingGraphConsumer.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.experimental.graphinfo.GraphNode;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/** Base implementation of a graph consumer that collects all of graph nodes and edges. */
+public class CollectingGraphConsumer implements GraphConsumer {
+
+  // Possible sub-consumer that is also inspecting the kept-graph.
+  private final GraphConsumer subConsumer;
+
+  // Directional map backwards from targets to direct sources.
+  private final Map<GraphNode, Map<GraphNode, Set<GraphEdgeInfo>>> target2sources =
+      new IdentityHashMap<>();
+
+  public CollectingGraphConsumer(GraphConsumer subConsumer) {
+    this.subConsumer = subConsumer;
+  }
+
+  @Override
+  public void acceptEdge(GraphNode source, GraphNode target, GraphEdgeInfo info) {
+    target2sources
+        .computeIfAbsent(target, k -> new IdentityHashMap<>())
+        .computeIfAbsent(source, k -> new HashSet<>())
+        .add(info);
+    if (subConsumer != null) {
+      subConsumer.acceptEdge(source, target, info);
+    }
+  }
+
+  public Set<GraphNode> getTargets() {
+    return target2sources.keySet();
+  }
+
+  public Map<GraphNode, Set<GraphEdgeInfo>> getSourcesTargeting(GraphNode target) {
+    return target2sources.get(target);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index c579500..95f077d 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -9,7 +9,17 @@
 
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.experimental.graphinfo.AnnotationGraphNode;
+import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
+import com.android.tools.r8.experimental.graphinfo.FieldGraphNode;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo.EdgeKind;
+import com.android.tools.r8.experimental.graphinfo.GraphNode;
+import com.android.tools.r8.experimental.graphinfo.KeepRuleGraphNode;
+import com.android.tools.r8.experimental.graphinfo.MethodGraphNode;
 import com.android.tools.r8.graph.AppInfo.ResolutionResult;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
@@ -36,15 +46,6 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.KeyedDexItem;
 import com.android.tools.r8.graph.PresortedComparable;
-import com.android.tools.r8.graphinfo.AnnotationGraphNode;
-import com.android.tools.r8.graphinfo.ClassGraphNode;
-import com.android.tools.r8.graphinfo.FieldGraphNode;
-import com.android.tools.r8.graphinfo.GraphConsumer;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo.EdgeKind;
-import com.android.tools.r8.graphinfo.GraphNode;
-import com.android.tools.r8.graphinfo.KeepRuleGraphNode;
-import com.android.tools.r8.graphinfo.MethodGraphNode;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Invoke.Type;
@@ -52,6 +53,8 @@
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
 import com.android.tools.r8.shaking.RootSetBuilder.IfRuleEvaluator;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
@@ -59,6 +62,7 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
@@ -1153,9 +1157,9 @@
   private void markDirectStaticOrConstructorMethodAsLive(
       DexEncodedMethod encodedMethod, KeepReason reason) {
     assert encodedMethod != null;
+    markMethodAsTargeted(encodedMethod, reason);
     if (!liveMethods.contains(encodedMethod)) {
       markTypeAsLive(encodedMethod.method.holder);
-      markMethodAsTargeted(encodedMethod, reason);
       if (Log.ENABLED) {
         Log.verbose(getClass(), "Method `%s` has become live due to direct invoke",
             encodedMethod.method);
@@ -2107,26 +2111,32 @@
       this.callSites = previous.callSites;
       this.brokenSuperInvokes = lense.rewriteMethodsConservatively(previous.brokenSuperInvokes);
       this.prunedTypes = rewriteItems(previous.prunedTypes, lense::lookupType);
-      assert lense.assertDefinitionNotModified(previous.noSideEffects.keySet());
+      assert lense.assertDefinitionsNotModified(previous.noSideEffects.keySet());
       this.noSideEffects = previous.noSideEffects;
-      assert lense.assertDefinitionNotModified(previous.assumedValues.keySet());
+      assert lense.assertDefinitionsNotModified(previous.assumedValues.keySet());
       this.assumedValues = previous.assumedValues;
-      assert lense.assertDefinitionNotModified(
-          previous.alwaysInline.stream().map(this::definitionFor).filter(Objects::nonNull)
+      assert lense.assertDefinitionsNotModified(
+          previous.alwaysInline.stream()
+              .map(this::definitionFor)
+              .filter(Objects::nonNull)
               .collect(Collectors.toList()));
       this.alwaysInline = previous.alwaysInline;
       this.forceInline = lense.rewriteMethodsWithRenamedSignature(previous.forceInline);
       this.neverInline = lense.rewriteMethodsWithRenamedSignature(previous.neverInline);
-      assert lense.assertDefinitionNotModified(
-          previous.neverMerge.stream().map(this::definitionFor).filter(Objects::nonNull)
+      assert lense.assertDefinitionsNotModified(
+          previous.neverMerge.stream()
+              .map(this::definitionFor)
+              .filter(Objects::nonNull)
               .collect(Collectors.toList()));
       this.neverClassInline = rewriteItems(previous.neverClassInline, lense::lookupType);
       this.neverMerge = previous.neverMerge;
       this.identifierNameStrings =
           lense.rewriteReferencesConservatively(previous.identifierNameStrings);
       // Switchmap classes should never be affected by renaming.
-      assert lense.assertDefinitionNotModified(
-          previous.switchMaps.keySet().stream().map(this::definitionFor).filter(Objects::nonNull)
+      assert lense.assertDefinitionsNotModified(
+          previous.switchMaps.keySet().stream()
+              .map(this::definitionFor)
+              .filter(Objects::nonNull)
               .collect(Collectors.toList()));
       this.switchMaps = previous.switchMaps;
       this.ordinalsMaps = rewriteKeys(previous.ordinalsMaps, lense::lookupType);
@@ -2757,7 +2767,11 @@
   }
 
   private void registerEdge(GraphNode target, KeepReason reason) {
-    keptGraphConsumer.acceptEdge(getSourceNode(reason), target, getEdgeInfo(reason));
+    GraphNode sourceNode = getSourceNode(reason);
+    // TODO(b/120959039): Make sure we do have edges to nodes deriving library nodes!
+    if (!sourceNode.isLibraryNode()) {
+      keptGraphConsumer.acceptEdge(sourceNode, target, getEdgeInfo(reason));
+    }
   }
 
   private GraphNode getSourceNode(KeepReason reason) {
@@ -2782,19 +2796,52 @@
   }
 
   AnnotationGraphNode getAnnotationGraphNode(DexItem type) {
-    return annotationNodes.computeIfAbsent(type, AnnotationGraphNode::new);
+    return annotationNodes.computeIfAbsent(type, t -> {
+      if (t instanceof DexType) {
+        return new AnnotationGraphNode(getClassGraphNode(((DexType) t)));
+      }
+      throw new Unimplemented("Incomplete support for annotation node on item: " + type.getClass());
+    });
   }
 
   ClassGraphNode getClassGraphNode(DexType type) {
-    return classNodes.computeIfAbsent(type, ClassGraphNode::new);
+    return classNodes.computeIfAbsent(type,
+        t -> new ClassGraphNode(
+            appInfo.definitionFor(type).isLibraryClass(),
+            Reference.classFromDescriptor(t.toDescriptorString())));
   }
 
   MethodGraphNode getMethodGraphNode(DexMethod context) {
-    return methodNodes.computeIfAbsent(context, MethodGraphNode::new);
+    return methodNodes.computeIfAbsent(
+        context,
+        m -> {
+          boolean isLibraryNode = appInfo.definitionFor(context.holder).isLibraryClass();
+          Builder<TypeReference> builder = ImmutableList.builder();
+          for (DexType param : m.proto.parameters.values) {
+            builder.add(Reference.typeFromDescriptor(param.toDescriptorString()));
+          }
+          return new MethodGraphNode(
+              isLibraryNode,
+              Reference.method(
+                  Reference.classFromDescriptor(m.holder.toDescriptorString()),
+                  m.name.toString(),
+                  builder.build(),
+                  m.proto.returnType.isVoidType()
+                      ? null
+                      : Reference.typeFromDescriptor(m.proto.returnType.toDescriptorString())));
+        });
   }
 
   FieldGraphNode getFieldGraphNode(DexField context) {
-    return fieldNodes.computeIfAbsent(context, FieldGraphNode::new);
+    return fieldNodes.computeIfAbsent(
+        context,
+        f ->
+            new FieldGraphNode(
+                appInfo.definitionFor(context.getHolder()).isLibraryClass(),
+                Reference.field(
+                    Reference.classFromDescriptor(f.getHolder().toDescriptorString()),
+                    f.name.toString(),
+                    Reference.typeFromDescriptor(f.type.toDescriptorString()))));
   }
 
   KeepRuleGraphNode getKeepRuleGraphNode(ProguardKeepRule rule) {
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index 82737a6..16d78a7 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -3,12 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo.EdgeKind;
+import com.android.tools.r8.experimental.graphinfo.GraphNode;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo.EdgeKind;
-import com.android.tools.r8.graphinfo.GraphNode;
 
 // TODO(herhut): Canonicalize reason objects.
 public abstract class KeepReason {
@@ -50,7 +50,7 @@
   }
 
   public static KeepReason fieldReferencedIn(DexEncodedMethod method) {
-    return new ReferenedFrom(method);
+    return new ReferencedFrom(method);
   }
 
   public static KeepReason referencedInAnnotation(DexItem holder) {
@@ -219,9 +219,9 @@
     }
   }
 
-  private static class ReferenedFrom extends BasedOnOtherMethod {
+  private static class ReferencedFrom extends BasedOnOtherMethod {
 
-    private ReferenedFrom(DexEncodedMethod method) {
+    private ReferencedFrom(DexEncodedMethod method) {
       super(method);
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index 9e66c52..543dcc4 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -18,6 +19,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
+import com.google.common.collect.ImmutableSet;
 import java.util.Set;
 import java.util.function.Consumer;
 
@@ -44,14 +46,54 @@
       // Super and interfaces are live, no need to add them.
       traceAnnotationsDirectDependencies(clazz.annotations);
       clazz.forEachField(field -> consumer.accept(field.field.type));
-      clazz.forEachMethod(
-          method -> {
-            traceMethodDirectDependencies(method.method, consumer);
-            method.registerCodeReferences(codeDirectReferenceCollector);
-          });
+      clazz.forEachMethod(method -> {
+        traceMethodDirectDependencies(method.method, consumer);
+        method.registerCodeReferences(codeDirectReferenceCollector);
+      });
     }
   }
 
+  public void runOnCode(DexEncodedMethod method) {
+    method.registerCodeReferences(codeDirectReferenceCollector);
+  }
+
+  private static class BooleanBox {
+    boolean value = false;
+  }
+
+  public static boolean hasReferencesOutside(
+      AppInfoWithSubtyping appInfo, DexProgramClass clazz, Set<DexType> types) {
+    BooleanBox result = new BooleanBox();
+
+    new MainDexDirectReferenceTracer(appInfo, type -> {
+      if (!types.contains(type)) {
+        DexClass cls = appInfo.definitionFor(type);
+        if (cls != null && !cls.isLibraryClass()) {
+          result.value = true;
+        }
+      }
+    }).run(ImmutableSet.of(clazz.type));
+
+    return result.value;
+  }
+
+  public static boolean hasReferencesOutsideFromCode(
+      AppInfoWithSubtyping appInfo, DexEncodedMethod method, Set<DexType> classes) {
+
+    BooleanBox result = new BooleanBox();
+
+    new MainDexDirectReferenceTracer(appInfo, type -> {
+      if (!classes.contains(type)) {
+        DexClass cls = appInfo.definitionFor(type);
+        if (cls != null && !cls.isLibraryClass()) {
+          result.value = true;
+        }
+      }
+    }).runOnCode(method);
+
+    return result.value;
+  }
+
   private void traceAnnotationsDirectDependencies(DexAnnotationSet annotations) {
     annotations.collectIndexedItems(annotationDirectReferenceCollector);
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 26bfcfb..c153fe3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -103,12 +103,14 @@
       this.ignoreWarnings = ignoreWarnings;
     }
 
-    public void disableOptimization() {
+    public Builder disableOptimization() {
       this.optimizing = false;
+      return this;
     }
 
-    public void disableObfuscation() {
+    public Builder disableObfuscation() {
       this.obfuscating = false;
+      return this;
     }
 
     boolean isObfuscating() {
@@ -123,8 +125,9 @@
       return shrinking;
     }
 
-    public void disableShrinking() {
+    public Builder disableShrinking() {
       shrinking = false;
+      return this;
     }
 
     public void setPrintConfiguration(boolean printConfiguration) {
@@ -165,8 +168,9 @@
       this.renameSourceFileAttribute = renameSourceFileAttribute;
     }
 
-    public void addKeepAttributePatterns(List<String> keepAttributePatterns) {
+    public Builder addKeepAttributePatterns(List<String> keepAttributePatterns) {
       this.keepAttributePatterns.addAll(keepAttributePatterns);
+      return this;
     }
 
     public void addRule(ProguardConfigurationRule rule) {
@@ -315,20 +319,13 @@
     }
 
     public ProguardConfiguration build() {
-      boolean rulesWasEmpty = rules.isEmpty();
-      if (rules.isEmpty()) {
-        disableObfuscation();
-        disableShrinking();
-        // TODO(sgjesse): Honor disable-optimization flag when no config provided.
-        // disableOptimization();
-      }
-
-      if ((keepAttributePatterns.isEmpty() && rulesWasEmpty)
-          || (forceProguardCompatibility && !isObfuscating())
-          || !isShrinking()) {
+      if (forceProguardCompatibility && !isObfuscating()) {
+        // For Proguard -keepattributes are only applicable when obfuscating.
         keepAttributePatterns.addAll(ProguardKeepAttributes.KEEP_ALL);
       }
-
+      // If either of the flags -dontshrink, -dontobfuscate or -dontoptimize is passed, or
+      // shrinking or minification is turned off through the API, then add a match all rule
+      // which will apply that.
       if (!isShrinking() || !isObfuscating() || !isOptimizing()) {
         addRule(ProguardKeepRule.defaultKeepAllRule(modifiers -> {
           modifiers.setAllowsShrinking(isShrinking());
@@ -601,11 +598,6 @@
     return keepDirectories;
   }
 
-  public static ProguardConfiguration defaultConfiguration(DexItemFactory dexItemFactory,
-      Reporter reporter) {
-    return builder(dexItemFactory, reporter).build();
-  }
-
   public boolean isPrintSeeds() {
     return printSeeds;
   }
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 33ee223..8d2c1b9 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -81,6 +81,10 @@
       // TODO(b/37137994): -outjars should be reported as errors, not just as warnings!
       "outjars");
 
+  private static final List<String> WARNED_OPTIONAL_SINGLE_ARG_OPTIONS = ImmutableList.of(
+      // TODO(b/121340442): we may support this later.
+      "dump");
+
   private static final List<String> WARNED_FLAG_OPTIONS = ImmutableList.of(
       // TODO(b/73707846): add support -addconfigurationdebugging
       "addconfigurationdebugging");
@@ -416,7 +420,11 @@
         if (option == null) {
           option = Iterables.find(WARNED_SINGLE_ARG_OPTIONS, this::skipOptionWithSingleArg, null);
           if (option == null) {
-            return false;
+            option = Iterables.find(
+                WARNED_OPTIONAL_SINGLE_ARG_OPTIONS, this::skipOptionWithOptionalSingleArg, null);
+            if (option == null) {
+              return false;
+            }
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
index 79828b3..cff6164 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
@@ -73,7 +73,7 @@
       if (previous) {
         return true;
       }
-      if (pattern.charAt(0) == '!') {
+      if (pattern.length() > 0 && pattern.charAt(0) == '!') {
         if (matches(pattern, 1, text, 0)) {
           break;
         }
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 75523bf..69d2ee1 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Descriptor;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
@@ -25,12 +26,16 @@
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.OffOrAuto;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
 import java.io.PrintStream;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -40,6 +45,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -58,7 +64,7 @@
   private final Map<DexDefinition, ProguardKeepRule> noShrinking = new IdentityHashMap<>();
   private final Set<DexDefinition> noOptimization = Sets.newIdentityHashSet();
   private final Set<DexDefinition> noObfuscation = Sets.newIdentityHashSet();
-  private final Set<DexDefinition> reasonAsked = Sets.newIdentityHashSet();
+  private final LinkedHashMap<DexDefinition, DexDefinition> reasonAsked = new LinkedHashMap<>();
   private final Set<DexDefinition> keepPackageName = Sets.newIdentityHashSet();
   private final Set<ProguardConfigurationRule> rulesThatUseExtendsOrImplementsWrong =
       Sets.newIdentityHashSet();
@@ -123,21 +129,22 @@
       Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
       Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
       if (rule instanceof ProguardKeepRule) {
+        if (clazz.isLibraryClass()) {
+          return;
+        }
         switch (((ProguardKeepRule) rule).getType()) {
-          case KEEP_CLASS_MEMBERS: {
+          case KEEP_CLASS_MEMBERS:
             // Members mentioned at -keepclassmembers always depend on their holder.
             preconditionSupplier = ImmutableMap.of(definition -> true, clazz);
-            markMatchingVisibleMethods(clazz, memberKeepRules, rule, preconditionSupplier);
-            markMatchingVisibleFields(clazz, memberKeepRules, rule, preconditionSupplier);
+            markMatchingVisibleMethods(clazz, memberKeepRules, rule, preconditionSupplier, false);
+            markMatchingVisibleFields(clazz, memberKeepRules, rule, preconditionSupplier, false);
             break;
-          }
-          case KEEP_CLASSES_WITH_MEMBERS: {
+          case KEEP_CLASSES_WITH_MEMBERS:
             if (!allRulesSatisfied(memberKeepRules, clazz)) {
               break;
             }
             // fallthrough;
-          }
-          case KEEP: {
+          case KEEP:
             markClass(clazz, rule);
             preconditionSupplier = new HashMap<>();
             if (ifRule != null) {
@@ -150,10 +157,9 @@
               // not triggered conditionally.
               preconditionSupplier.put((definition -> true), null);
             }
-            markMatchingVisibleMethods(clazz, memberKeepRules, rule, preconditionSupplier);
-            markMatchingVisibleFields(clazz, memberKeepRules, rule, preconditionSupplier);
+            markMatchingVisibleMethods(clazz, memberKeepRules, rule, preconditionSupplier, false);
+            markMatchingVisibleFields(clazz, memberKeepRules, rule, preconditionSupplier, false);
             break;
-          }
           case CONDITIONAL:
             throw new Unreachable("-if rule will be evaluated separately, not here.");
         }
@@ -170,11 +176,11 @@
       } else if (rule instanceof ProguardWhyAreYouKeepingRule
           || rule instanceof ProguardKeepPackageNamesRule) {
         markClass(clazz, rule);
-        markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
-        markMatchingVisibleFields(clazz, memberKeepRules, rule, null);
+        markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true);
+        markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true);
       } else if (rule instanceof ProguardAssumeNoSideEffectRule) {
-        markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
-        markMatchingVisibleFields(clazz, memberKeepRules, rule, null);
+        markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true);
+        markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true);
       } else if (rule instanceof ClassMergingRule) {
         if (allRulesSatisfied(memberKeepRules, clazz)) {
           markClass(clazz, rule);
@@ -186,8 +192,8 @@
           markClass(clazz, rule);
         }
       } else if (rule instanceof ProguardAssumeValuesRule) {
-        markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
-        markMatchingVisibleFields(clazz, memberKeepRules, rule, null);
+        markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true);
+        markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true);
       } else {
         assert rule instanceof ProguardIdentifierNameStringRule;
         markMatchingFields(clazz, memberKeepRules, rule, null);
@@ -249,7 +255,7 @@
         noShrinking,
         noOptimization,
         noObfuscation,
-        reasonAsked,
+        ImmutableList.copyOf(reasonAsked.values()),
         keepPackageName,
         checkDiscarded,
         alwaysInline,
@@ -463,22 +469,30 @@
       DexClass clazz,
       Collection<ProguardMemberRule> memberKeepRules,
       ProguardConfigurationRule rule,
-      Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
+      Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+      boolean includeLibraryClasses) {
     Set<Wrapper<DexMethod>> methodsMarked =
         options.forceProguardCompatibility ? null : new HashSet<>();
     DexClass startingClass = clazz;
     while (clazz != null) {
+      if (!includeLibraryClasses && clazz.isLibraryClass()) {
+        return;
+      }
       // In compat mode traverse all direct methods in the hierarchy.
       if (clazz == startingClass || options.forceProguardCompatibility) {
-        Arrays.stream(clazz.directMethods()).forEach(method -> {
-          DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
-          markMethod(method, memberKeepRules, methodsMarked, rule, precondition);
-        });
+        Arrays.stream(clazz.directMethods())
+            .forEach(
+                method -> {
+                  DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
+                  markMethod(method, memberKeepRules, methodsMarked, rule, precondition);
+                });
       }
-      Arrays.stream(clazz.virtualMethods()).forEach(method -> {
-        DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
-        markMethod(method, memberKeepRules, methodsMarked, rule, precondition);
-      });
+      Arrays.stream(clazz.virtualMethods())
+          .forEach(
+              method -> {
+                DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
+                markMethod(method, memberKeepRules, methodsMarked, rule, precondition);
+              });
       clazz = clazz.superType == null ? null : application.definitionFor(clazz.superType);
     }
   }
@@ -488,18 +502,23 @@
       Collection<ProguardMemberRule> memberKeepRules,
       ProguardConfigurationRule rule,
       Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
-    clazz.forEachMethod(method -> {
-      DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
-      markMethod(method, memberKeepRules, null, rule, precondition);
-    });
+    clazz.forEachMethod(
+        method -> {
+          DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
+          markMethod(method, memberKeepRules, null, rule, precondition);
+        });
   }
 
   private void markMatchingVisibleFields(
       DexClass clazz,
       Collection<ProguardMemberRule> memberKeepRules,
       ProguardConfigurationRule rule,
-      Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
+      Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+      boolean includeLibraryClasses) {
     while (clazz != null) {
+      if (!includeLibraryClasses && clazz.isLibraryClass()) {
+        return;
+      }
       clazz.forEachField(
           field -> {
             DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
@@ -514,10 +533,11 @@
       Collection<ProguardMemberRule> memberKeepRules,
       ProguardConfigurationRule rule,
       Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
-    clazz.forEachField(field -> {
-      DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
-      markField(field, memberKeepRules, rule, precondition);
-    });
+    clazz.forEachField(
+        field -> {
+          DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
+          markField(field, memberKeepRules, rule, precondition);
+        });
   }
 
   // TODO(b/67934426): Test this code.
@@ -855,6 +875,11 @@
       ProguardMemberRule rule,
       DexDefinition precondition) {
     if (context instanceof ProguardKeepRule) {
+      if (item.isDexEncodedMethod() && item.asDexEncodedMethod().accessFlags.isSynthetic()) {
+        // Don't keep synthetic methods (see b/120971047 for additional details).
+        return;
+      }
+
       ProguardKeepRule keepRule = (ProguardKeepRule) context;
       ProguardKeepRuleModifiers modifiers = keepRule.getModifiers();
       if (!modifiers.allowsShrinking) {
@@ -877,7 +902,7 @@
     } else if (context instanceof ProguardAssumeNoSideEffectRule) {
       noSideEffects.put(item, rule);
     } else if (context instanceof ProguardWhyAreYouKeepingRule) {
-      reasonAsked.add(item);
+      reasonAsked.computeIfAbsent(item, i -> i);
     } else if (context instanceof ProguardKeepPackageNamesRule) {
       keepPackageName.add(item);
     } else if (context instanceof ProguardAssumeValuesRule) {
@@ -934,7 +959,7 @@
     public final Map<DexDefinition, ProguardKeepRule> noShrinking;
     public final Set<DexDefinition> noOptimization;
     public final Set<DexDefinition> noObfuscation;
-    public final Set<DexDefinition> reasonAsked;
+    public final ImmutableList<DexDefinition> reasonAsked;
     public final Set<DexDefinition> keepPackageName;
     public final Set<DexDefinition> checkDiscarded;
     public final Set<DexMethod> alwaysInline;
@@ -952,7 +977,7 @@
         Map<DexDefinition, ProguardKeepRule> noShrinking,
         Set<DexDefinition> noOptimization,
         Set<DexDefinition> noObfuscation,
-        Set<DexDefinition> reasonAsked,
+        ImmutableList<DexDefinition> reasonAsked,
         Set<DexDefinition> keepPackageName,
         Set<DexDefinition> checkDiscarded,
         Set<DexMethod> alwaysInline,
@@ -968,7 +993,7 @@
       this.noShrinking = Collections.unmodifiableMap(noShrinking);
       this.noOptimization = noOptimization;
       this.noObfuscation = noObfuscation;
-      this.reasonAsked = Collections.unmodifiableSet(reasonAsked);
+      this.reasonAsked = reasonAsked;
       this.keepPackageName = Collections.unmodifiableSet(keepPackageName);
       this.checkDiscarded = Collections.unmodifiableSet(checkDiscarded);
       this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
@@ -986,10 +1011,11 @@
     // Add dependent items that depend on -if rules.
     void addDependentItems(
         Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentItems) {
-      dependentItems.forEach((def, dependence) -> {
-        dependentNoShrinking.computeIfAbsent(def, x -> new IdentityHashMap<>())
-            .putAll(dependence);
-      });
+      dependentItems.forEach(
+          (def, dependence) ->
+              dependentNoShrinking
+                  .computeIfAbsent(def, x -> new IdentityHashMap<>())
+                  .putAll(dependence));
     }
 
     Map<DexDefinition, ProguardKeepRule> getDependentItems(DexDefinition item) {
@@ -997,6 +1023,156 @@
           .unmodifiableMap(dependentNoShrinking.getOrDefault(item, Collections.emptyMap()));
     }
 
+    public boolean verifyKeptFieldsAreAccessedAndLive(AppInfoWithLiveness appInfo) {
+      for (DexDefinition definition : noShrinking.keySet()) {
+        if (definition.isDexEncodedField()) {
+          DexEncodedField field = definition.asDexEncodedField();
+          if (field.isStatic() || isKeptDirectlyOrIndirectly(field.field.clazz, appInfo)) {
+            // TODO(b/121354886): Enable asserts for reads and writes.
+            /*assert appInfo.fieldsRead.contains(field.field)
+                : "Expected kept field `" + field.field.toSourceString() + "` to be read";
+            assert appInfo.fieldsWritten.contains(field.field)
+                : "Expected kept field `" + field.field.toSourceString() + "` to be written";*/
+            assert appInfo.liveFields.contains(field.field)
+                : "Expected kept field `" + field.field.toSourceString() + "` to be live";
+          }
+        }
+      }
+      return true;
+    }
+
+    public boolean verifyKeptMethodsAreTargetedAndLive(AppInfoWithLiveness appInfo) {
+      for (DexDefinition definition : noShrinking.keySet()) {
+        if (definition.isDexEncodedMethod()) {
+          DexEncodedMethod method = definition.asDexEncodedMethod();
+          assert appInfo.targetedMethods.contains(method.method)
+              : "Expected kept method `" + method.method.toSourceString() + "` to be targeted";
+
+          if (!method.accessFlags.isAbstract()
+              && isKeptDirectlyOrIndirectly(method.method.holder, appInfo)) {
+            assert appInfo.liveMethods.contains(method.method)
+                : "Expected non-abstract kept method `"
+                    + method.method.toSourceString()
+                    + "` to be live";
+          }
+        }
+      }
+      return true;
+    }
+
+    public boolean verifyKeptTypesAreLive(AppInfoWithLiveness appInfo) {
+      for (DexDefinition definition : noShrinking.keySet()) {
+        if (definition.isDexClass()) {
+          DexClass clazz = definition.asDexClass();
+          assert appInfo.liveTypes.contains(clazz.type)
+              : "Expected kept type `" + clazz.type.toSourceString() + "` to be live";
+        }
+      }
+      return true;
+    }
+
+    private boolean isKeptDirectlyOrIndirectly(DexType type, AppInfoWithLiveness appInfo) {
+      DexClass clazz = appInfo.definitionFor(type);
+      if (clazz == null) {
+        return false;
+      }
+      if (noShrinking.containsKey(clazz)) {
+        return true;
+      }
+      if (clazz.superType != null) {
+        return isKeptDirectlyOrIndirectly(clazz.superType, appInfo);
+      }
+      return false;
+    }
+
+    public boolean verifyKeptItemsAreKept(
+        DexApplication application, AppInfo appInfo, InternalOptions options) {
+      if (options.getProguardConfiguration().hasApplyMappingFile()) {
+        // TODO(b/121295633): Root set is obsolete after mapping has been applied.
+        return true;
+      }
+
+      boolean isInterfaceMethodDesugaringEnabled =
+          options.enableDesugaring
+              && options.interfaceMethodDesugaring == OffOrAuto.Auto
+              && !options.canUseDefaultAndStaticInterfaceMethods();
+
+      Set<DexReference> pinnedItems =
+          appInfo.hasLiveness() ? appInfo.withLiveness().pinnedItems : null;
+
+      // Create a mapping from each required type to the set of required members on that type.
+      Map<DexType, Set<DexDefinition>> requiredDefinitionsPerType = new IdentityHashMap<>();
+      for (DexDefinition definition : noShrinking.keySet()) {
+        // Check that `pinnedItems` is a super set of the root set.
+        assert pinnedItems == null || pinnedItems.contains(definition.toReference());
+        if (definition.isDexClass()) {
+          DexType type = definition.toReference().asDexType();
+          requiredDefinitionsPerType.putIfAbsent(type, Sets.newIdentityHashSet());
+        } else {
+          assert definition.isDexEncodedField() || definition.isDexEncodedMethod();
+          Descriptor<?, ?> descriptor = definition.toReference().asDescriptor();
+          requiredDefinitionsPerType
+              .computeIfAbsent(descriptor.getHolder(), key -> Sets.newIdentityHashSet())
+              .add(definition);
+        }
+      }
+
+      // Run through each class in the program and check that it has members it must have.
+      for (DexProgramClass clazz : application.classes()) {
+        Set<DexDefinition> requiredDefinitions =
+            requiredDefinitionsPerType.getOrDefault(clazz.type, ImmutableSet.of());
+
+        Set<DexField> fields = null;
+        Set<DexMethod> methods = null;
+
+        for (DexDefinition requiredDefinition : requiredDefinitions) {
+          if (requiredDefinition.isDexEncodedField()) {
+            DexEncodedField requiredField = requiredDefinition.asDexEncodedField();
+            if (fields == null) {
+              // Create a Set of the fields to avoid quadratic behavior.
+              fields =
+                  Streams.stream(clazz.fields())
+                      .map(DexEncodedField::getKey)
+                      .collect(Collectors.toSet());
+            }
+            assert fields.contains(requiredField.field);
+          } else if (requiredDefinition.isDexEncodedMethod()) {
+            DexEncodedMethod requiredMethod = requiredDefinition.asDexEncodedMethod();
+            if (isInterfaceMethodDesugaringEnabled) {
+              if (clazz.isInterface() && requiredMethod.hasCode()) {
+                // TODO(b/121240523): Ideally, these default interface methods should not be in the
+                // root set.
+                continue;
+              }
+            }
+            if (methods == null) {
+              // Create a Set of the methods to avoid quadratic behavior.
+              methods =
+                  Streams.stream(clazz.methods())
+                      .map(DexEncodedMethod::getKey)
+                      .collect(Collectors.toSet());
+            }
+            assert methods.contains(requiredMethod.method);
+          } else {
+            assert false;
+          }
+        }
+        requiredDefinitionsPerType.remove(clazz.type);
+      }
+
+      // If the map is non-empty, then a type in the root set was not in the application.
+      if (!requiredDefinitionsPerType.isEmpty()) {
+        DexType type = requiredDefinitionsPerType.keySet().iterator().next();
+        DexClass clazz = application.definitionFor(type);
+        assert clazz == null || clazz.isProgramClass()
+            : "Unexpected library type in root set: `" + type + "`";
+        assert requiredDefinitionsPerType.isEmpty()
+            : "Expected type `" + type.toSourceString() + "` to be present";
+      }
+
+      return true;
+    }
+
     @Override
     public String toString() {
       StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index d1e0606..a51116b 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.shaking;
 
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -48,10 +49,99 @@
  * of X into Y -- it does not change all occurrences of X in the program into Y. This makes the
  * optimization more applicable, because it would otherwise not be possible to merge two classes if
  * they inherited from, say, X' and Y' (since multiple inheritance is not allowed).
+ *
+ * <p>When there is a main dex specification, merging will only happen within the three groups of
+ * classes found from main dex tracing ("main dex roots", "main dex dependencies" and
+ * "non main dex classes"), and not between them. This ensures that the size of the main dex
+ * will not grow out of proportions due to non main dex classes getting merged into main dex
+ * classes.
  */
 public class StaticClassMerger {
 
-  private static final String GLOBAL = "<global>";
+  enum MergeGroup {
+    MAIN_DEX_ROOTS,
+    MAIN_DEX_DEPENDENCIES,
+    NOT_MAIN_DEX,
+    DONT_MERGE;
+
+    private static final String GLOBAL = "<global>";
+    private static Key mainDexRootsGlobalKey = new Key(MergeGroup.MAIN_DEX_ROOTS, GLOBAL);
+    private static Key mainDexDependenciesGlobalKey =
+        new Key(MergeGroup.MAIN_DEX_DEPENDENCIES, GLOBAL);
+    private static Key notMainDexGlobalKey = new Key(MergeGroup.NOT_MAIN_DEX, GLOBAL);
+
+    private static class Key {
+      private final MergeGroup mergeGroup;
+      private final String packageOrGlobal;
+
+      public Key(MergeGroup mergeGroup, String packageOrGlobal) {
+        this.mergeGroup = mergeGroup;
+        this.packageOrGlobal = packageOrGlobal;
+      }
+
+      public MergeGroup getMergeGroup() {
+        return mergeGroup;
+      }
+
+      public String getPackageOrGlobal() {
+        return packageOrGlobal;
+      }
+
+      public boolean isGlobal() {
+        return packageOrGlobal.equals(GLOBAL);
+      }
+
+      @Override
+      public int hashCode() {
+        return mergeGroup.ordinal() * 13 + packageOrGlobal.hashCode();
+      }
+
+      @Override
+      public boolean equals(Object other) {
+        if (other == this) {
+          return true;
+        }
+        if (other == null || this.getClass() != other.getClass()) {
+          return false;
+        }
+        Key o = (Key) other;
+        return o.mergeGroup == mergeGroup && o.packageOrGlobal.equals(packageOrGlobal);
+      }
+    }
+
+    public Key globalKey() {
+      switch (this) {
+        case NOT_MAIN_DEX:
+          return notMainDexGlobalKey;
+        case MAIN_DEX_ROOTS:
+          return mainDexRootsGlobalKey;
+        case MAIN_DEX_DEPENDENCIES:
+          return mainDexDependenciesGlobalKey;
+        default:
+          throw new Unreachable("Unexpected MergeGroup value");
+      }
+    }
+
+    public Key key(String pkg) {
+      assert this != DONT_MERGE;
+      return new Key(this, pkg);
+    }
+
+    @Override
+    public String toString() {
+      switch (this) {
+        case NOT_MAIN_DEX:
+          return "outside main dex";
+        case MAIN_DEX_ROOTS:
+          return "main dex roots";
+        case MAIN_DEX_DEPENDENCIES:
+          return "main dex dependencies";
+        default:
+          assert this == DONT_MERGE;
+          return "don't merge";
+      }
+    }
+  }
 
   // There are 52 characters in [a-zA-Z], so with a capacity just below 52 the minifier should be
   // able to find single-character names for all members, but around 30 appears to work better in
@@ -101,12 +191,13 @@
   }
 
   private final AppView<? extends AppInfoWithLiveness> appView;
+  private final MainDexClasses mainDexClasses;
 
   /** The equivalence that should be used for the member buckets in {@link Representative}. */
   private final Equivalence<DexField> fieldEquivalence;
   private final Equivalence<DexMethod> methodEquivalence;
 
-  private final Map<String, Representative> representatives = new HashMap<>();
+  private final Map<MergeGroup.Key, Representative> representatives = new HashMap<>();
 
   private final BiMap<DexField, DexField> fieldMapping = HashBiMap.create();
   private final BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
@@ -114,21 +205,26 @@
   private int numberOfMergedClasses = 0;
 
   public StaticClassMerger(
-      AppView<? extends AppInfoWithLiveness> appView, InternalOptions options) {
+      AppView<? extends AppInfoWithLiveness> appView,
+      InternalOptions options,
+      MainDexClasses mainDexClasses) {
     this.appView = appView;
-    if (options.proguardConfiguration.isOverloadAggressivelyWithoutUseUniqueClassMemberNames()) {
+    if (options
+        .getProguardConfiguration().isOverloadAggressivelyWithoutUseUniqueClassMemberNames()) {
       fieldEquivalence = FieldSignatureEquivalence.getEquivalenceIgnoreName();
       methodEquivalence = MethodSignatureEquivalence.getEquivalenceIgnoreName();
     } else {
       fieldEquivalence = new SingletonEquivalence<>();
       methodEquivalence = MethodJavaSignatureEquivalence.getEquivalenceIgnoreName();
     }
+    this.mainDexClasses = mainDexClasses;
   }
 
   public GraphLense run() {
     for (DexProgramClass clazz : appView.appInfo().app.classesWithDeterministicOrder()) {
-      if (satisfiesMergeCriteria(clazz)) {
-        merge(clazz);
+      MergeGroup group = satisfiesMergeCriteria(clazz);
+      if (group != MergeGroup.DONT_MERGE) {
+        merge(clazz, group);
       }
     }
     if (Log.ENABLED) {
@@ -157,26 +253,26 @@
     return appView.graphLense();
   }
 
-  private boolean satisfiesMergeCriteria(DexProgramClass clazz) {
+  private MergeGroup satisfiesMergeCriteria(DexProgramClass clazz) {
     if (appView.appInfo().neverMerge.contains(clazz.type)) {
-      return false;
+      return MergeGroup.DONT_MERGE;
     }
     if (clazz.staticFields().length + clazz.directMethods().length + clazz.virtualMethods().length
         == 0) {
-      return false;
+      return MergeGroup.DONT_MERGE;
     }
     if (clazz.instanceFields().length > 0) {
-      return false;
+      return MergeGroup.DONT_MERGE;
     }
     if (Arrays.stream(clazz.staticFields())
         .anyMatch(field -> appView.appInfo().isPinned(field.field))) {
-      return false;
+      return MergeGroup.DONT_MERGE;
     }
     if (Arrays.stream(clazz.directMethods()).anyMatch(DexEncodedMethod::isInitializer)) {
-      return false;
+      return MergeGroup.DONT_MERGE;
     }
     if (!Arrays.stream(clazz.virtualMethods()).allMatch(DexEncodedMethod::isPrivateMethod)) {
-      return false;
+      return MergeGroup.DONT_MERGE;
     }
     if (Streams.stream(clazz.methods())
         .anyMatch(
@@ -187,7 +283,7 @@
                     // modify any methods from the sets alwaysInline and noSideEffects.
                     || appView.appInfo().alwaysInline.contains(method.method)
                     || appView.appInfo().noSideEffects.keySet().contains(method))) {
-      return false;
+      return MergeGroup.DONT_MERGE;
     }
     if (clazz.classInitializationMayHaveSideEffects(appView.appInfo())) {
       // This could have a negative impact on inlining.
@@ -196,33 +292,41 @@
       // for further details.
       //
       // Note that this will be true for all classes that inherit from or implement a library class.
-      return false;
+      return MergeGroup.DONT_MERGE;
     }
-    return true;
+    if (!mainDexClasses.isEmpty()) {
+      if (mainDexClasses.getRoots().contains(clazz.type)) {
+        return MergeGroup.MAIN_DEX_ROOTS;
+      }
+      if (mainDexClasses.getDependencies().contains(clazz.type)) {
+        return MergeGroup.MAIN_DEX_DEPENDENCIES;
+      }
+    }
+    return MergeGroup.NOT_MAIN_DEX;
   }
 
   private boolean isValidRepresentative(DexProgramClass clazz) {
     // Disallow interfaces from being representatives, since interface methods require desugaring.
     return !clazz.isInterface();
   }
-
-  private boolean merge(DexProgramClass clazz) {
-    assert satisfiesMergeCriteria(clazz);
+  private boolean merge(DexProgramClass clazz, MergeGroup group) {
+    assert satisfiesMergeCriteria(clazz) == group;
+    assert group != MergeGroup.DONT_MERGE;
 
     String pkg = clazz.type.getPackageDescriptor();
     return mayMergeAcrossPackageBoundaries(clazz)
-        ? mergeGlobally(clazz, pkg)
-        : mergeInsidePackage(clazz, pkg);
+        ? mergeGlobally(clazz, pkg, group)
+        : mergeInsidePackage(clazz, pkg, group);
   }
 
-  private boolean mergeGlobally(DexProgramClass clazz, String pkg) {
-    Representative globalRepresentative = representatives.get(GLOBAL);
+  private boolean mergeGlobally(DexProgramClass clazz, String pkg, MergeGroup group) {
+    Representative globalRepresentative = representatives.get(group.globalKey());
     if (globalRepresentative == null) {
       if (isValidRepresentative(clazz)) {
         // Make the current class the global representative.
-        setRepresentative(GLOBAL, getOrCreateRepresentative(clazz, pkg));
+        setRepresentative(group.globalKey(), getOrCreateRepresentative(group.key(pkg), clazz));
       } else {
-        clearRepresentative(GLOBAL);
+        clearRepresentative(group.globalKey());
       }
 
       // Do not attempt to merge this class inside its own package, because that could lead to
@@ -235,9 +339,9 @@
       if (globalRepresentative.isFull()) {
         if (isValidRepresentative(clazz)) {
           // Make the current class the global representative instead.
-          setRepresentative(GLOBAL, getOrCreateRepresentative(clazz, pkg));
+          setRepresentative(group.globalKey(), getOrCreateRepresentative(group.key(pkg), clazz));
         } else {
-          clearRepresentative(GLOBAL);
+          clearRepresentative(group.globalKey());
         }
 
         // Do not attempt to merge this class inside its own package, because that could lead to
@@ -251,17 +355,18 @@
     }
   }
 
-  private boolean mergeInsidePackage(DexProgramClass clazz, String pkg) {
-    Representative packageRepresentative = representatives.get(pkg);
+  private boolean mergeInsidePackage(DexProgramClass clazz, String pkg, MergeGroup group) {
+    MergeGroup.Key key = group.key(pkg);
+    Representative packageRepresentative = representatives.get(key);
     if (packageRepresentative != null) {
       if (isValidRepresentative(clazz)
           && clazz.accessFlags.isMoreVisibleThan(packageRepresentative.clazz.accessFlags)) {
         // Use `clazz` as a representative for this package instead.
-        Representative newRepresentative = getOrCreateRepresentative(clazz, pkg);
+        Representative newRepresentative = getOrCreateRepresentative(key, clazz);
         newRepresentative.include(packageRepresentative.clazz);
 
         if (!newRepresentative.isFull()) {
-          setRepresentative(pkg, newRepresentative);
+          setRepresentative(group.key(pkg), newRepresentative);
           moveMembersFromSourceToTarget(packageRepresentative.clazz, clazz);
           return true;
         }
@@ -283,51 +388,54 @@
 
     // We were unable to use the current representative for this package (if any).
     if (isValidRepresentative(clazz)) {
-      setRepresentative(pkg, getOrCreateRepresentative(clazz, pkg));
+      setRepresentative(key, getOrCreateRepresentative(key, clazz));
     }
     return false;
   }
 
-  private Representative getOrCreateRepresentative(DexProgramClass clazz, String pkg) {
-    Representative globalRepresentative = representatives.get(GLOBAL);
+  private Representative getOrCreateRepresentative(MergeGroup.Key key, DexProgramClass clazz) {
+    Representative globalRepresentative = representatives.get(key.getMergeGroup().globalKey());
     if (globalRepresentative != null && globalRepresentative.clazz == clazz) {
       return globalRepresentative;
     }
-    Representative packageRepresentative = representatives.get(pkg);
+    Representative packageRepresentative = representatives.get(key);
     if (packageRepresentative != null && packageRepresentative.clazz == clazz) {
       return packageRepresentative;
     }
     return new Representative(clazz);
   }
 
-  private void setRepresentative(String pkg, Representative representative) {
+  private void setRepresentative(MergeGroup.Key key, Representative representative) {
     assert isValidRepresentative(representative.clazz);
     if (Log.ENABLED) {
-      if (pkg.equals(GLOBAL)) {
+      if (key.isGlobal()) {
         Log.info(
             getClass(),
-            "Making %s the global representative",
-            representative.clazz.type.toSourceString());
+            "Making %s the global representative in group %s",
+            representative.clazz.type.toSourceString(),
+            key.getMergeGroup().toString());
       } else {
         Log.info(
             getClass(),
-            "Making %s the representative for package %s",
+            "Making %s the representative for package %s in group %s",
             representative.clazz.type.toSourceString(),
-            pkg);
+            key.getPackageOrGlobal(),
+            key.getMergeGroup().toString());
       }
     }
-    representatives.put(pkg, representative);
+    representatives.put(key, representative);
   }
 
-  private void clearRepresentative(String pkg) {
+  private void clearRepresentative(MergeGroup.Key key) {
     if (Log.ENABLED) {
-      if (pkg.equals(GLOBAL)) {
+      if (key.isGlobal()) {
         Log.info(getClass(), "Removing the global representative");
       } else {
-        Log.info(getClass(), "Removing the representative for package %s", pkg);
+        Log.info(
+            getClass(), "Removing the representative for package %s", key.getPackageOrGlobal());
       }
     }
-    representatives.remove(pkg);
+    representatives.remove(key);
   }
 
   private boolean mayMergeAcrossPackageBoundaries(DexProgramClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 1d57ddf..6cf0da0 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -8,8 +8,10 @@
 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.DexType;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.KeyedDexItem;
 import com.android.tools.r8.graph.PresortedComparable;
@@ -38,7 +40,9 @@
     this.application = application;
     this.appInfo = appInfo;
     this.options = options;
-    this.usagePrinter = options.proguardConfiguration.isPrintUsage()
+    this.usagePrinter =
+        options.getProguardConfiguration() != null
+            && options.getProguardConfiguration().isPrintUsage()
         ? new UsagePrinter() : UsagePrinter.DONT_PRINT;
   }
 
@@ -100,15 +104,44 @@
         clazz.setInstanceFields(reachableFields(clazz.instanceFields()));
         clazz.setStaticFields(reachableFields(clazz.staticFields()));
         clazz.removeInnerClasses(this::isAttributeReferencingPrunedType);
+        clazz.removeEnclosingMethod(this::isAttributeReferencingPrunedItem);
         usagePrinter.visited();
       }
     }
     return newClasses;
   }
 
+  private boolean isAttributeReferencingPrunedItem(EnclosingMethodAttribute attr) {
+    return
+        (attr.getEnclosingClass() != null
+            && !appInfo.liveTypes.contains(attr.getEnclosingClass()))
+        || (attr.getEnclosingMethod() != null
+            && !appInfo.liveMethods.contains(attr.getEnclosingMethod()));
+  }
+
   private boolean isAttributeReferencingPrunedType(InnerClassAttribute attr) {
-    return (attr.getInner() != null && !appInfo.liveTypes.contains(attr.getInner()))
-        || (attr.getOuter() != null && !appInfo.liveTypes.contains(attr.getOuter()));
+    if (!appInfo.liveTypes.contains(attr.getInner())) {
+      return true;
+    }
+    DexType context = attr.getOuter();
+    if (context == null) {
+      DexClass inner = appInfo.definitionFor(attr.getInner());
+      if (inner != null && inner.getEnclosingMethod() != null) {
+        EnclosingMethodAttribute enclosingMethodAttribute = inner.getEnclosingMethod();
+        if (enclosingMethodAttribute.getEnclosingClass() != null) {
+          context = enclosingMethodAttribute.getEnclosingClass();
+        } else {
+          DexMethod enclosingMethod = enclosingMethodAttribute.getEnclosingMethod();
+          if (!appInfo.liveMethods.contains(enclosingMethod)) {
+            // EnclosingMethodAttribute will be pruned as it references the pruned method.
+            // Hence, removal of the current InnerClassAttribute too.
+            return true;
+          }
+          context = enclosingMethod.getHolder();
+        }
+      }
+    }
+    return context == null || !appInfo.liveTypes.contains(context);
   }
 
   private <S extends PresortedComparable<S>, T extends KeyedDexItem<S>> int firstUnreachableIndex(
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 77e0b40..45ca1af 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -225,12 +225,15 @@
   // All the bridge methods that have been synthesized during vertical class merging.
   private final List<SynthesizedBridgeCode> synthesizedBridges = new ArrayList<>();
 
+  private final MainDexClasses mainDexClasses;
+
   public VerticalClassMerger(
       DexApplication application,
       AppView<AppInfoWithLiveness> appView,
       ExecutorService executorService,
       InternalOptions options,
-      Timing timing) {
+      Timing timing,
+      MainDexClasses mainDexClasses) {
     this.application = application;
     this.appInfo = appView.appInfo();
     this.appView = appView;
@@ -240,6 +243,7 @@
     this.options = options;
     this.renamedMembersLense = new VerticalClassMergerGraphLense.Builder();
     this.timing = timing;
+    this.mainDexClasses = mainDexClasses;
 
     Iterable<DexProgramClass> classes = application.classesWithDeterministicOrder();
     initializePinnedTypes(classes); // Must be initialized prior to mergeCandidates.
@@ -604,28 +608,67 @@
     timing.begin("fixup");
     GraphLense result = new TreeFixer().fixupTypeReferences(mergingGraphLense);
     timing.end();
-    assert result.assertDefinitionNotModified(
-        appInfo
-            .alwaysInline
-            .stream()
+    assert result.assertDefinitionsNotModified(
+        appInfo.alwaysInline.stream()
             .map(appInfo::definitionFor)
             .filter(Objects::nonNull)
             .collect(Collectors.toList()));
-    assert result.assertDefinitionNotModified(appInfo.noSideEffects.keySet());
-    // TODO(christofferqa): Enable this assert.
-    // assert result.assertNotModified(appInfo.pinnedItems);
-    assert verifyGraphLense(graphLense);
+    assert verifyGraphLense(result);
     return result;
   }
 
   private boolean verifyGraphLense(GraphLense graphLense) {
+    assert graphLense.assertDefinitionsNotModified(appInfo.noSideEffects.keySet());
+
+    // Note that the method assertReferencesNotModified() relies on getRenamedFieldSignature() and
+    // getRenamedMethodSignature() instead of lookupField() and lookupMethod(). This is important
+    // for this check to succeed, since it is not guaranteed that calling lookupMethod() with a
+    // pinned method will return the method itself.
+    //
+    // Consider the following example.
+    //
+    //   class A {
+    //     public void method() {}
+    //   }
+    //   class B extends A {
+    //     @Override
+    //     public void method() {}
+    //   }
+    //   class C extends B {
+    //     @Override
+    //     public void method() {}
+    //   }
+    //
+    // If A.method() is pinned, then A cannot be merged into B, but B can still be merged into C.
+    // Now, if there is an invoke-super instruction in C that hits B.method(), then this needs to
+    // be rewritten into an invoke-direct instruction. In particular, there could be an instruction
+    // `invoke-super A.method` in C. This would hit B.method(). Therefore, the graph lens records
+    // that `invoke-super A.method` instructions, which are in one of the methods from C, needs to
+    // be rewritten to `invoke-direct C.method$B`. This is valid even though A.method() is actually
+    // pinned, because this rewriting does not affect A.method() in any way.
+    assert graphLense.assertReferencesNotModified(appInfo.pinnedItems);
+
     for (DexProgramClass clazz : appInfo.classes()) {
       for (DexEncodedMethod encodedMethod : clazz.methods()) {
         DexMethod method = encodedMethod.method;
         DexMethod originalMethod = graphLense.getOriginalMethodSignature(method);
+        DexMethod renamedMethod = graphLense.getRenamedMethodSignature(originalMethod);
 
-        // Must be able to map back.
-        assert method == graphLense.getRenamedMethodSignature(originalMethod);
+        // Must be able to map back and forth.
+        if (encodedMethod.hasCode() && encodedMethod.getCode() instanceof SynthesizedBridgeCode) {
+          // For virtual methods, the vertical class merger creates two methods in the sub class
+          // in order to deal with invoke-super instructions (one that is private and one that is
+          // virtual). Therefore, it is not possible to go back and forth. Instead, we check that
+          // the two methods map back to the same original method, and that the original method
+          // can be mapped to the implementation method.
+          DexMethod implementationMethod =
+              ((SynthesizedBridgeCode) encodedMethod.getCode()).invocationTarget;
+          DexMethod originalImplementationMethod = graphLense.getOriginalMethodSignature(method);
+          assert originalMethod == originalImplementationMethod;
+          assert implementationMethod == renamedMethod;
+        } else {
+          assert method == renamedMethod;
+        }
 
         // Verify that all types are up-to-date. After vertical class merging, there should be no
         // more references to types that have been merged into another type.
@@ -699,6 +742,10 @@
     return false;
   }
 
+  private boolean hasReferencesOutside(DexProgramClass clazz, Set<DexType> types) {
+    return MainDexDirectReferenceTracer.hasReferencesOutside(appInfo, clazz, types);
+  }
+
   private void mergeClassIfPossible(DexProgramClass clazz) {
     if (!mergeCandidates.contains(clazz)) {
       return;
@@ -730,6 +777,23 @@
       return;
     }
 
+    // For a main dex class in the dependent set only merge with other classes in either main dex
+    // set.
+    if ((mainDexClasses.getDependencies().contains(clazz.type)
+        || mainDexClasses.getDependencies().contains(targetClass.type))
+        && !(mainDexClasses.getClasses().contains(clazz.type)
+        && mainDexClasses.getClasses().contains(targetClass.type))) {
+      return;
+    }
+
+    // For a main dex class in the root set only merge with other classes in main dex root set.
+    if ((mainDexClasses.getRoots().contains(clazz.type)
+        || mainDexClasses.getRoots().contains(targetClass.type))
+        && !(mainDexClasses.getRoots().contains(clazz.type)
+        && mainDexClasses.getRoots().contains(targetClass.type))) {
+      return;
+    }
+
     ClassMerger merger = new ClassMerger(clazz, targetClass);
     boolean merged;
     try {
@@ -1594,7 +1658,7 @@
         ConstraintWithTarget constraint =
             jarCode.computeInliningConstraint(
                 method,
-                appInfo,
+                appView,
                 new SingleTypeMapperGraphLense(method.method.holder, invocationContext),
                 invocationContext);
         return constraint == ConstraintWithTarget.NEVER;
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index 6b66465..20a05ab 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -222,16 +222,32 @@
         Map<DexType, DexType> mergedClasses,
         DexItemFactory dexItemFactory,
         Map<DexProto, DexProto> cache) {
+      assert !signature.holder.isArrayType();
       DexType newHolder = mergedClasses.getOrDefault(signature.holder, signature.holder);
       DexProto newProto =
           dexItemFactory.applyClassMappingToProto(
-              signature.proto, type -> mergedClasses.getOrDefault(type, type), cache);
+              signature.proto,
+              type -> getTypeAfterClassMerging(type, mergedClasses, dexItemFactory),
+              cache);
       if (signature.holder.equals(newHolder) && signature.proto.equals(newProto)) {
         return signature;
       }
       return dexItemFactory.createMethod(newHolder, newProto, signature.name);
     }
 
+    private static DexType getTypeAfterClassMerging(
+        DexType type, Map<DexType, DexType> mergedClasses, DexItemFactory dexItemFactory) {
+      if (type.isArrayType()) {
+        DexType baseType = type.toBaseType(dexItemFactory);
+        DexType newBaseType = mergedClasses.getOrDefault(baseType, baseType);
+        if (newBaseType != baseType) {
+          return type.replaceBaseType(newBaseType, dexItemFactory);
+        }
+        return type;
+      }
+      return mergedClasses.getOrDefault(type, type);
+    }
+
     public boolean hasMappingForSignatureInContext(DexType context, DexMethod signature) {
       Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap =
           contextualVirtualToDirectMethodMaps.get(context);
diff --git a/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
index e2fc31e..0572e88 100644
--- a/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
@@ -4,36 +4,43 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graphinfo.ClassGraphNode;
-import com.android.tools.r8.graphinfo.FieldGraphNode;
-import com.android.tools.r8.graphinfo.GraphConsumer;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo.EdgeKind;
-import com.android.tools.r8.graphinfo.GraphNode;
-import com.android.tools.r8.graphinfo.KeepRuleGraphNode;
-import com.android.tools.r8.graphinfo.MethodGraphNode;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
+import com.android.tools.r8.experimental.graphinfo.FieldGraphNode;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo.EdgeKind;
+import com.android.tools.r8.experimental.graphinfo.GraphNode;
+import com.android.tools.r8.experimental.graphinfo.KeepRuleGraphNode;
+import com.android.tools.r8.experimental.graphinfo.MethodGraphNode;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.position.TextPosition;
 import com.android.tools.r8.position.TextRange;
 import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
 import java.io.PrintStream;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Deque;
-import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-public class WhyAreYouKeepingConsumer implements GraphConsumer {
+/**
+ * Implementation of the whyareyoukeeping rule using an R8 kept-graph consumer.
+ *
+ * <p>This class is not intended for public use and clients should define their own consumer
+ * instead.
+ */
+public class WhyAreYouKeepingConsumer extends CollectingGraphConsumer {
 
   // Single-linked path description when BF searching for a path.
   private static class GraphPath {
@@ -47,37 +54,50 @@
     }
   }
 
-  // Possible sub-consumer that is also inspecting the kept-graph.
-  private final GraphConsumer subConsumer;
-
-  // Directional map backwards from targets to direct sources.
-  private final Map<GraphNode, Map<GraphNode, Set<GraphEdgeInfo>>> target2sources =
-      new IdentityHashMap<>();
-
   public WhyAreYouKeepingConsumer(GraphConsumer subConsumer) {
-    this.subConsumer = subConsumer;
+    super(subConsumer);
   }
 
-  @Override
-  public void acceptEdge(GraphNode source, GraphNode target, GraphEdgeInfo info) {
-    target2sources
-        .computeIfAbsent(target, k -> new IdentityHashMap<>())
-        .computeIfAbsent(source, k -> new HashSet<>())
-        .add(info);
-    if (subConsumer != null) {
-      subConsumer.acceptEdge(source, target, info);
-    }
-  }
-
-  /** Print the shortest path from a root to a node in the graph. */
-  public void printWhyAreYouKeeping(ClassReference clazz, PrintStream out) {
-    for (GraphNode node : target2sources.keySet()) {
-      if (node.identity().equals(clazz.toDescriptor())) {
-        printWhyAreYouKeeping(node, out);
-        return;
+  public ClassGraphNode getClassNode(ClassReference clazz) {
+    for (GraphNode node : getTargets()) {
+      if (node instanceof ClassGraphNode && ((ClassGraphNode) node).getReference() == clazz) {
+        return (ClassGraphNode) node;
       }
     }
-    printNothingKeeping(clazz, out);
+    return null;
+  }
+
+  public MethodGraphNode getMethodNode(MethodReference method) {
+    for (GraphNode node : getTargets()) {
+      if (node instanceof MethodGraphNode && ((MethodGraphNode) node).getReference() == method) {
+        return (MethodGraphNode) node;
+      }
+    }
+    return null;
+  }
+
+  public FieldGraphNode getFieldNode(FieldReference field) {
+    for (GraphNode node : getTargets()) {
+      if (node instanceof FieldGraphNode && ((FieldGraphNode) node).getReference() == field) {
+        return (FieldGraphNode) node;
+      }
+    }
+    return null;
+  }
+
+  public void printWhyAreYouKeeping(ClassReference reference, PrintStream out) {
+    ClassGraphNode node = getClassNode(reference);
+    printWhyAreYouKeeping(node != null ? node : new ClassGraphNode(false, reference), out);
+  }
+
+  public void printWhyAreYouKeeping(MethodReference reference, PrintStream out) {
+    MethodGraphNode node = getMethodNode(reference);
+    printWhyAreYouKeeping(node != null ? node : new MethodGraphNode(false, reference), out);
+  }
+
+  public void printWhyAreYouKeeping(FieldReference reference, PrintStream out) {
+    FieldGraphNode node = getFieldNode(reference);
+    printWhyAreYouKeeping(node != null ? node : new FieldGraphNode(false, reference), out);
   }
 
   public void printWhyAreYouKeeping(GraphNode node, PrintStream out) {
@@ -102,13 +122,16 @@
 
   private void printNothingKeeping(ClassReference clazz, PrintStream out) {
     out.print("Nothing is keeping ");
-    out.println(DescriptorUtils.descriptorToJavaType(clazz.toDescriptor()));
+    out.println(DescriptorUtils.descriptorToJavaType(clazz.getDescriptor()));
   }
 
-  private List<Pair<GraphNode, GraphEdgeInfo>> findShortestPathTo(final GraphNode node) {
+  public List<Pair<GraphNode, GraphEdgeInfo>> findShortestPathTo(final GraphNode node) {
+    if (node == null) {
+      return null;
+    }
     Deque<GraphPath> queue;
     {
-      Map<GraphNode, Set<GraphEdgeInfo>> sources = target2sources.get(node);
+      Map<GraphNode, Set<GraphEdgeInfo>> sources = getSourcesTargeting(node);
       if (sources == null) {
         // The node is not targeted at all (it is not reachable).
         return null;
@@ -121,7 +144,7 @@
     Map<GraphNode, GraphNode> seen = new IdentityHashMap<>();
     while (!queue.isEmpty()) {
       GraphPath path = queue.removeFirst();
-      Map<GraphNode, Set<GraphEdgeInfo>> sources = target2sources.get(path.node);
+      Map<GraphNode, Set<GraphEdgeInfo>> sources = getSourcesTargeting(path.node);
       if (sources == null) {
         return getCanonicalPath(path, node);
       }
@@ -144,11 +167,11 @@
     while (path.path != null) {
       GraphNode source = path.node;
       GraphNode target = path.path.node;
-      Set<GraphEdgeInfo> infos = target2sources.get(target).get(source);
+      Set<GraphEdgeInfo> infos = getSourcesTargeting(target).get(source);
       canonical.add(new Pair<>(source, getCanonicalInfo(infos)));
       path = path.path;
     }
-    Set<GraphEdgeInfo> infos = target2sources.get(endTarget).get(path.node);
+    Set<GraphEdgeInfo> infos = getSourcesTargeting(endTarget).get(path.node);
     canonical.add(new Pair<>(path.node, getCanonicalInfo(infos)));
     return canonical;
   }
@@ -201,27 +224,28 @@
 
   private String getNodeString(GraphNode node) {
     if (node instanceof ClassGraphNode) {
-      return DescriptorUtils.descriptorToJavaType(((ClassGraphNode) node).getDescriptor());
+      return DescriptorUtils.descriptorToJavaType(
+          ((ClassGraphNode) node).getReference().getDescriptor());
     }
     if (node instanceof MethodGraphNode) {
-      MethodGraphNode methodNode = (MethodGraphNode) node;
-      MethodSignature signature =
-          MethodSignature.fromSignature(
-              methodNode.getMethodName(), methodNode.getMethodDescriptor());
-      return signature.type
+      MethodReference method = ((MethodGraphNode) node).getReference();
+      return (method.getReturnType() == null ? "void" : method.getReturnType().getTypeName())
           + ' '
-          + DescriptorUtils.descriptorToJavaType(methodNode.getHolderDescriptor())
+          + method.getHolderClass().getTypeName()
           + '.'
-          + methodNode.getMethodName()
-          + StringUtils.join(Arrays.asList(signature.parameters), ",", BraceType.PARENS);
+          + method.getMethodName()
+          + StringUtils.join(
+              ListUtils.map(method.getFormalTypes(), TypeReference::getTypeName),
+              ",",
+              BraceType.PARENS);
     }
     if (node instanceof FieldGraphNode) {
-      FieldGraphNode fieldNode = (FieldGraphNode) node;
-      return DescriptorUtils.descriptorToJavaType(fieldNode.getFieldDescriptor())
+      FieldReference field = ((FieldGraphNode) node).getReference();
+      return field.getFieldType().getTypeName()
           + ' '
-          + DescriptorUtils.descriptorToJavaType(fieldNode.getHolderDescriptor())
+          + field.getHolderClass().getTypeName()
           + '.'
-          + fieldNode.getFieldName();
+          + field.getFieldName();
     }
     if (node instanceof KeepRuleGraphNode) {
       KeepRuleGraphNode keepRuleNode = (KeepRuleGraphNode) node;
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index 976768b..c059819 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -4,7 +4,10 @@
 package com.android.tools.r8.utils;
 
 import java.lang.reflect.Array;
+import java.util.ArrayList;
 import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Predicate;
 
 public class ArrayUtils {
 
@@ -33,4 +36,74 @@
     return results;
   }
 
+  /**
+   * Filters the input array based on the given predicate.
+   *
+   * @param clazz target type's Class to cast
+   * @param original an array of original elements
+   * @param filter a predicate that tells us what to keep
+   * @param <T> target type
+   * @return a partial copy of the original array
+   */
+  public static <T> T[] filter(Class<T[]> clazz, T[] original, Predicate<T> filter) {
+    ArrayList<T> filtered = null;
+    for (int i = 0; i < original.length; i++) {
+      T elt = original[i];
+      if (filter.test(elt)) {
+        if (filtered != null) {
+          filtered.add(elt);
+        }
+      } else {
+        if (filtered == null) {
+          filtered = new ArrayList<>(original.length);
+          for (int j = 0; j < i; j++) {
+            filtered.add(original[j]);
+          }
+        }
+      }
+    }
+    if (filtered == null) {
+      return original;
+    }
+    return filtered.toArray(
+        clazz.cast(Array.newInstance(clazz.getComponentType(), filtered.size())));
+  }
+
+  /**
+   * Rewrites the input array based on the given function.
+   *
+   * @param clazz target type's Class to cast
+   * @param original an array of original elements
+   * @param mapper a mapper that rewrites an original element to a new one, maybe `null`
+   * @param <T> target type
+   * @return an array with written elements
+   */
+  public static <T> T[] map(Class<T[]> clazz, T[] original, Function<T, T> mapper) {
+    ArrayList<T> results = null;
+    for (int i = 0; i < original.length; i++) {
+      T oldOne = original[i];
+      T newOne = mapper.apply(oldOne);
+      if (newOne == oldOne) {
+        if (results != null) {
+          results.add(oldOne);
+        }
+      } else {
+        if (results == null) {
+          results = new ArrayList<>(original.length);
+          for (int j = 0; j < i; j++) {
+            results.add(original[j]);
+          }
+        }
+        if (newOne != null) {
+          results.add(newOne);
+        }
+      }
+    }
+    if (results == null) {
+      return original;
+    }
+    return results.toArray(
+        clazz.cast(Array.newInstance(clazz.getComponentType(), results.size())));
+  }
+
 }
diff --git a/src/main/java/com/android/tools/r8/utils/ClassMap.java b/src/main/java/com/android/tools/r8/utils/ClassMap.java
index 0ebfb9d..33c1bf3 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassMap.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassMap.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.graph.ClassKind;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -134,6 +135,18 @@
     return loadedClasses;
   }
 
+  public Map<DexType, DexClass> getAllClassesInMap() {
+    if (classProvider.get() != null) {
+      throw new Unreachable("Getting all classes from not fully loaded collection.");
+    }
+    ImmutableMap.Builder<DexType, DexClass> builder = ImmutableMap.builder();
+    // This is fully loaded, so the class map will no longer change.
+    for (Map.Entry<DexType, Supplier<T>> entry : classes.entrySet()) {
+      builder.put(entry.getKey(), entry.getValue().get());
+    }
+    return builder.build();
+  }
+
   public Iterable<DexType> getAllTypes() {
     return classes.keySet();
   }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 8ae156e..90b9e18 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -14,11 +14,11 @@
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graphinfo.GraphConsumer;
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -47,7 +47,12 @@
   }
 
   public final DexItemFactory itemFactory;
-  public final ProguardConfiguration proguardConfiguration;
+
+  public ProguardConfiguration getProguardConfiguration() {
+    return proguardConfiguration;
+  }
+
+  private final ProguardConfiguration proguardConfiguration;
   public final Reporter reporter;
 
   // TODO(zerny): Make this private-final once we have full program-consumer support.
@@ -60,7 +65,7 @@
   public InternalOptions() {
     reporter = new Reporter();
     itemFactory = new DexItemFactory();
-    proguardConfiguration = ProguardConfiguration.defaultConfiguration(itemFactory, reporter);
+    proguardConfiguration = null;
   }
 
   // Constructor for D8.
@@ -69,7 +74,7 @@
     assert factory != null;
     this.reporter = reporter;
     itemFactory = factory;
-    proguardConfiguration = ProguardConfiguration.defaultConfiguration(itemFactory, reporter);
+    proguardConfiguration = null;
   }
 
   // Constructor for R8.
@@ -522,6 +527,7 @@
     public boolean nondeterministicCycleElimination = false;
     public Set<Inliner.Reason> validInliningReasons = null;
     public boolean allowFailureOnInnerClassErrors = false;
+    public boolean noLocalsTableOnInput = false;
     public boolean forceNameReflectionOptimization = false;
   }
 
@@ -809,7 +815,7 @@
   //
   // We used to insert a empty loop at the end, however, mediatek has an optimizer
   // on lollipop devices that cannot deal with an unreachable infinite loop, so we
-  // couldn't do that. See b/119895393.
+  // couldn't do that. See b/119895393.2
   public boolean canHaveTracingPastInstructionsStreamBug() {
     return minApiLevel < AndroidApiLevel.L.getLevel();
   }
@@ -841,4 +847,27 @@
     // TODO(ager): Update this with an actual bound when the issue has been fixed.
     return true;
   }
+
+  // Some Art Lollipop version do not deal correctly with long-to-int conversions.
+  //
+  // In particular, the following code performs an out of bounds array access when the
+  // long loaded from the long array is very large (has non-zero values in the upper 32 bits).
+  //
+  //   aget-wide v9, v3, v1
+  //   long-to-int v9, v9
+  //   aget-wide v10, v3, v9
+  //
+  // The issue seems to be that the higher bits of the 64-bit register holding the long
+  // are not cleared and the integer is therefore a 64-bit integer that is not truncated
+  // to 32 bits.
+  //
+  // As a workaround, we do not allow long-to-int to have the same source and target register
+  // for min-apis where lollipop devices could be targeted.
+  //
+  // See b/80262475.
+  public boolean canHaveLongToIntBug() {
+    // We have only seen this happening on Lollipop arm64 backends. We have tested on
+    // Marshmallow and Nougat arm64 devices and they do not have the bug.
+    return minApiLevel < AndroidApiLevel.M.getLevel();
+  }
 }
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
index 7bce10a..fc18db1 100644
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
@@ -124,6 +124,8 @@
           R8Command.builder(handler)
               .setMode(mode)
               .setMinApiLevel(minApiLevel)
+              .setDisableTreeShaking(true)
+              .setDisableMinification(true)
               .setProgramConsumer(new EnsureOutputConsumer())
               .addLibraryFiles(libraries)
               .addProgramFiles(inputs)
@@ -140,6 +142,8 @@
       R8Command.Builder builder =
           R8Command.builder(handler)
               .setMinApiLevel(minApiLevel)
+              .setDisableTreeShaking(true)
+              .setDisableMinification(true)
               .setProgramConsumer(new EnsureOutputConsumer())
               .addLibraryFiles(libraries);
       for (ClassFileContent classfile : readClassFiles(inputs)) {
@@ -160,6 +164,8 @@
       R8Command.Builder builder =
           R8Command.builder(handler)
               .setMinApiLevel(minApiLevel)
+              .setDisableTreeShaking(true)
+              .setDisableMinification(true)
               .setProgramConsumer(new EnsureOutputConsumer())
               .addLibraryFiles(libraries);
       for (Path input : inputs) {
@@ -189,6 +195,8 @@
       R8Command.Builder builder =
           R8Command.builder(handler)
               .setMinApiLevel(minApiLevel)
+              .setDisableTreeShaking(true)
+              .setDisableMinification(true)
               .setProgramConsumer(new EnsureOutputConsumer())
               .addProgramFiles(inputs);
       for (Path library : libraries) {
@@ -211,6 +219,8 @@
       R8.run(
           R8Command.builder(handler)
               .setMinApiLevel(minApiLevel)
+              .setDisableTreeShaking(true)
+              .setDisableMinification(true)
               .setProgramConsumer(new EnsureOutputConsumer())
               .addLibraryFiles(libraries)
               .addProgramFiles(inputs)
@@ -240,6 +250,8 @@
       R8.run(
           R8Command.builder(handler)
               .setMinApiLevel(minApiLevel)
+              .setDisableTreeShaking(true)
+              .setDisableMinification(true)
               .setProgramConsumer(new EnsureOutputConsumer())
               .addLibraryFiles(libraries)
               .addProgramFiles(inputs)
@@ -261,6 +273,8 @@
       R8.run(
           R8Command.builder(handler)
               .setMinApiLevel(minApiLevel)
+              .setDisableTreeShaking(true)
+              .setDisableMinification(true)
               .setProgramConsumer(new EnsureOutputConsumer())
               .addLibraryFiles(libraries)
               .addProgramFiles(inputs)
@@ -280,6 +294,8 @@
       R8Command.Builder builder =
           R8Command.builder(handler)
               .setMinApiLevel(minApiLevel)
+              .setDisableTreeShaking(true)
+              .setDisableMinification(true)
               .setProgramConsumer(new EnsureOutputConsumer())
               .addLibraryFiles(libraries)
               .addProgramFiles(inputs);
diff --git a/src/test/java/com/android/tools/r8/AsmTestBase.java b/src/test/java/com/android/tools/r8/AsmTestBase.java
index 2c4cb6b..34c05d1 100644
--- a/src/test/java/com/android/tools/r8/AsmTestBase.java
+++ b/src/test/java/com/android/tools/r8/AsmTestBase.java
@@ -76,15 +76,16 @@
     ProcessResult javaResult =
         useJavaNoVerify ? runOnJavaRawNoVerify(main, classes) : runOnJavaRaw(main, classes);
     ProcessResult d8Result = runOnArtRaw(compileWithD8(app), main);
-    ProcessResult r8Result = runOnArtRaw(compileWithR8(app), main);
+    ProcessResult r8NonShakenResult =
+        runOnArtRaw(compileWithR8(app, "-dontshrink\n-dontobfuscate\n"), main);
     ProcessResult r8ShakenResult = runOnArtRaw(
         compileWithR8(app, keepMainProguardConfiguration(main) + "-dontobfuscate\n"), main);
     Assert.assertEquals(javaResult.stdout, d8Result.stdout);
-    Assert.assertEquals(javaResult.stdout, r8Result.stdout);
+    Assert.assertEquals(javaResult.stdout, r8NonShakenResult.stdout);
     Assert.assertEquals(javaResult.stdout, r8ShakenResult.stdout);
     Assert.assertEquals(0, javaResult.exitCode);
     Assert.assertEquals(0, d8Result.exitCode);
-    Assert.assertEquals(0, r8Result.exitCode);
+    Assert.assertEquals(0, r8NonShakenResult.exitCode);
     Assert.assertEquals(0, r8ShakenResult.exitCode);
   }
 
@@ -95,7 +96,7 @@
     CompilationFailedException r8Error = null;
     CompilationFailedException r8ShakenError = null;
     try {
-      runOnArtRaw(compileWithR8(app), main);
+      runOnArtRaw(compileWithR8(app, "-dontshrink\n-dontobfuscate\n"), main);
     } catch (CompilationFailedException e) {
       r8Error = e;
     }
diff --git a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
index 6f40632..2253f1f 100644
--- a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
@@ -7,6 +7,8 @@
 import static com.google.common.io.ByteStreams.toByteArray;
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.origin.Origin;
+import com.google.common.collect.ImmutableList;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.nio.file.Path;
@@ -305,6 +307,9 @@
             .addProgramFiles(inputJar)
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
+            .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
             .setOutput(outputJar, OutputMode.ClassFile)
             .build();
     ToolHelper.runR8(
diff --git a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
index 6d0ef5a..c5e44a3 100644
--- a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
+++ b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
@@ -49,13 +49,16 @@
     Path mainDexList = temp.getRoot().toPath().resolve("maindexlist.txt");
     FileUtils.writeTextFile(mainDexList, "desugaringwithmissingclasstest1/Main.class");
 
+    // It is important to place the api usage sample jar after the current classpath because we want
+    // to find D8/R8 classes before the ones in the jar, otherwise renamed classes and fields cannot
+    // be found.
+    String classPath = System.getProperty("java.class.path") + File.pathSeparator + jar.toString();
     List<String> command =
         ImmutableList.<String>builder()
             .addAll(
                 ImmutableList.of(
                     ToolHelper.getJavaExecutable(),
-                    "-cp",
-                    jar.toString() + File.pathSeparator + System.getProperty("java.class.path"),
+                    "-cp", classPath,
                     main,
                     // Compiler arguments.
                     "--output",
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index d8d983f..35dafae 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -283,7 +283,7 @@
           .put(
               "lang.ClassLoader.getSystemResourceAsStreamLjava_lang_String.ClassLoader_getSystemResourceAsStream_A01",
               anyDexVm())
-          .put("lang.ClassLoader.getPackages.ClassLoader_getPackages_A01", any())
+          .put("lang.ClassLoader.getPackages.ClassLoader_getPackages_A01", anyDexVm())
           .put(
               "lang.ClassLoader.setClassAssertionStatusLjava_lang_StringZ.ClassLoader_setClassAssertionStatus_A01",
               any())
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 109b42d..0743f18 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -202,6 +202,18 @@
   }
 
   @Override
+  public ProguardTestBuilder addKeepRuleFiles(List<Path> files) {
+    try {
+      for (Path file : files) {
+        config.addAll(FileUtils.readAllLines(file));
+      }
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    return self();
+  }
+
+  @Override
   public ProguardTestBuilder addKeepRules(Collection<String> rules) {
     config.addAll(rules);
     return self();
diff --git a/src/test/java/com/android/tools/r8/R8CFExamplesTests.java b/src/test/java/com/android/tools/r8/R8CFExamplesTests.java
index bb88036..0b464c7 100644
--- a/src/test/java/com/android/tools/r8/R8CFExamplesTests.java
+++ b/src/test/java/com/android/tools/r8/R8CFExamplesTests.java
@@ -104,6 +104,8 @@
         R8Command.builder()
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setMode(mode)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
             .addProgramFiles(inputJar)
             .setOutput(outputJar, OutputMode.ClassFile)
             .build(),
diff --git a/src/test/java/com/android/tools/r8/R8CodeCanonicalizationTest.java b/src/test/java/com/android/tools/r8/R8CodeCanonicalizationTest.java
index 405994c..65aea6e 100644
--- a/src/test/java/com/android/tools/r8/R8CodeCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/R8CodeCanonicalizationTest.java
@@ -36,6 +36,8 @@
   public void testNumberOfCodeItemsUnchanged() throws Exception {
     int numberOfCodes = readNumberOfCodes(SOURCE_DEX);
     R8Command.Builder builder = R8Command.builder()
+        .setDisableTreeShaking(true)
+        .setDisableMinification(true)
         .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
         .setOutput(temp.getRoot().toPath(), OutputMode.DexIndexed);
     ToolHelper.getAppBuilder(builder).addProgramFiles(SOURCE_DEX);
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 518d26f..5331ca9 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -64,8 +64,8 @@
   private void verifyEmptyCommand(R8Command command) throws Throwable {
     assertEquals(0, ToolHelper.getApp(command).getDexProgramResourcesForTesting().size());
     assertEquals(0, ToolHelper.getApp(command).getClassProgramResourcesForTesting().size());
-    assertFalse(command.getEnableMinification());
-    assertFalse(command.getEnableTreeShaking());
+    assertTrue(command.getEnableMinification());
+    assertTrue(command.getEnableTreeShaking());
     assertEquals(CompilationMode.RELEASE, command.getMode());
     assertTrue(command.getProgramConsumer() instanceof DexIndexedConsumer);
   }
@@ -93,7 +93,12 @@
     Path output = working.resolve("classes.dex");
     assertFalse(Files.exists(output));
     ProcessResult result =
-        ToolHelper.forkR8(working, input.toString(), "--lib", library.toAbsolutePath().toString());
+        ToolHelper.forkR8(
+            working,
+            input.toString(),
+            "--lib",
+            library.toAbsolutePath().toString(),
+            "--no-tree-shaking");
     assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
     assertTrue(Files.exists(output));
   }
@@ -113,6 +118,7 @@
         "24",
         "--lib",
         library.toAbsolutePath().toString(),
+        "--no-tree-shaking",
         input.toString());
     assertEquals(0, ToolHelper.forkR8(working, "@flags.txt").exitCode);
     assertTrue(Files.exists(output));
@@ -281,6 +287,8 @@
     ProcessResult result =
         ToolHelper.forkR8(
             Paths.get("."),
+            "--no-tree-shaking",
+            "--no-minification",
             input.toString(),
             "--output",
             existingDir.toString(),
@@ -552,7 +560,7 @@
   @Test
   public void noTreeShakingOption() throws Throwable {
     // Default "keep all" rule implies no tree shaking.
-    assertFalse(parse().getEnableTreeShaking());
+    assertTrue(parse().getEnableTreeShaking());
     assertFalse(parse("--no-tree-shaking").getEnableTreeShaking());
 
     // With a Proguard configuration --no-tree-shaking takes effect.
@@ -565,7 +573,7 @@
   @Test
   public void noMinificationOption() throws Throwable {
     // Default "keep all" rule implies no tree minification.
-    assertFalse(parse().getEnableMinification());
+    assertTrue(parse().getEnableMinification());
     assertFalse(parse("--no-minification").getEnableMinification());
 
     // With a Proguard configuration --no-tree-shaking takes effect.
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 0634d6f..0642d44 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -1576,6 +1576,10 @@
           R8Command.Builder r8builder =
               R8Command.builder()
                   .setMode(mode)
+                  .setDisableTreeShaking(true)
+                  .setDisableMinification(true)
+                  .addProguardConfiguration(
+                      ImmutableList.of("-keepattributes *"), Origin.unknown())
                   .setProgramConsumer(
                       new ClassFileConsumer() {
 
@@ -1635,6 +1639,9 @@
           R8Command.Builder builder =
               R8Command.builder()
                   .setMode(mode)
+                  .setDisableTreeShaking(true)
+                  .setDisableMinification(true)
+                  .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
                   .setOutput(
                       Paths.get(resultPath),
                       cfBackend ? OutputMode.ClassFile : OutputMode.DexIndexed);
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidNTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidNTest.java
index 8d89f42..db48866 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidNTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidNTest.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8;
 
+import com.android.tools.r8.origin.Origin;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.function.UnaryOperator;
 
@@ -21,6 +23,15 @@
     }
 
     @Override
+    TestRunner withKeepAll() {
+      return withBuilderTransformation(builder ->
+          builder
+              .setDisableTreeShaking(true)
+              .setDisableMinification(true)
+              .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown()));
+    }
+
+    @Override
     void build(Path inputFile, Path out) throws Throwable {
       R8Command.Builder builder = R8Command.builder();
       for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 0b66e5a..003607b 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -210,12 +210,21 @@
       return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel.getLevel()));
     }
 
+    @Override R8TestRunner withKeepAll() {
+      return withBuilderTransformation(builder ->
+          builder
+              .setDisableTreeShaking(true)
+              .setDisableMinification(true)
+              .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown()));
+    }
+
     @Override
     void build(Path inputFile, Path out, OutputMode mode) throws Throwable {
       R8Command.Builder builder = R8Command.builder().setOutput(out, mode);
       for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
         builder = transformation.apply(builder);
       }
+
       builder.addLibraryFiles(ToolHelper.getAndroidJar(
           androidJarVersion == null ? builder.getMinApiLevel() : androidJarVersion.getLevel()));
       R8Command command = builder.addProgramFiles(inputFile).build();
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
index 7d1a088..9ac01a0 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -70,6 +71,15 @@
     }
 
     @Override
+    R8TestRunner withKeepAll() {
+      return withBuilderTransformation(builder ->
+          builder
+              .setDisableTreeShaking(true)
+              .setDisableMinification(true)
+              .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown()));
+    }
+
+    @Override
     void build(Path inputFile, Path out) throws Throwable {
       R8Command.Builder builder = R8Command.builder();
       for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 428c611..195e01c 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -12,8 +12,10 @@
 import com.android.tools.r8.R8RunArtTestsTest.DexTool;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
+import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
@@ -168,6 +170,9 @@
                           : ToolHelper.getDefaultAndroidJar())
                   .setOutput(getOutputFile(), outputMode)
                   .setMode(mode)
+                  .setDisableTreeShaking(true)
+                  .setDisableMinification(true)
+                  .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
                   .build();
           ToolHelper.runR8(
               command,
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
index 670e6be..11035f5 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
@@ -4,7 +4,9 @@
 
 package com.android.tools.r8;
 
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.function.UnaryOperator;
 
@@ -22,6 +24,15 @@
     }
 
     @Override
+    R8TestRunner withKeepAll() {
+      return withBuilderTransformation(builder ->
+          builder
+              .setDisableTreeShaking(true)
+              .setDisableMinification(true)
+              .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown()));
+    }
+
+    @Override
     void build(Path inputFile, Path out) throws Throwable {
       R8Command.Builder builder = R8Command.builder();
       for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
diff --git a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
index 11ad12b..0d21319 100644
--- a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
@@ -7,6 +7,7 @@
 
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
 import com.google.common.collect.ImmutableList;
@@ -165,8 +166,9 @@
     Path originalDexFile = Paths.get(SMALI_DIR, directoryName, dexFileName);
     String outputPath = temp.getRoot().getCanonicalPath();
     R8Command.Builder builder = R8Command.builder()
+        .addProguardConfiguration(ImmutableList.of("-keep class * { *; }"), Origin.unknown())
         .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
-            .setOutput(Paths.get(outputPath), OutputMode.DexIndexed);
+        .setOutput(Paths.get(outputPath), OutputMode.DexIndexed);
     ToolHelper.getAppBuilder(builder).addProgramFiles(originalDexFile);
 
     if (failingOnX8.contains(directoryName)) {
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 8c4f682..2c72ec7 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -6,10 +6,11 @@
 import com.android.tools.r8.R8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.TestBase.R8Mode;
-import com.android.tools.r8.graphinfo.GraphConsumer;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -74,6 +75,12 @@
   }
 
   @Override
+  public R8TestBuilder addKeepRuleFiles(List<Path> files) {
+    builder.addProguardConfigurationFiles(files);
+    return self();
+  }
+
+  @Override
   public R8TestBuilder addKeepRules(Collection<String> rules) {
     builder.addProguardConfiguration(new ArrayList<>(rules), Origin.unknown());
     return self();
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
index 5b4482e..f062988 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
@@ -99,6 +99,10 @@
 
     abstract TestRunner withMinApiLevel(int minApiLevel);
 
+    TestRunner withKeepAll() {
+      return this;
+    }
+
     abstract void build(Path inputFile, Path out) throws Throwable;
   }
 
@@ -126,6 +130,7 @@
     test("staticinterfacemethods", "interfacemethods", "StaticInterfaceMethods")
         .withMinApiLevel(AndroidApiLevel.K.getLevel())
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
+        .withKeepAll()
         .run();
   }
 
@@ -134,7 +139,8 @@
     test("staticinterfacemethods-error-due-to-min-sdk", "interfacemethods",
         "StaticInterfaceMethods")
         .withInterfaceMethodDesugaring(OffOrAuto.Off)
-       .run();
+        .withKeepAll()
+        .run();
   }
 
   @Test
@@ -142,6 +148,7 @@
     test("defaultmethods", "interfacemethods", "DefaultMethods")
         .withMinApiLevel(AndroidApiLevel.K.getLevel())
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
+        .withKeepAll()
         .run();
   }
 
@@ -150,6 +157,7 @@
     test("defaultmethods-error-due-to-min-sdk", "interfacemethods",
         "DefaultMethods")
         .withInterfaceMethodDesugaring(OffOrAuto.Off)
+        .withKeepAll()
         .run();
   }
 
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 4821c25..4f98b6f 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -44,7 +44,6 @@
 import java.util.function.Predicate;
 import java.util.function.UnaryOperator;
 import java.util.stream.Collectors;
-import java.util.zip.ZipException;
 import java.util.zip.ZipFile;
 import org.junit.Assert;
 import org.junit.Assume;
@@ -175,6 +174,10 @@
 
     abstract C withMinApiLevel(AndroidApiLevel minApiLevel);
 
+    C withKeepAll() {
+      return self();
+    }
+
     C withAndroidJar(AndroidApiLevel androidJarVersion) {
       assert this.androidJarVersion == null;
       this.androidJarVersion = androidJarVersion;
@@ -312,6 +315,7 @@
   public void stringConcat() throws Throwable {
     test("stringconcat", "stringconcat", "StringConcat")
         .withMinApiLevel(AndroidApiLevel.K)
+        .withKeepAll()
         .run();
   }
 
@@ -319,6 +323,7 @@
   public void invokeCustom() throws Throwable {
     test("invokecustom", "invokecustom", "InvokeCustom")
         .withMinApiLevel(AndroidApiLevel.O)
+        .withKeepAll()
         .run();
   }
 
@@ -326,6 +331,7 @@
   public void invokeCustom2() throws Throwable {
     test("invokecustom2", "invokecustom2", "InvokeCustom")
         .withMinApiLevel(AndroidApiLevel.O)
+        .withKeepAll()
         .run();
   }
 
@@ -333,6 +339,7 @@
   public void invokeCustomErrorDueToMinSdk() throws Throwable {
     test("invokecustom-error-due-to-min-sdk", "invokecustom", "InvokeCustom")
         .withMinApiLevel(AndroidApiLevel.N_MR1)
+        .withKeepAll()
         .run();
   }
 
@@ -340,6 +347,7 @@
   public void invokePolymorphic() throws Throwable {
     test("invokepolymorphic", "invokepolymorphic", "InvokePolymorphic")
         .withMinApiLevel(AndroidApiLevel.O)
+        .withKeepAll()
         .run();
   }
 
@@ -347,6 +355,7 @@
   public void invokePolymorphicErrorDueToMinSdk() throws Throwable {
     test("invokepolymorphic-error-due-to-min-sdk", "invokepolymorphic", "InvokePolymorphic")
         .withMinApiLevel(AndroidApiLevel.N_MR1)
+        .withKeepAll()
         .run();
   }
 
@@ -354,6 +363,7 @@
   public void lambdaDesugaring() throws Throwable {
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
         .withMinApiLevel(AndroidApiLevel.K)
+        .withKeepAll()
         .run();
   }
 
@@ -362,6 +372,7 @@
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
         .withMinApiLevel(AndroidApiLevel.K)
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
+        .withKeepAll()
         .run();
   }
 
@@ -371,6 +382,7 @@
         .withMinApiLevel(AndroidApiLevel.K)
         .withAndroidJar(AndroidApiLevel.O)
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
+        .withKeepAll()
         .run();
   }
 
@@ -380,6 +392,7 @@
         .withMinApiLevel(AndroidApiLevel.K)
         .withAndroidJar(AndroidApiLevel.O)
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
+        .withKeepAll()
         .run();
   }
 
@@ -387,6 +400,7 @@
   public void lambdaDesugaringValueAdjustments() throws Throwable {
     test("lambdadesugaring-value-adjustments", "lambdadesugaring", "ValueAdjustments")
         .withMinApiLevel(AndroidApiLevel.K)
+        .withKeepAll()
         .run();
   }
 
@@ -394,6 +408,7 @@
   public void paramNames() throws Throwable {
     test("paramnames", "paramnames", "ParameterNames")
         .withMinApiLevel(AndroidApiLevel.O)
+        .withKeepAll()
         .run();
   }
 
@@ -410,13 +425,16 @@
     // No need to specify minSdk as repeat annotations are handled by javac and we do not have
     // to do anything to support them. The library methods to access them just have to be in
     // the system.
-    test("repeat_annotations", "repeat_annotations", "RepeatAnnotations").run();
+    test("repeat_annotations", "repeat_annotations", "RepeatAnnotations")
+        .withKeepAll()
+        .run();
   }
 
   @Test
   public void testTryWithResources() throws Throwable {
     test("try-with-resources-simplified", "trywithresources", "TryWithResourcesNotDesugaredTests")
         .withTryWithResourcesDesugaring(OffOrAuto.Off)
+        .withKeepAll()
         .run();
   }
 
@@ -429,6 +447,7 @@
               Assert.assertFalse(invoke.invokedMethod().name.toString().equals("addSuppressed"));
               Assert.assertFalse(invoke.invokedMethod().name.toString().equals("getSuppressed"));
             })
+        .withKeepAll()
         .run();
   }
 
@@ -477,6 +496,7 @@
         .withMinApiLevel(AndroidApiLevel.K) // K to create dispatch classes
         .withAndroidJar(AndroidApiLevel.O)
         .withArg(String.valueOf(ToolHelper.getMinApiLevelForDexVm().getLevel() >= 24))
+        .withKeepAll()
         .run();
   }
 
@@ -507,7 +527,8 @@
             .withMinApiLevel(minApi)
             .withOptionConsumer(option -> option.minimalMainDex = true)
             .withOptionConsumer(option -> option.enableInheritanceClassInDexDistributor = false)
-            .withMainDexClass(mainDexClasses);
+            .withMainDexClass(mainDexClasses)
+            .withKeepAll();
     Path fullDexes = temp.getRoot().toPath().resolve(packageName + "full" + ZIP_EXTENSION);
     full.build(input, fullDexes);
 
@@ -564,7 +585,8 @@
             .withOptionConsumer(option -> option.minimalMainDex = true)
             .withOptionConsumer(option -> option.enableInheritanceClassInDexDistributor = false)
             .withMainDexClass(mainDexClasses)
-            .withMinApiLevel(minApi);
+            .withMinApiLevel(minApi)
+            .withKeepAll();
 
     Path dexesThroughIntermediate =
         temp.getRoot().toPath().resolve(packageName + "dex" + ZIP_EXTENSION);
@@ -581,7 +603,7 @@
   }
 
   void execute(String testName,
-      String qualifiedMainClass, Path[] jars, Path[] dexes, List<String> args) throws IOException {
+      String qualifiedMainClass, Path[] jars, Path[] dexes, List<String> args) {
     Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles());
     boolean expectedToFail = expectedToFail(testName);
     try {
@@ -614,7 +636,7 @@
   }
 
   protected CodeInspector getMainDexInspector(Path zip)
-      throws ZipException, IOException, ExecutionException {
+      throws IOException, ExecutionException {
     try (ZipFile zipFile = new ZipFile(zip.toFile(), StandardCharsets.UTF_8)) {
       try (InputStream in =
           zipFile.getInputStream(zipFile.getEntry(ToolHelper.DEFAULT_DEX_FILENAME))) {
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
index 2ab5e3e..661c815 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
@@ -161,6 +161,10 @@
 
     abstract C withMinApiLevel(int minApiLevel);
 
+    C withKeepAll() {
+      return self();
+    }
+
     C withAndroidJar(int androidJarVersion) {
       assert this.androidJarVersion == null;
       this.androidJarVersion = androidJarVersion;
@@ -219,6 +223,7 @@
   public void invokeCustom() throws Throwable {
     test("invokecustom", "invokecustom", "InvokeCustom")
         .withMinApiLevel(AndroidApiLevel.P.getLevel())
+        .withKeepAll()
         .run();
   }
 
@@ -226,6 +231,7 @@
   public void invokeCustomErrorDueToMinSdk() throws Throwable {
     test("invokecustom-error-due-to-min-sdk", "invokecustom", "InvokeCustom")
         .withMinApiLevel(AndroidApiLevel.O.getLevel())
+        .withKeepAll()
         .run();
   }
 
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
index 9c91282..c95d8a6 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
@@ -164,6 +164,10 @@
 
     abstract C withMinApiLevel(int minApiLevel);
 
+    C withKeepAll() {
+      return self();
+    }
+
     C withAndroidJar(int androidJarVersion) {
       assert this.androidJarVersion == null;
       this.androidJarVersion = androidJarVersion;
@@ -264,6 +268,7 @@
     test("native-private-interface-methods",
         "privateinterfacemethods", "PrivateInterfaceMethods")
         .withMinApiLevel(AndroidApiLevel.N.getLevel())
+        .withKeepAll()
         .run();
   }
 
@@ -273,6 +278,7 @@
     test("desugared-private-interface-methods",
         "privateinterfacemethods", "PrivateInterfaceMethods")
         .withMinApiLevel(AndroidApiLevel.M.getLevel())
+        .withKeepAll()
         .withDexCheck(dexInspector -> {
           ClassSubject companion = dexInspector.clazz(
               iName + InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX);
@@ -298,6 +304,7 @@
   public void varHandleErrorDueToMinSdk() throws Throwable {
     test("varhandle-error-due-to-min-sdk", "varhandle", "VarHandleTests")
         .withMinApiLevel(AndroidApiLevel.O.getLevel())
+        .withKeepAll()
         .run();
   }
 
@@ -306,6 +313,7 @@
     TestRunner<?> test = test("twr-close-resource", "twrcloseresource", "TwrCloseResourceTest");
     test
         .withMinApiLevel(AndroidApiLevel.I.getLevel())
+        .withKeepAll()
         .withAndroidJar(AndroidApiLevel.K.getLevel())
         .withArg(test.getInputJar().toAbsolutePath().toString())
         .run();
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 1cd1b3e..0783413 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -442,7 +442,7 @@
 
   /** Compile an application with D8. */
   protected AndroidApp compileWithD8(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
-      throws IOException, CompilationFailedException {
+      throws CompilationFailedException {
     return ToolHelper.runD8(app, optionsConsumer);
   }
 
@@ -467,8 +467,7 @@
   }
 
   /** Compile an application with R8. */
-  protected AndroidApp compileWithR8(AndroidApp app)
-      throws IOException, CompilationFailedException {
+  protected AndroidApp compileWithR8(AndroidApp app) throws CompilationFailedException {
     R8Command command = ToolHelper.prepareR8CommandBuilder(app).build();
     return ToolHelper.runR8(command);
   }
@@ -476,7 +475,12 @@
   /** Compile an application with R8. */
   protected AndroidApp compileWithR8(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
       throws IOException, CompilationFailedException {
-    R8Command command = ToolHelper.prepareR8CommandBuilder(app).build();
+    R8Command command = ToolHelper.prepareR8CommandBuilder(app)
+        .setDisableTreeShaking(true)
+        .setDisableMinification(true)
+        .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
+        .build();
+
     return ToolHelper.runR8(command, optionsConsumer);
   }
 
@@ -590,6 +594,11 @@
         + "-printmapping\n";
   }
 
+  public static String noShrinkingNoMinificationProguardConfiguration() {
+    return "-dontshrink\n"
+        + "-dontobfuscate\n";
+  }
+
   /**
    * Generate a Proguard configuration for keeping the "static void main(String[])" method of the
    * specified class and specify if -allowaccessmodification and -dontobfuscate are added as well.
@@ -948,6 +957,10 @@
     public boolean isMinify() {
       return this != NONE;
     }
+
+    public boolean isAggressive() {
+      return this == AGGRESSIVE;
+    }
   }
 
   public static ProgramConsumer emptyConsumer(Backend backend) {
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index ae7cb4f..42b56ca 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -5,12 +5,14 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.TestBase.Backend;
-import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 
 public abstract class TestShrinkerBuilder<
         C extends BaseCompilerCommand,
@@ -28,8 +30,10 @@
 
   public abstract T noMinification();
 
-  public T addKeepRules(Path path) throws IOException {
-    return addKeepRules(FileUtils.readAllLines(path));
+  public abstract T addKeepRuleFiles(List<Path> files);
+
+  public T addKeepRuleFiles(Path... files) throws IOException {
+    return addKeepRuleFiles(Arrays.asList(files));
   }
 
   public abstract T addKeepRules(Collection<String> rules);
@@ -71,4 +75,31 @@
             "  public static void main(java.lang.String[]);",
             "}"));
   }
+
+  public T addKeepMethodRules(MethodReference... methods) {
+    for (MethodReference method : methods) {
+      addKeepRules(
+          "-keep class "
+              + method.getHolderClass().getTypeName()
+              + " { "
+              + getMethodLine(method)
+              + " }");
+    }
+    return self();
+  }
+
+  private static String getMethodLine(MethodReference method) {
+    // Should we encode modifiers in method references?
+    StringBuilder builder = new StringBuilder();
+    builder.append(method.getMethodName()).append("(");
+    boolean first = true;
+    for (TypeReference parameterType : method.getFormalTypes()) {
+      if (!first) {
+        builder.append(", ");
+      }
+      builder.append(parameterType.getTypeName());
+      first = false;
+    }
+    return builder.append(");").toString();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 69c710d..57650e2 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
-import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
@@ -876,11 +875,15 @@
   }
 
   public static ProguardConfiguration loadProguardConfiguration(
-      DexItemFactory factory, List<Path> configPaths)
-      throws IOException, ProguardRuleParserException {
+      DexItemFactory factory, List<Path> configPaths) {
     Reporter reporter = new Reporter();
     if (configPaths.isEmpty()) {
-      return ProguardConfiguration.defaultConfiguration(factory, reporter);
+      return ProguardConfiguration.builder(factory, reporter)
+          .disableShrinking()
+          .disableObfuscation()
+          .disableOptimization()
+          .addKeepAttributePatterns(ImmutableList.of("*"))
+          .build();
     }
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(factory, reporter);
@@ -923,7 +926,11 @@
 
   public static AndroidApp runR8(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
       throws CompilationFailedException {
-    return runR8(prepareR8CommandBuilder(app).build(), optionsConsumer);
+    R8Command command = prepareR8CommandBuilder(app)
+        .setDisableTreeShaking(true)
+        .setDisableMinification(true)
+        .build();
+    return runR8(command, optionsConsumer);
   }
 
   public static AndroidApp runR8(R8Command command) throws CompilationFailedException {
diff --git a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java
index 246e0c7..b0292e3 100644
--- a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java
@@ -40,6 +40,8 @@
     R8.run(
         R8Command.builder()
             .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
             .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
             .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
             .setProgramConsumer(new DexIndexedConsumer.ArchiveConsumer(outDex))
@@ -47,6 +49,8 @@
     R8.run(
         R8Command.builder()
             .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
             .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outCf))
diff --git a/src/test/java/com/android/tools/r8/cf/AnnotationTestRunner.java b/src/test/java/com/android/tools/r8/cf/AnnotationTestRunner.java
index 7705228..e9b4755 100644
--- a/src/test/java/com/android/tools/r8/cf/AnnotationTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/AnnotationTestRunner.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.origin.Origin;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import org.junit.Rule;
 import org.junit.Test;
@@ -32,6 +33,10 @@
     R8.run(
         R8Command.builder()
             .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
+            .addProguardConfiguration(
+                ImmutableList.of("-keepattributes *Annotation*"), Origin.unknown())
             .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setProgramConsumer(new DirectoryConsumer(out))
diff --git a/src/test/java/com/android/tools/r8/cf/CallLoopTestRunner.java b/src/test/java/com/android/tools/r8/cf/CallLoopTestRunner.java
index dd3f1e8..1ed7481 100644
--- a/src/test/java/com/android/tools/r8/cf/CallLoopTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/CallLoopTestRunner.java
@@ -24,6 +24,8 @@
     R8.run(
         R8Command.builder()
             .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
             .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setProgramConsumer(new DirectoryConsumer(out))
diff --git a/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java b/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
index 433483f..f8a654a 100644
--- a/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
@@ -54,6 +54,8 @@
     Builder builder =
         R8Command.builder()
             .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setProgramConsumer(consumer);
     input.accept(builder);
diff --git a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
index 413bd83..3d75846 100644
--- a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
@@ -53,10 +53,19 @@
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .build();
     assertEquals(2, countCatchHandlers(inputApp));
-    AndroidApp outputDexApp = ToolHelper.runR8(inputApp);
+    AndroidApp outputDexApp =
+        ToolHelper.runR8(
+            ToolHelper.prepareR8CommandBuilder(inputApp)
+                .setDisableTreeShaking(true)
+                .setDisableMinification(true)
+                .build());
     assertEquals(1, countCatchHandlers(outputDexApp));
     AndroidApp outputCfApp =
-        ToolHelper.runR8WithProgramConsumer(inputApp, ClassFileConsumer.emptyConsumer());
+        ToolHelper.runR8(
+            ToolHelper.prepareR8CommandBuilder(inputApp, ClassFileConsumer.emptyConsumer())
+                .setDisableTreeShaking(true)
+                .setDisableMinification(true)
+                .build());
     assertEquals(1, countCatchHandlers(outputCfApp));
   }
 
diff --git a/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
index 632d0b9..077c7a7 100644
--- a/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
@@ -51,6 +51,8 @@
     R8.run(
         R8Command.builder()
             .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setProgramConsumer(appBuilder.wrapClassFileConsumer(new ArchiveConsumer(outPath)))
             .addClassProgramData(inputClass, Origin.unknown())
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
index 8dd966d..46ed96b 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
@@ -156,7 +156,11 @@
     // MethodHandle.invoke() only supported from Android O
     // ConstMethodHandle only supported from Android P
     Builder builder =
-        R8Command.builder().setMode(compilationMode).setProgramConsumer(programConsumer);
+        R8Command.builder()
+            .setMode(compilationMode)
+            .setProgramConsumer(programConsumer)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true);
     if (programConsumer instanceof ClassFileConsumer) {
       builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
     } else {
diff --git a/src/test/java/com/android/tools/r8/cf/NegativeZeroTestRunner.java b/src/test/java/com/android/tools/r8/cf/NegativeZeroTestRunner.java
index 32cf86a..a38bf60 100644
--- a/src/test/java/com/android/tools/r8/cf/NegativeZeroTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/NegativeZeroTestRunner.java
@@ -24,6 +24,8 @@
     R8.run(
         R8Command.builder()
             .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
             .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setProgramConsumer(new DirectoryConsumer(out))
diff --git a/src/test/java/com/android/tools/r8/cf/NestedExceptionTestRunner.java b/src/test/java/com/android/tools/r8/cf/NestedExceptionTestRunner.java
index a60eda9..a7c1c1d 100644
--- a/src/test/java/com/android/tools/r8/cf/NestedExceptionTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/NestedExceptionTestRunner.java
@@ -28,6 +28,8 @@
     R8.run(
         R8Command.builder()
             .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
             .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setProgramConsumer(sink.wrapClassFileConsumer(null))
diff --git a/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
index b778c41..2664ef4 100644
--- a/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
@@ -50,10 +50,20 @@
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .build();
     assertEquals(2, countCatchHandlers(inputApp));
-    AndroidApp outputDexApp = ToolHelper.runR8(inputApp);
+    AndroidApp outputDexApp =
+        ToolHelper.runR8(
+            ToolHelper.prepareR8CommandBuilder(inputApp)
+                .setDisableTreeShaking(true)
+                .setDisableMinification(true)
+                .build());
     assertEquals(1, countCatchHandlers(outputDexApp));
     AndroidApp outputCfApp =
-        ToolHelper.runR8WithProgramConsumer(inputApp, ClassFileConsumer.emptyConsumer());
+        ToolHelper.runR8(
+            ToolHelper.prepareR8CommandBuilder(inputApp)
+                .setDisableTreeShaking(true)
+                .setDisableMinification(true)
+                .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+                .build());
     // ClassCastException and NullPointerException will not have same type on stack.
     assertEquals(2, countCatchHandlers(outputCfApp));
   }
diff --git a/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
index 1f6d7f7..4b9b937 100644
--- a/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
@@ -31,6 +31,8 @@
     AndroidAppConsumers a = new AndroidAppConsumers();
     R8.run(
         R8Command.builder()
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
             .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setProgramConsumer(a.wrapClassFileConsumer(null))
diff --git a/src/test/java/com/android/tools/r8/cf/UninitializedInFrameTestRunner.java b/src/test/java/com/android/tools/r8/cf/UninitializedInFrameTestRunner.java
index 8182501..466ac41 100644
--- a/src/test/java/com/android/tools/r8/cf/UninitializedInFrameTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/UninitializedInFrameTestRunner.java
@@ -53,6 +53,8 @@
     R8.run(
         R8Command.builder()
             .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
             .addClassProgramData(clazz, Origin.unknown())
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setProgramConsumer(new ArchiveConsumer(output))
diff --git a/src/test/java/com/android/tools/r8/cf/UnneededLoadStoreDebugInfoTest.java b/src/test/java/com/android/tools/r8/cf/UnneededLoadStoreDebugInfoTest.java
index 016fe18..42bc68b 100644
--- a/src/test/java/com/android/tools/r8/cf/UnneededLoadStoreDebugInfoTest.java
+++ b/src/test/java/com/android/tools/r8/cf/UnneededLoadStoreDebugInfoTest.java
@@ -66,6 +66,8 @@
     R8.run(
         R8Command.builder()
             .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
             .addProgramFiles(inputJar)
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setProgramConsumer(new ArchiveConsumer(outputJar))
diff --git a/src/test/java/com/android/tools/r8/classmerging/ForceInliningWithStaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/ForceInliningWithStaticInterfaceMethodTest.java
new file mode 100644
index 0000000..7c86d1a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/ForceInliningWithStaticInterfaceMethodTest.java
@@ -0,0 +1,62 @@
+// 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.classmerging;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+/** Regression test for b/120121170. */
+public class ForceInliningWithStaticInterfaceMethodTest extends TestBase {
+
+  @Test
+  public void test() throws Exception {
+    String expectedOutput = StringUtils.lines("A.<init>()", "I.m()", "B.<init>()");
+    testForR8(Backend.DEX)
+        .addInnerClasses(ForceInliningWithStaticInterfaceMethodTest.class)
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(AndroidApiLevel.M)
+        .compile()
+        .run(TestClass.class)
+        .assertSuccessWithOutput(expectedOutput);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new B();
+    }
+  }
+
+  static class A {
+
+    public A() {
+      System.out.println("A.<init>()");
+
+      // By the time the vertical class merger runs, I.m() still exists, so the inlining oracle
+      // concludes that A.<init>() is eligible for inlining. However, by the time A.<init>() will
+      // be force-inlined into B.<init>(), I.m() has been rewritten as a result of interface method
+      // desugaring, but the target method is not yet added to the application. Hence the inlining
+      // oracle concludes that A.<init>() is not eligible for inlining, which leads to an error
+      // "FORCE inlining on non-inlinable".
+      I.m();
+    }
+  }
+
+  static class B extends A {
+
+    public B() {
+      System.out.println("B.<init>()");
+    }
+  }
+
+  interface I {
+
+    static void m() {
+      System.out.println("I.m()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java b/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java
index e0b953a..22bca9f 100644
--- a/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java
@@ -27,7 +27,7 @@
     DebugTestConfig d8NoLocals = new D8DebugTestConfig().compileAndAdd(
         temp,
         Collections.singletonList(ToolHelper.getClassFileForTestClass(CLASS)),
-        options -> options.proguardConfiguration.getKeepAttributes().localVariableTable = false);
+        options -> options.testing.noLocalsTableOnInput = true);
 
     new DebugStreamComparator()
         .add("CF", streamDebugTest(cf, NAME, NO_FILTER))
diff --git a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
index fb5ffe2..635f77e 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
 import com.android.tools.r8.debug.DebugTestConfig.RuntimeKind;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.google.common.collect.ImmutableList;
@@ -46,7 +47,13 @@
     Path outdir = temp.newFolder().toPath();
     Path outjar = outdir.resolve("r8_compiled.jar");
     R8Command.Builder builder =
-        R8Command.builder().addProgramFiles(DEBUGGEE_JAR).setMode(CompilationMode.RELEASE);
+        R8Command.builder()
+            .addProgramFiles(DEBUGGEE_JAR)
+            .setMode(CompilationMode.RELEASE)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
+            .addProguardConfiguration(
+                ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), Origin.unknown());
 
     if (runtimeKind == RuntimeKind.DEX) {
       AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
diff --git a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
index 6dccf0b..099f55b 100644
--- a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
@@ -9,8 +9,10 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.function.Consumer;
@@ -54,6 +56,9 @@
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setMode(CompilationMode.DEBUG)
             .setOutput(output, OutputMode.ClassFile)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
+            .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
             .build(),
         optionsConsumer);
     return new CfDebugTestConfig(output);
diff --git a/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
index c7a5b16..cfd0a98 100644
--- a/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
@@ -17,6 +17,8 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
+import com.android.tools.r8.origin.Origin;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.stream.Stream;
 import org.junit.Assume;
@@ -112,6 +114,10 @@
     Builder builder =
         R8Command.builder()
             .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
+            .addProguardConfiguration(
+                ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), Origin.unknown())
             .setProgramConsumer(consumer)
             .addProgramFiles(inputJar);
     if ((consumer instanceof ClassFileConsumer)) {
diff --git a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
index 78e9a56..35f24c9 100644
--- a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.debug.DebugTestConfig.RuntimeKind;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.google.common.collect.ImmutableList;
@@ -58,7 +59,12 @@
     R8Command.Builder builder =
         R8Command.builder()
             .addProgramFiles(DEBUGGEE_JAR)
-            .setMode(dontOptimizeByEnablingDebug ? CompilationMode.DEBUG : CompilationMode.RELEASE);
+            .setMode(dontOptimizeByEnablingDebug ? CompilationMode.DEBUG : CompilationMode.RELEASE)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
+            .addProguardConfiguration(
+                ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), Origin.unknown());
+
     DebugTestConfig config = null;
 
     if (runtimeKind == RuntimeKind.CF) {
diff --git a/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java b/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java
index 805473b..f741381 100644
--- a/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java
@@ -35,6 +35,9 @@
           "R8/" + backend,
           temp ->
               testForR8(temp, backend)
+                  .noTreeShaking()
+                  .noMinification()
+                  .addKeepRules("-keepattributes SourceFile,LineNumberTable")
                   .addProgramClasses(CLASS)
                   .setMode(CompilationMode.DEBUG)
                   .debugConfig());
diff --git a/src/test/java/com/android/tools/r8/debug/MinificationTest.java b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
index 1d9bf27..d1c3ac1 100644
--- a/src/test/java/com/android/tools/r8/debug/MinificationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.debug.DebugTestConfig.RuntimeKind;
 import com.android.tools.r8.errors.Unreachable;
@@ -69,16 +68,17 @@
 
   private DebugTestConfig getTestConfig() throws Throwable {
     List<String> proguardConfigurations = Collections.emptyList();
-    if (minificationMode.isMinify()) {
-      ImmutableList.Builder<String> builder = ImmutableList.builder();
-      builder.add("-keep public class Minified { public static void main(java.lang.String[]); }");
-      builder.add("-keepattributes SourceFile");
-      builder.add("-keepattributes LineNumberTable");
-      if (minificationMode == MinifyMode.AGGRESSIVE) {
-        builder.add("-overloadaggressively");
-      }
-      proguardConfigurations = builder.build();
+    ImmutableList.Builder<String> proguardConfigurationBuilder = ImmutableList.builder();
+    if (!minificationMode.isMinify()) {
+      proguardConfigurationBuilder.add("-dontobfuscate");
+    } else if (minificationMode.isAggressive()) {
+      proguardConfigurationBuilder.add("-overloadaggressively");
     }
+    proguardConfigurationBuilder.add(
+        "-keep public class Minified { public static void main(java.lang.String[]); }");
+    proguardConfigurationBuilder.add("-keepattributes SourceFile");
+    proguardConfigurationBuilder.add("-keepattributes LineNumberTable");
+    proguardConfigurations = proguardConfigurationBuilder.build();
 
     Path outputPath = temp.getRoot().toPath().resolve("classes.zip");
     Path proguardMap = writeProguardMap ? temp.getRoot().toPath().resolve("proguard.map") : null;
diff --git a/src/test/java/com/android/tools/r8/debug/NonExitingMethodTestRunner.java b/src/test/java/com/android/tools/r8/debug/NonExitingMethodTestRunner.java
index 1405ce5..3e934b8 100644
--- a/src/test/java/com/android/tools/r8/debug/NonExitingMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/NonExitingMethodTestRunner.java
@@ -11,6 +11,8 @@
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.VmTestRunner;
 import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
+import com.android.tools.r8.origin.Origin;
+import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
 import org.junit.Assume;
@@ -40,6 +42,10 @@
     ToolHelper.runR8(
         R8Command.builder()
             .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
+            .addProguardConfiguration(
+                ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), Origin.unknown())
             .addProgramFiles(getClassFilePath())
             .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(path))
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
diff --git a/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java b/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java
index cf341fd..1739c30 100644
--- a/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java
@@ -10,7 +10,9 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.stream.Stream;
 import org.junit.Assume;
@@ -49,6 +51,10 @@
         R8Command.builder()
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
+            .addProguardConfiguration(
+                ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), Origin.unknown())
             .addProgramFiles(inputJar)
             .setOutput(outputJar, OutputMode.ClassFile)
             .build(),
diff --git a/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java b/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
index 67b7c1a..134b727 100644
--- a/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
@@ -8,8 +8,10 @@
 import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import org.junit.rules.TemporaryFolder;
 
@@ -23,10 +25,15 @@
       AndroidAppConsumers sink = new AndroidAppConsumers();
       R8.run(
           R8Command.builder()
+              .setDisableTreeShaking(true)
+              .setDisableMinification(true)
               .setMode(CompilationMode.DEBUG)
               .addProgramFiles(DebugTestBase.DEBUGGEE_JAR)
               .setProgramConsumer(sink.wrapClassFileConsumer(null))
               .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+              .setDisableTreeShaking(true)
+              .setDisableMinification(true)
+              .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
               .build());
       compiledResources = sink.build();
     }
diff --git a/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java b/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java
index 17f0c04..51bb411 100644
--- a/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java
+++ b/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.origin.Origin;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.Collection;
@@ -42,6 +43,11 @@
                     .setOutput(out, OutputMode.ClassFile)
                     .setMode(CompilationMode.DEBUG)
                     .addProgramFiles(DebugTestBase.DEBUGGEE_JAR)
+                    .setDisableTreeShaking(true)
+                    .setDisableMinification(true)
+                    .addProguardConfiguration(
+                        ImmutableList.of("-keepattributes SourceFile,LineNumberTable"),
+                        Origin.unknown())
                     .build(),
                 options -> options.enableCfFrontend = true);
           } catch (Exception e) {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
index ce84f51..980325e 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
@@ -22,8 +22,9 @@
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
 import com.android.tools.r8.naming.Range;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
-import java.io.IOException;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -91,7 +92,7 @@
   }
 
   @Test
-  public void testStackTrace() throws IOException, Exception {
+  public void testStackTrace() throws Exception {
     // See InliningWithoutPositionsTestSourceDump for the code compiled here.
     Path testClassDir = temp.newFolder(TEST_PACKAGE.split(".")).toPath();
     Path testClassPath = testClassDir.resolve(TEST_CLASS + ".class");
@@ -121,6 +122,11 @@
           .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
           .setOutput(outputPath, OutputMode.ClassFile);
     }
+    builder
+        .setDisableTreeShaking(true)
+        .setDisableMinification(true)
+        .addProguardConfiguration(
+            ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), Origin.unknown());
 
     ToolHelper.runR8(builder.build(), options -> options.inliningInstructionLimit = 40);
 
diff --git a/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
index 76d2291..a0e9a1d 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
@@ -15,6 +15,8 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import org.junit.Test;
 
@@ -71,6 +73,10 @@
     Builder builder =
         R8Command.builder()
             .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
+            .addProguardConfiguration(
+                ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), Origin.unknown())
             .setProgramConsumer(consumer)
             .addProgramFiles(inputJar);
     if ((consumer instanceof ClassFileConsumer)) {
diff --git a/src/test/java/com/android/tools/r8/desugar/KeptLambdaDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/KeptLambdaDesugaringTest.java
index 4664d42..8fddde3 100644
--- a/src/test/java/com/android/tools/r8/desugar/KeptLambdaDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/KeptLambdaDesugaringTest.java
@@ -5,13 +5,11 @@
 package com.android.tools.r8.desugar;
 
 import com.android.tools.r8.TestBase;
-import org.junit.Ignore;
 import org.junit.Test;
 
 /** Regression test for b/120971047. */
 public class KeptLambdaDesugaringTest extends TestBase {
 
-  @Ignore("b/120971047")
   @Test
   public void test() throws Exception {
     testForR8(Backend.DEX)
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
index be14412..9f25230 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -140,6 +139,9 @@
       if (pgConfs != null) {
         builder.addProguardConfigurationFiles(
             pgConfs.stream().map(Paths::get).collect(Collectors.toList()));
+      } else {
+        builder.setDisableTreeShaking(true);
+        builder.setDisableMinification(true);
       }
       builder.setMode(mode);
       builder.setProgramConsumer(dexIndexedConsumerSupplier.get());
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
index 1b3bc40..7f970b9 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
@@ -27,6 +27,8 @@
   private CompilationResult doRun() throws CompilationFailedException {
     R8Command command =
         R8Command.builder()
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
             .addProgramFiles(Paths.get(GMSCORE_V7_DIR, GMSCORE_APK))
             .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
             .setMinApiLevel(AndroidApiLevel.L.getLevel())
diff --git a/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java b/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
index e6ec726..b13a990 100644
--- a/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
@@ -108,6 +108,8 @@
     Builder builder =
         R8Command.builder()
             .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
             .setProgramConsumer(consumer)
             .addProgramFiles(inputJar);
     if (consumer instanceof ClassFileConsumer) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
index e681c77..58c4237 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
@@ -12,12 +12,15 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.ProguardClassFilter;
+import com.android.tools.r8.shaking.ProguardKeepRule;
 import com.android.tools.r8.shaking.RootSetBuilder;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableList;
 import java.util.concurrent.ExecutorService;
 
 public abstract class NonNullTrackerTestBase extends TestBase {
@@ -29,16 +32,23 @@
     DexApplication dexApplication =
         new ApplicationReader(app, TEST_OPTIONS, timing).read().toDirect();
     AppView<AppInfoWithSubtyping> appView =
-        new AppView<>(new AppInfoWithSubtyping(dexApplication), GraphLense.getIdentityLense());
+        new AppView<>(
+            new AppInfoWithSubtyping(dexApplication), GraphLense.getIdentityLense(), TEST_OPTIONS);
     ExecutorService executorService = ThreadUtils.getExecutorService(TEST_OPTIONS);
     RootSet rootSet =
         new RootSetBuilder(
-            appView, dexApplication, TEST_OPTIONS.proguardConfiguration.getRules(), TEST_OPTIONS)
+            appView,
+            dexApplication,
+            ImmutableList.of(ProguardKeepRule.defaultKeepAllRule(unused -> {})),
+            TEST_OPTIONS)
         .run(executorService);
     Enqueuer enqueuer =
         new Enqueuer(appView, TEST_OPTIONS, null, TEST_OPTIONS.forceProguardCompatibility);
     return enqueuer.traceApplication(
-        rootSet, TEST_OPTIONS.proguardConfiguration.getDontWarnPatterns(), executorService, timing);
+        rootSet,
+        ProguardClassFilter.empty(),
+        executorService,
+        timing);
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
index 6fea6cd..0194a1a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -42,16 +43,28 @@
       System.out.println("B instanceof B: " + (getBForFoo() instanceof B));
       System.out.println("null instanceof A: " + (getNullForFoo() instanceof A));
       System.out.println("null instanceof B: " + (getNullForFoo() instanceof B));
+      System.out.println("A[] instanceof A[]: " + (getAarrayForFoo() instanceof A[]));
+      System.out.println("A[] instanceof B[]: " + (getAarrayForFoo() instanceof B[]));
+      System.out.println("B[] instanceof A[]: " + (getBarrayForFoo() instanceof A[]));
+      System.out.println("B[] instanceof B[]: " + (getBarrayForFoo() instanceof B[]));
     }
 
     public static A getAForFoo() {
       return new A();
     }
 
+    public static A[] getAarrayForFoo() {
+      return new A[] { };
+    }
+
     public static A getBForFoo() {
       return new B();
     }
 
+    public static A[] getBarrayForFoo() {
+      return new B[0];
+    }
+
     public static A getNullForFoo() { return null; }
 
     @NeverInline
@@ -61,7 +74,11 @@
       System.out.println("B instanceof A: " + (getBForBar() instanceof A));
       System.out.println("B instanceof B: " + (getBForBar() instanceof B));
       System.out.println("null instanceof A: " + (getNullForBar(true) instanceof A));
-      System.out.print("null instanceof B: " + (getNullForBar(true) instanceof B));
+      System.out.println("null instanceof B: " + (getNullForBar(true) instanceof B));
+      System.out.println("A[] instanceof A[]: " + (getAarrayForBar() instanceof A[]));
+      System.out.println("A[] instanceof B[]: " + (getAarrayForBar() instanceof B[]));
+      System.out.println("B[] instanceof A[]: " + (getBarrayForBar() instanceof A[]));
+      System.out.println("B[] instanceof B[]: " + (getBarrayForBar() instanceof B[]));
     }
 
     @NeverInline
@@ -70,11 +87,21 @@
     }
 
     @NeverInline
+    public static A[] getAarrayForBar() {
+      return new A[] { new A(), new B() };
+    }
+
+    @NeverInline
     public static A getBForBar() {
       return new B();
     }
 
     @NeverInline
+    public static A[] getBarrayForBar() {
+      return new B[0];
+    }
+
+    @NeverInline
     public static A getNullForBar(boolean returnNull) {
       // Avoid `return null` since we will then use the fact that getNullForBar() always returns
       // null at the call site.
@@ -103,12 +130,21 @@
             "B instanceof B: true",
             "null instanceof A: false",
             "null instanceof B: false",
+            "A[] instanceof A[]: true",
+            "A[] instanceof B[]: false",
+            "B[] instanceof A[]: true",
+            "B[] instanceof B[]: true",
             "A instanceof A: true",
             "A instanceof B: false",
             "B instanceof A: true",
             "B instanceof B: true",
             "null instanceof A: false",
-            "null instanceof B: false");
+            "null instanceof B: false",
+            "A[] instanceof A[]: true",
+            "A[] instanceof B[]: false",
+            "B[] instanceof A[]: true",
+            "B[] instanceof B[]: true",
+            "");
 
     testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expected);
 
@@ -122,18 +158,18 @@
             .assertSuccessWithOutput(expected)
             .inspector();
 
+    ClassSubject testClass = inspector.clazz(TestClass.class);
+
     // With inlining we can prove that all instance-of checks succeed or fail.
-    MethodSubject fooMethodSubject =
-        inspector.clazz(TestClass.class).method("void", "foo", ImmutableList.of());
+    MethodSubject fooMethodSubject = testClass.uniqueMethodWithName("foo");
     Iterator<InstructionSubject> fooInstructionIterator =
         fooMethodSubject.iterateInstructions(InstructionSubject::isInstanceOf);
     assertEquals(0, Streams.stream(fooInstructionIterator).count());
 
     // Without inlining we cannot prove any of the instance-of checks to be trivial.
-    MethodSubject barMethodSubject =
-        inspector.clazz(TestClass.class).method("void", "bar", ImmutableList.of());
+    MethodSubject barMethodSubject = testClass.uniqueMethodWithName("bar");
     Iterator<InstructionSubject> barInstructionIterator =
         barMethodSubject.iterateInstructions(InstructionSubject::isInstanceOf);
-    assertEquals(4, Streams.stream(barInstructionIterator).count());
+    assertEquals(6, Streams.stream(barInstructionIterator).count());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
index 9cc69fa..54f9df3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
@@ -263,7 +263,7 @@
         .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
         .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
     if (!enableMinification) {
-      builder.addKeepRules("-dontobfuscate");
+      builder.noMinification();
     }
     TestRunResult result =
         builder
@@ -284,7 +284,7 @@
         .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
         .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
     if (!enableMinification) {
-      builder.addKeepRules("-dontobfuscate");
+      builder.noMinification();
     }
     TestRunResult result =
         builder
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
index b75e4ee..5a9ca75 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
@@ -124,7 +124,7 @@
       "Inner",
       "Inner"
   );
-  private static final String OUTPUT_WITH_SHRUNK_ATTRIBUTE = StringUtils.lines(
+  private static final String OUTPUT_WITH_SHRUNK_ATTRIBUTES = StringUtils.lines(
       "Local_t03",
       "InnerLocal",
       "$",
@@ -211,7 +211,7 @@
         .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
         .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
     if (!enableMinification) {
-      builder.addKeepRules("-dontobfuscate");
+      builder.noMinification();
     }
     TestRunResult result =
         builder
@@ -231,12 +231,12 @@
         .addKeepRules("-keep,allowobfuscation class **.ClassGetSimpleName*")
         // See b/119471127: some old VMs are not resilient to broken attributes.
         // Comment out the following line to reproduce b/120130435
-        // then use OUTPUT_WITH_SHRUNK_ATTRIBUTE
+        // then use OUTPUT_WITH_SHRUNK_ATTRIBUTES
         .addKeepRules("-keep,allowobfuscation class **.Outer*")
         .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
         .addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
     if (!enableMinification) {
-      builder.addKeepRules("-dontobfuscate");
+      builder.noMinification();
     }
     R8TestRunResult result =
         builder
diff --git a/src/test/java/com/android/tools/r8/jar/UnicodeSetRegression/UnicodeSetRegressionTest.java b/src/test/java/com/android/tools/r8/jar/UnicodeSetRegression/UnicodeSetRegressionTest.java
index 3107636..689824f 100644
--- a/src/test/java/com/android/tools/r8/jar/UnicodeSetRegression/UnicodeSetRegressionTest.java
+++ b/src/test/java/com/android/tools/r8/jar/UnicodeSetRegression/UnicodeSetRegressionTest.java
@@ -50,6 +50,8 @@
     R8Command.Builder builder =
         R8Command.builder()
             .addProgramFiles(Paths.get(JAR_FILE))
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
             .setOutput(Paths.get(combinedInput.toString()), OutputMode.DexIndexed);
     AndroidAppConsumers compatSink = new AndroidAppConsumers(builder);
     // Ignore missing classes since we don't want to link to the IBM text library.
@@ -87,7 +89,6 @@
   @Test
   public void testUnicodeSetFromJarToCF() throws Throwable {
     Path combinedInput = temp.getRoot().toPath().resolve("all.zip");
-    Path oatFile = temp.getRoot().toPath().resolve("all.oat");
     R8Command.Builder builder =
         R8Command.builder()
             .addProgramFiles(Paths.get(JAR_FILE))
@@ -96,6 +97,6 @@
     AndroidAppConsumers compatSink = new AndroidAppConsumers(builder);
     // Ignore missing classes since we don't want to link to the IBM text library.
     ToolHelper.runR8(builder.build(), options -> options.ignoreMissingClasses = true);
-    AndroidApp result = compatSink.build();
+    compatSink.build();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
index fd68772..1c23340 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
@@ -40,6 +41,9 @@
             .addProgramFiles(inputJar)
             .setOutput(outputJar, OutputMode.ClassFile)
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
+            .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
             .build(),
         options -> options.enableCfFrontend = true);
     ProcessResult processResult = ToolHelper.runJava(outputJar, main);
diff --git a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
index 4dba00d..33f28fc 100644
--- a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
@@ -671,7 +671,8 @@
     Assert.assertTrue(dxOutput.stderr.contains(name));
     ProcessResult d8Output = runOnArtD8Raw(app, library, MAIN_CLASS);
     Assert.assertTrue(d8Output.stderr.contains(name));
-    ProcessResult r8Output = runOnArtR8Raw(app, library, MAIN_CLASS, null, null);
+    ProcessResult r8Output = runOnArtR8Raw(app, library, MAIN_CLASS,
+        noShrinkingNoMinificationProguardConfiguration(), null);
     Assert.assertTrue(r8Output.stderr.contains(name));
     ProcessResult r8ShakenOutput = runOnArtR8Raw(app, library, MAIN_CLASS,
         keepMainProguardConfiguration(MAIN_CLASS), null);
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java b/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java
index 1463b65..458ddbc 100644
--- a/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java
@@ -80,6 +80,8 @@
         ToolHelper.runR8(
             ToolHelper.prepareR8CommandBuilder(originalApplication, emptyConsumer(backend))
                 .addLibraryFiles(runtimeJar(backend))
+                .setDisableTreeShaking(true)
+                .setDisableMinification(true)
                 .build());
 
     CodeInspector inspector =
diff --git a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
index 4919227..ac48680 100644
--- a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
+++ b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
@@ -49,7 +49,7 @@
   public TemporaryFolder tmpOutputDir = ToolHelper.getTemporaryFolderForTest();
 
   private AndroidApp compileWithD8(Path intputPath, Path outputPath)
-      throws IOException, CompilationFailedException {
+      throws CompilationFailedException {
     D8Command.Builder builder =
         D8Command.builder()
             .setMinApiLevel(AndroidApiLevel.O.getLevel())
@@ -61,7 +61,7 @@
   }
 
   private AndroidApp compileWithR8(Path inputPath, Path outputPath, Path keepRulesPath)
-      throws IOException, CompilationFailedException {
+      throws CompilationFailedException {
     return ToolHelper.runR8(
         R8Command.builder()
             .addProgramFiles(inputPath)
diff --git a/src/test/java/com/android/tools/r8/jsr45/keep-rules-1.txt b/src/test/java/com/android/tools/r8/jsr45/keep-rules-1.txt
index 04e2d30..f659607 100644
--- a/src/test/java/com/android/tools/r8/jsr45/keep-rules-1.txt
+++ b/src/test/java/com/android/tools/r8/jsr45/keep-rules-1.txt
@@ -2,6 +2,8 @@
 # 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.
 
+-keepattributes SourceDebugExtension
+
 -dontshrink
 -dontobfuscate
 
diff --git a/src/test/java/com/android/tools/r8/jsr45/keep-rules-2.txt b/src/test/java/com/android/tools/r8/jsr45/keep-rules-2.txt
index 63b52e4..d04091d 100644
--- a/src/test/java/com/android/tools/r8/jsr45/keep-rules-2.txt
+++ b/src/test/java/com/android/tools/r8/jsr45/keep-rules-2.txt
@@ -2,6 +2,8 @@
 # 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.
 
+-keepattributes SourceDebugExtension
+
 -dontshrink
 # allow access modification to enable minification
 -allowaccessmodification
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
index c7a8ef2..b8c4b47 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
@@ -22,7 +22,6 @@
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class KotlinLambdaMergingTest extends AbstractR8KotlinTestBase {
@@ -455,7 +454,6 @@
     });
   }
 
-  @Ignore("b/121107286: assertion failure in VerticalClassMerger#verifyGraphLense")
   @Test
   public void testSingleton() throws Exception {
     final String mainClassName = "lambdas_singleton.MainKt";
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index 48b23fe..11e92e1 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -96,6 +96,8 @@
     Path mainDexListOutput = temp.getRoot().toPath().resolve("main-dex-output.txt");
     R8Command command =
         ToolHelper.prepareR8CommandBuilder(readClasses(HelloWorldMain.class))
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
             .addMainDexRulesFiles(mainDexRules)
             .setMainDexListOutputPath(mainDexListOutput)
             .setOutput(temp.getRoot().toPath(), OutputMode.DexIndexed)
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 2674caf..ff948aa 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.maindexlist;
 
-import static com.android.tools.r8.resolution.SingleTargetLookupTest.appInfo;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -474,7 +473,11 @@
       jasminBuilder.addClass(name);
     }
     Path input = temp.newFolder().toPath().resolve("input.zip");
-    ToolHelper.runR8(jasminBuilder.build()).writeToZip(input, OutputMode.DexIndexed);
+    ToolHelper.runR8(
+        ToolHelper.prepareR8CommandBuilder(jasminBuilder.build())
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
+            .build()).writeToZip(input, OutputMode.DexIndexed);
 
     // Test with empty main dex list.
     runDeterministicTest(input, null, true);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index 7df3a29..31170d3 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -23,6 +24,7 @@
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.PrintStream;
@@ -321,6 +323,10 @@
                 Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
             .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
             .setOutput(out, OutputMode.DexIndexed)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
+            .addProguardConfiguration(
+                ImmutableList.of("-keepattributes *Annotation*"), Origin.unknown())
             .addMainDexRulesFiles(mainDexRules)
             .setMainDexListConsumer((string, handler) -> r8MainDexListOutput.content = string)
             .build();
diff --git a/src/test/java/com/android/tools/r8/maindexlist/checkdiscard/MainDexListCheckDiscard.java b/src/test/java/com/android/tools/r8/maindexlist/checkdiscard/MainDexListCheckDiscard.java
index a68ae3b..cd85cd4 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/checkdiscard/MainDexListCheckDiscard.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/checkdiscard/MainDexListCheckDiscard.java
@@ -36,6 +36,8 @@
             .addMainDexRules(ImmutableList.of(checkDiscardRule), Origin.unknown())
             .setOutput(temp.getRoot().toPath(), OutputMode.DexIndexed)
             .setMode(CompilationMode.RELEASE)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
             .build();
     try {
       ToolHelper.runR8(command);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java b/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
index 1d8f7d8..0acd733 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graphinfo.GraphConsumer;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
@@ -85,6 +85,8 @@
   public void runTestWithR8(GraphConsumer consumer, String rule) throws Exception {
     R8TestBuilder builder =
         testForR8(Backend.DEX)
+            .noTreeShaking()
+            .noMinification()
             .setMinApi(AndroidApiLevel.K)
             .addProgramClasses(CLASSES)
             .addMainDexRules(keepMainProguardConfiguration(HelloWorldMain.class))
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
index 7fc9cd5..4c54504 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
@@ -95,7 +95,9 @@
     R8Command.Builder builder =
         R8Command.builder()
             .setOutput(Paths.get(out), TestBase.outputMode(backend))
-            .addLibraryFiles(JAR_LIBRARY, TestBase.runtimeJar(backend));
+            .addLibraryFiles(JAR_LIBRARY, TestBase.runtimeJar(backend))
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true);
     if (backend == Backend.DEX) {
       builder.setMinApiLevel(minApiLevel);
     }
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
new file mode 100644
index 0000000..179f9c6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.Streams;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EnumMinificationKotlinTest extends KotlinTestBase {
+  private static final String FOLDER = "minify_enum";
+  private static final String MAIN_CLASS_NAME = "minify_enum.MainKt";
+  private static final String ENUM_CLASS_NAME = "minify_enum.MinifyEnum";
+
+  private final Backend backend;
+  private final boolean minify;
+
+  @Parameterized.Parameters(name = "Backend: {0} target: {1} minify: {2}")
+  public static Collection<Object[]> data() {
+    return buildParameters(Backend.values(), KotlinTargetVersion.values(), BooleanUtils.values());
+  }
+
+  public EnumMinificationKotlinTest(
+      Backend backend, KotlinTargetVersion targetVersion, boolean minify) {
+    super(targetVersion);
+    this.backend = backend;
+    this.minify = minify;
+  }
+
+  @Test
+  public void b121221542() throws Exception {
+    R8TestBuilder builder = testForR8(backend)
+        .addProgramFiles(getKotlinJarFile(FOLDER))
+        .addProgramFiles(getJavaJarFile(FOLDER))
+        .addKeepMainRule(MAIN_CLASS_NAME);
+    if (!minify) {
+      builder.noMinification();
+    }
+    CodeInspector inspector = builder.run(MAIN_CLASS_NAME).inspector();
+    ClassSubject enumClass = inspector.clazz(ENUM_CLASS_NAME);
+    assertThat(enumClass, isPresent());
+    assertEquals(minify, enumClass.isRenamed());
+    MethodSubject clinit = enumClass.clinit();
+    assertThat(clinit, isPresent());
+    assertEquals(0,
+        Streams.stream(clinit.iterateInstructions(InstructionSubject::isThrow)).count());
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index 3d9edfc..4f4639a 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.optimize.ClassAndMemberPublicizer;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.ProguardConfiguration;
-import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.shaking.RootSetBuilder;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
 import com.android.tools.r8.utils.InternalOptions;
@@ -45,7 +44,6 @@
 
   private DexApplication program;
   DexItemFactory dexItemFactory;
-  private AppView<AppInfoWithSubtyping> appView;
 
   NamingTestBase(
       String test,
@@ -62,21 +60,21 @@
   public void readApp() throws IOException, ExecutionException {
     program = ToolHelper.buildApplication(ImmutableList.of(appFileName));
     dexItemFactory = program.dexItemFactory;
-    appView = new AppView<>(new AppInfoWithSubtyping(program), GraphLense.getIdentityLense());
   }
 
-  NamingLens runMinifier(List<Path> configPaths)
-      throws IOException, ProguardRuleParserException, ExecutionException {
+  NamingLens runMinifier(List<Path> configPaths) throws ExecutionException {
     ProguardConfiguration configuration =
         ToolHelper.loadProguardConfiguration(dexItemFactory, configPaths);
     InternalOptions options = new InternalOptions(configuration, new Reporter());
 
     ExecutorService executor = ThreadUtils.getExecutorService(1);
 
+    AppView<AppInfoWithSubtyping> appView =
+        new AppView<>(new AppInfoWithSubtyping(program), GraphLense.getIdentityLense(), options);
     RootSet rootSet =
         new RootSetBuilder(appView, program, configuration.getRules(), options).run(executor);
 
-    if (options.proguardConfiguration.isAccessModificationAllowed()) {
+    if (options.getProguardConfiguration().isAccessModificationAllowed()) {
       ClassAndMemberPublicizer.run(executor, timing, program, appView, rootSet);
       rootSet =
           new RootSetBuilder(appView, program, configuration.getRules(), options).run(executor);
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
index e7b0554..2ef1f5e 100644
--- a/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.debug.DebugTestBase;
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.debug.DexDebugTestConfig;
+import com.android.tools.r8.shaking.ProguardKeepRule;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
@@ -41,6 +42,7 @@
           ToolHelper.addProguardConfigurationConsumer(
                   R8Command.builder(),
                   pgConfig -> {
+                    pgConfig.addRule(ProguardKeepRule.defaultKeepAllRule(unused -> {}));
                     pgConfig.setRenameSourceFileAttribute(TEST_FILE);
                     pgConfig.addKeepAttributePatterns(
                         ImmutableList.of("SourceFile", "LineNumberTable"));
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingTest.java
index e490c6f..1038f5c 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingTest.java
@@ -261,6 +261,8 @@
             ToolHelper.addProguardConfigurationConsumer(
                 getCommandForApps(out, flag, NAMING001_JAR).setDisableMinification(true),
                 pgConfig -> {
+                  pgConfig.disableShrinking();
+                  pgConfig.disableObfuscation();
                   pgConfig.setPrintMapping(true);
                   pgConfig.setPrintMappingFile(proguardMap);
                 })
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ShrunkenLibraryTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ShrunkenLibraryTest.java
new file mode 100644
index 0000000..61250a2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ShrunkenLibraryTest.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.naming.applymapping;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class ShrunkenLibraryTest extends TestBase {
+
+  private static Path mappingFile;
+
+  @Before
+  public void setup() throws Exception {
+    // Mapping file that describes that Runnable has been renamed to A.
+    mappingFile = temp.newFile("mapping.txt").toPath();
+    FileUtils.writeTextFile(
+        mappingFile, Runnable.class.getTypeName() + " -> " + A.class.getTypeName() + ":");
+  }
+
+  @Test
+  public void testProguard() throws Exception {
+    testForProguard()
+        .addProgramClasses(ShrunkenLibraryTestClass.class)
+        .addKeepRules(
+            "-keep class " + ShrunkenLibraryTestClass.class.getTypeName() + " {",
+            "  public void method(" + Runnable.class.getTypeName() + ");",
+            "}",
+            "-applymapping " + mappingFile.toAbsolutePath())
+        .compile()
+        .inspect(this::inspect);
+  }
+
+  @Ignore("b/121305642")
+  @Test
+  public void test() throws Exception {
+    testForR8(Backend.DEX)
+        .addProgramClasses(ShrunkenLibraryTestClass.class)
+        .addKeepRules(
+            "-keep class " + ShrunkenLibraryTestClass.class.getTypeName() + " {",
+            "  public void method(" + Runnable.class.getTypeName() + ");",
+            "}",
+            "-applymapping " + mappingFile.toAbsolutePath())
+        .compile()
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    MethodSubject methodSubject =
+        inspector.clazz(ShrunkenLibraryTestClass.class).uniqueMethodWithName("method");
+    assertThat(methodSubject, isPresent());
+    assertEquals(
+        A.class.getTypeName(), methodSubject.getMethod().method.proto.parameters.toSourceString());
+  }
+}
+
+class ShrunkenLibraryTestClass {
+
+  public void method(Runnable obj) {}
+}
+
+class A {}
diff --git a/src/test/java/com/android/tools/r8/regress/b69906048/Regress69906048Test.java b/src/test/java/com/android/tools/r8/regress/b69906048/Regress69906048Test.java
index f1d846e..06c0ca4 100644
--- a/src/test/java/com/android/tools/r8/regress/b69906048/Regress69906048Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b69906048/Regress69906048Test.java
@@ -5,6 +5,7 @@
 
 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.google.common.collect.ImmutableList;
 import org.junit.Assert;
@@ -14,8 +15,14 @@
 
   @Test
   public void buildWithD8AndRunWithDalvikOrArt() throws Exception {
-    AndroidApp androidApp = compileWithR8(
-        ImmutableList.of(ClassWithAnnotations.class, AnAnnotation.class),
+    AndroidApp androidApp = ToolHelper.runR8(
+        ToolHelper.prepareR8CommandBuilder(
+            readClasses(ClassWithAnnotations.class, AnAnnotation.class))
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
+            .addProguardConfiguration(
+                ImmutableList.of("-keepattributes *Annotation*"), Origin.unknown())
+            .build(),
         options -> options.minApiLevel = ToolHelper.getMinApiLevelForDexVm().getLevel());
     String result = runOnArt(androidApp, ClassWithAnnotations.class);
     Assert.assertEquals("@" + AnAnnotation.class.getCanonicalName() + "()", result);
diff --git a/src/test/java/com/android/tools/r8/regress/b72485384/Regress72485384Test.java b/src/test/java/com/android/tools/r8/regress/b72485384/Regress72485384Test.java
index ba65d92..be733d3 100644
--- a/src/test/java/com/android/tools/r8/regress/b72485384/Regress72485384Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b72485384/Regress72485384Test.java
@@ -36,8 +36,6 @@
           {baseConfig + "-dontshrink", null},
           {baseConfig + "-dontshrink -dontobfuscate", null},
           {baseConfig + "-dontobfuscate", null},
-          {"", null},
-          {"-dontshrink", null},
           {"-keep class DoesNotExist -dontshrink", "ClassNotFoundException"}
         });
   }
diff --git a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
index 72ed9d6..8f3eea0 100644
--- a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
+++ b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
@@ -37,7 +37,9 @@
     ProcessResult d8Result =
         runOnArtRaw(compileWithD8(app), Regress78493232Dump_WithPhi.CLASS_NAME);
     ProcessResult r8Result =
-        runOnArtRaw(compileWithR8(app), Regress78493232Dump_WithPhi.CLASS_NAME);
+        runOnArtRaw(
+            compileWithR8(app, "-dontshrink\n-dontobfuscate\n"),
+            Regress78493232Dump_WithPhi.CLASS_NAME);
     String proguardConfig =
         keepMainProguardConfiguration(Regress78493232Dump_WithPhi.CLASS_NAME) + "-dontobfuscate\n";
     ProcessResult r8ShakenResult =
diff --git a/src/test/java/com/android/tools/r8/regress/b80262475/B80262475.java b/src/test/java/com/android/tools/r8/regress/b80262475/B80262475.java
new file mode 100644
index 0000000..b3fc08f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b80262475/B80262475.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2018 the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b80262475;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.LongToInt;
+import com.android.tools.r8.code.Return;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.code.ReturnWide;
+import com.android.tools.r8.code.Throw;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+class TestClass {
+  public static void f(long[] a) {
+    int i = (int) a[0];
+    a[i] = a[0];
+  }
+}
+
+public class B80262475 extends TestBase {
+
+  private boolean overlappingLongToIntInputAndOutput(Instruction instruction) {
+    if (instruction instanceof LongToInt) {
+      LongToInt longToInt = (LongToInt) instruction;
+      return longToInt.A == longToInt.B;
+    }
+    return false;
+  }
+
+  @Test
+  public void testLongToIntOverlap()
+      throws IOException, CompilationFailedException, ExecutionException {
+    MethodSubject method = getMethodSubject(AndroidApiLevel.L);
+    Instruction[] instructions = method.getMethod().getCode().asDexCode().instructions;
+    for (Instruction instruction : instructions) {
+      assertFalse(overlappingLongToIntInputAndOutput(instruction));
+    }
+  }
+
+  private MethodSubject getMethodSubject(AndroidApiLevel level)
+      throws CompilationFailedException, IOException, ExecutionException {
+    CodeInspector inspector = testForD8()
+        .addProgramClasses(TestClass.class)
+        .setMinApi(level)
+        .compile()
+        .inspector();
+    ClassSubject clazz = inspector.clazz(TestClass.class);
+    assertThat(clazz, isPresent());
+    MethodSubject method = clazz.method("void", "f", ImmutableList.of("long[]"));
+    assertThat(method, isPresent());
+    return method;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java
index fc31713..d70c724 100644
--- a/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java
@@ -10,12 +10,14 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidAppConsumers;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.LineNumberTable;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Streams;
 import it.unimi.dsi.fastutil.ints.IntCollection;
 import java.io.IOException;
@@ -51,6 +53,8 @@
             .setProgramConsumer(sink.wrapProgramConsumer(emptyConsumer(backend)))
             .setDisableMinification(true)
             .setDisableTreeShaking(true)
+            .addProguardConfiguration(
+                ImmutableList.of("-keepattributes LineNumberTable"), Origin.unknown())
             .build(),
         options -> options.lineNumberOptimization = LineNumberOptimization.OFF);
     CodeInspector inspector = new CodeInspector(sink.build());
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index a84f585..7ec1613 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -105,7 +105,8 @@
     AndroidApp app = readClassesAndAsmDump(CLASSES, ASM_CLASSES);
     DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
     AppView<? extends AppInfoWithSubtyping> appView =
-        new AppView<>(new AppInfoWithSubtyping(application), GraphLense.getIdentityLense());
+        new AppView<>(
+            new AppInfoWithSubtyping(application), GraphLense.getIdentityLense(), options);
 
     ExecutorService executor = Executors.newSingleThreadExecutor();
     RootSet rootSet =
diff --git a/src/test/java/com/android/tools/r8/resource/DataResourceTest.java b/src/test/java/com/android/tools/r8/resource/DataResourceTest.java
index a58676d..81b8447 100644
--- a/src/test/java/com/android/tools/r8/resource/DataResourceTest.java
+++ b/src/test/java/com/android/tools/r8/resource/DataResourceTest.java
@@ -59,6 +59,8 @@
     Path r8Out = temp.getRoot().toPath().resolve("r8out.jar");
     R8Command.Builder builder =
         R8Command.builder()
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
             .addProgramFiles(inputJar)
             .addProguardConfiguration(ImmutableList.of("-keepdirectories"), Origin.unknown())
             .setProgramConsumer(
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
index fff9ac3..4b88194 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
@@ -166,6 +166,8 @@
     R8Command command =
         ToolHelper.prepareR8CommandBuilder(readClasses(ClassWithAssertions.class))
             .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
             .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
             .setOutput(outputJar, OutputMode.ClassFile)
             .build();
@@ -213,6 +215,8 @@
                 ToolHelper.getClassAsBytes(ChromuimAssertionHookMock.class), Origin.unknown())
             .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
             .setMode(CompilationMode.DEBUG)
+            .setDisableTreeShaking(true)
+            .setDisableMinification(true)
             .build());
   }
 
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
index fc40d4f..a6c0d57 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
@@ -60,7 +60,7 @@
     builder.addLibraryResourceProvider(PreloadedClassFileProvider.fromClassData(
         "Lcom/android/tools/r8/rewrite/staticvalues/inlibraries/LibraryClass;",
         compileTimeLibrary.buildClasses().get(0)));
-    AndroidApp app = compileWithR8(builder.build());
+    AndroidApp app = compileWithR8(builder.build(), "-dontshrink\n-dontobfuscate\n");
 
     // Build the third version of LibraryClass
     SmaliBuilder runtimeLibrary = new SmaliBuilder(LibraryClass.class.getCanonicalName());
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
index 4d00bba..24789b0 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
@@ -130,6 +130,8 @@
     AndroidApp app =
         ToolHelper.runR8(
             ToolHelper.prepareR8CommandBuilder(builder.build(), emptyConsumer(backend))
+                .setDisableTreeShaking(true)
+                .setDisableMinification(true)
                 .addLibraryFiles(runtimeJar(backend))
                 .build());
 
@@ -198,6 +200,8 @@
     AndroidApp app =
         ToolHelper.runR8(
             ToolHelper.prepareR8CommandBuilder(builder.build(), emptyConsumer(backend))
+                .setDisableTreeShaking(true)
+                .setDisableMinification(true)
                 .addLibraryFiles(runtimeJar(backend))
                 .build());
 
@@ -275,6 +279,8 @@
     AndroidApp app =
         ToolHelper.runR8(
             ToolHelper.prepareR8CommandBuilder(appBuilder.build(), emptyConsumer(backend))
+                .setDisableTreeShaking(true)
+                .setDisableMinification(true)
                 .addLibraryFiles(runtimeJar(backend))
                 .build());
 
diff --git a/src/test/java/com/android/tools/r8/shaking/EnclosingMethodTest.java b/src/test/java/com/android/tools/r8/shaking/EnclosingMethodTest.java
index c14cef5..4e121a8 100644
--- a/src/test/java/com/android/tools/r8/shaking/EnclosingMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/EnclosingMethodTest.java
@@ -49,6 +49,8 @@
   private final boolean enableMinification;
   private Collection<Path> classPaths;
   private static final String JAVA_OUTPUT = "-Returned-null-" + System.lineSeparator();
+  private static final String OUTPUT_WITH_SHRUNK_ATTRIBUTES =
+      "com.android.tools.r8.shaking.GetNameClass$1" + System.lineSeparator();
   private static final Class<?> MAIN = GetNameMain.class;
 
   @Parameterized.Parameters(name = "Backend: {0} minification: {1}")
@@ -88,18 +90,10 @@
         .addKeepRules("-keep class **.GetName*")
         .addKeepRules("-keepattributes InnerClasses,EnclosingMethod");
     if (!enableMinification) {
-      builder.addKeepRules("-dontobfuscate");
+      builder.noMinification();
     }
 
     R8TestRunResult result = builder.run(MAIN);
-    if (backend == Backend.DEX) {
-      if (ToolHelper.getDexVm().getVersion().isNewerThan(Version.V4_4_4)
-          && ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V6_0_1)) {
-        result.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
-        return;
-      }
-    }
-
-    result.assertSuccessWithOutput(JAVA_OUTPUT);
+    result.assertSuccessWithOutput(OUTPUT_WITH_SHRUNK_ATTRIBUTES);
   }
 }
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 de10b45..ff84486 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -1851,6 +1851,30 @@
   }
 
   @Test
+  public void parse_dump_withoutFile() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-dump"
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
+        "Ignoring", "-dump");
+  }
+
+  @Test
+  public void parse_dump_withFile() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-dump class_files.txt"
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
+        "Ignoring", "-dump");
+  }
+
+  @Test
   public void parse_android() throws Exception {
     Path proguardConfig = writeTextToTempFile(
         "-android"
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
index 36993e7..279194a 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
@@ -13,18 +13,20 @@
 
 import com.android.tools.r8.KotlinTestBase;
 import com.android.tools.r8.R8TestBuilder;
-import com.android.tools.r8.R8TestRunResult;
-import com.android.tools.r8.TestRunResult;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -34,6 +36,7 @@
   private static final String FOLDER = "internal_annotation";
   private static final String MAIN_CLASS_NAME = "internal_annotation.MainKt";
   private static final String ANNOTATION_NAME = "internal_annotation.Annotation";
+  private static final String IMPL_CLASS_NAME = "internal_annotation.Impl";
   private static final String KEEP_ANNOTATIONS = "-keepattributes *Annotation*";
 
   private static final String JAVA_OUTPUT = StringUtils.lines(
@@ -46,6 +49,13 @@
       "null"
   );
 
+  private static final Map<String, String> EXPECTED_ANNOTATION_VALUES = ImmutableMap.of(
+      "f1", "2",
+      "f2", "Impl::Annotation::field2",
+      "f3", "3]",
+      "f4", "field4]"
+  );
+
   private final Backend backend;
   private final boolean minify;
 
@@ -87,11 +97,9 @@
     if (!minify) {
       builder.noMinification();
     }
-    TestRunResult result = builder.run(MAIN_CLASS_NAME);
 
-    result.assertSuccessWithOutput(JAVA_OUTPUT);
-
-    CodeInspector inspector = result.inspector();
+    CodeInspector inspector =
+        builder.run(MAIN_CLASS_NAME).assertSuccessWithOutput(JAVA_OUTPUT).inspector();
     ClassSubject clazz = inspector.clazz(ANNOTATION_NAME);
     assertThat(clazz, isPresent());
     assertThat(clazz, not(isRenamed()));
@@ -107,6 +115,12 @@
     MethodSubject f4 = clazz.uniqueMethodWithName("f4");
     assertThat(f4, isPresent());
     assertThat(f4, not(isRenamed()));
+
+    ClassSubject impl = inspector.clazz(IMPL_CLASS_NAME);
+    assertThat(impl, isPresent());
+    AnnotationSubject anno = impl.annotation(ANNOTATION_NAME);
+    assertThat(anno, isPresent());
+    inspectAnnotationInstantiation(anno, ImmutableSet.of("f1", "f2", "f3", "f4"));
   }
 
   @Test
@@ -124,24 +138,9 @@
     if (!minify) {
       builder.noMinification();
     }
-    R8TestRunResult result = builder.run(MAIN_CLASS_NAME);
 
-    if (backend == Backend.DEX) {
-      if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)) {
-        result.assertFailureWithErrorThatMatches(
-            containsString("failure in processEncodedAnnotation"));
-        return;
-      }
-      if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V6_0_1)) {
-        result.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
-        return;
-      }
-      result.assertSuccessWithOutput(OUTPUT_WITHOUT_ANNOTATION);
-    } else {
-      result.assertSuccessWithOutput(JAVA_OUTPUT);
-    }
-
-    CodeInspector inspector = result.inspector();
+    CodeInspector inspector =
+        builder.run(MAIN_CLASS_NAME).assertSuccessWithOutput(JAVA_OUTPUT).inspector();
     ClassSubject clazz = inspector.clazz(ANNOTATION_NAME);
     assertThat(clazz, isPresent());
     assertEquals(minify, clazz.isRenamed());
@@ -155,6 +154,12 @@
     assertThat(f3, not(isPresent()));
     MethodSubject f4 = clazz.uniqueMethodWithName("f4");
     assertThat(f4, not(isPresent()));
+
+    ClassSubject impl = inspector.clazz(IMPL_CLASS_NAME);
+    assertThat(impl, isPresent());
+    AnnotationSubject anno = impl.annotation(ANNOTATION_NAME);
+    assertThat(anno, isPresent());
+    inspectAnnotationInstantiation(anno, ImmutableSet.of("f1", "f2"));
   }
 
   @Test
@@ -167,24 +172,9 @@
     if (!minify) {
       builder.noMinification();
     }
-    R8TestRunResult result = builder.run(MAIN_CLASS_NAME);
 
-    if (backend == Backend.DEX) {
-      if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)) {
-        result.assertFailureWithErrorThatMatches(
-            containsString("failure in processEncodedAnnotation"));
-        return;
-      }
-      if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V6_0_1)) {
-        result.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
-        return;
-      }
-      result.assertSuccessWithOutput(OUTPUT_WITHOUT_ANNOTATION);
-    } else {
-      result.assertSuccessWithOutput(JAVA_OUTPUT);
-    }
-
-    CodeInspector inspector = result.inspector();
+    CodeInspector inspector =
+        builder.run(MAIN_CLASS_NAME).assertSuccessWithOutput(JAVA_OUTPUT).inspector();
     ClassSubject clazz = inspector.clazz(ANNOTATION_NAME);
     assertThat(clazz, isPresent());
     assertEquals(minify, clazz.isRenamed());
@@ -198,6 +188,12 @@
     assertThat(f3, not(isPresent()));
     MethodSubject f4 = clazz.uniqueMethodWithName("f4");
     assertThat(f4, not(isPresent()));
+
+    ClassSubject impl = inspector.clazz(IMPL_CLASS_NAME);
+    assertThat(impl, isPresent());
+    AnnotationSubject anno = impl.annotation(ANNOTATION_NAME);
+    assertThat(anno, isPresent());
+    inspectAnnotationInstantiation(anno, ImmutableSet.of("f1", "f2"));
   }
 
   @Test
@@ -209,10 +205,9 @@
     if (!minify) {
       builder.noMinification();
     }
-    TestRunResult result = builder.run(MAIN_CLASS_NAME);
-    result.assertSuccessWithOutput(OUTPUT_WITHOUT_ANNOTATION);
 
-    CodeInspector inspector = result.inspector();
+    CodeInspector inspector =
+        builder.run(MAIN_CLASS_NAME).assertSuccessWithOutput(OUTPUT_WITHOUT_ANNOTATION).inspector();
     ClassSubject clazz = inspector.clazz(ANNOTATION_NAME);
     assertThat(clazz, isPresent());
     assertEquals(minify, clazz.isRenamed());
@@ -224,6 +219,25 @@
     assertThat(f3, not(isPresent()));
     MethodSubject f4 = clazz.uniqueMethodWithName("f4");
     assertThat(f4, not(isPresent()));
+
+    ClassSubject impl = inspector.clazz(IMPL_CLASS_NAME);
+    assertThat(impl, isPresent());
+    AnnotationSubject anno = impl.annotation(ANNOTATION_NAME);
+    assertThat(anno, not(isPresent()));
+  }
+
+  private void inspectAnnotationInstantiation(
+      AnnotationSubject annotationSubject, Set<String> expectedFields) {
+    int count = 0;
+    for (DexAnnotationElement element : annotationSubject.getAnnotation().elements) {
+      String fieldName = element.name.toString();
+      if (expectedFields.contains(fieldName)) {
+        count++;
+        String expectedValue = EXPECTED_ANNOTATION_VALUES.get(fieldName);
+        assertThat(element.value.toString(), containsString(expectedValue));
+      }
+    }
+    assertEquals(expectedFields.size(), count);
   }
 
 }
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 07a9493..6504169 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
@@ -108,7 +108,7 @@
     builder.addProguardConfiguration(
         ImmutableList.of(keepMainProguardConfiguration(mainClass, true, false)), Origin.unknown());
     builder.addProguardConfiguration(
-        ImmutableList.of("-keep class " + annotationClass.getCanonicalName() + " { }"),
+        ImmutableList.of("-keep class " + annotationClass.getCanonicalName() + " { *; }"),
         Origin.unknown());
     if (keepAnnotations) {
       builder.addProguardConfiguration(ImmutableList.of("-keepattributes *Annotation*"),
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/applymapping/IfRuleWithApplyMappingTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/applymapping/IfRuleWithApplyMappingTest.java
new file mode 100644
index 0000000..49679c4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/applymapping/IfRuleWithApplyMappingTest.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.ifrule.applymapping;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class IfRuleWithApplyMappingTest extends TestBase {
+
+  private static Path mappingFile;
+
+  @Before
+  public void setup() throws Exception {
+    // Mapping file that describes that Runnable has been renamed to A.
+    mappingFile = temp.newFolder().toPath().resolve("mapping.txt");
+    FileUtils.writeTextFile(
+        mappingFile, Runnable.class.getTypeName() + " -> " + A.class.getTypeName() + ":");
+  }
+
+  @Test
+  public void testProguard() throws Exception {
+    testForProguard()
+        .addProgramClasses(IfRuleWithApplyMappingTestClass.class)
+        .addKeepMainRule(IfRuleWithApplyMappingTestClass.class)
+        .addKeepRules(
+            "-if class " + IfRuleWithApplyMappingTestClass.class.getTypeName(),
+            "-keep class " + IfRuleWithApplyMappingTestClass.class.getTypeName() + " {",
+            "  public void method(" + Runnable.class.getTypeName() + ");",
+            "}",
+            "-applymapping " + mappingFile.toAbsolutePath())
+        .compile()
+        .inspect(this::inspect);
+  }
+
+  @Ignore("b/117403482")
+  @Test
+  public void testR8() throws Exception {
+    testForR8(Backend.DEX)
+        .addProgramClasses(IfRuleWithApplyMappingTestClass.class)
+        .addKeepMainRule(IfRuleWithApplyMappingTestClass.class)
+        .addKeepRules(
+            "-if class " + IfRuleWithApplyMappingTestClass.class.getTypeName(),
+            "-keep class " + IfRuleWithApplyMappingTestClass.class.getTypeName() + " {",
+            "  public void method(" + Runnable.class.getTypeName() + ");",
+            "}",
+            "-applymapping " + mappingFile.toAbsolutePath())
+        .compile()
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    MethodSubject methodSubject =
+        inspector.clazz(IfRuleWithApplyMappingTestClass.class).uniqueMethodWithName("method");
+    assertThat(methodSubject, isPresent());
+    assertEquals(
+        A.class.getTypeName(), methodSubject.getMethod().method.proto.parameters.toSourceString());
+  }
+}
+
+class IfRuleWithApplyMappingTestClass {
+
+  public static void main(String[] args) {}
+
+  public void method(Runnable obj) {}
+}
+
+class A {}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptMethodTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptMethodTest.java
new file mode 100644
index 0000000..35083c7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptMethodTest.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.keptgraph;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.shaking.CollectingGraphConsumer;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+class Main {
+
+  public static void foo() {
+    bar();
+  }
+
+  @NeverInline
+  public static void bar() {
+    System.out.println("called bar");
+  }
+
+  @NeverInline
+  public static void baz() {
+    System.out.println("called baz");
+  }
+}
+
+@RunWith(Parameterized.class)
+public class KeptMethodTest extends TestBase {
+
+  private final Backend backend;
+
+  @Parameters(name = "{0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public KeptMethodTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void testKeptMethod()
+      throws NoSuchMethodException, CompilationFailedException, IOException, ExecutionException {
+    MethodReference fooMethod = Reference.methodFromMethod(Main.class.getMethod("foo"));
+    MethodReference barMethod = Reference.methodFromMethod(Main.class.getMethod("bar"));
+    MethodReference bazMethod = Reference.methodFromMethod(Main.class.getMethod("baz"));
+
+    CollectingGraphConsumer graphConsumer = new CollectingGraphConsumer(null);
+    R8TestBuilder builder =
+        testForR8(backend)
+            .setKeptGraphConsumer(graphConsumer)
+            .enableInliningAnnotations()
+            .addProgramClasses(Main.class)
+            .addKeepMethodRules(fooMethod);
+
+    CodeInspector codeInspector = builder.compile().inspector();
+    GraphInspector graphInspector = new GraphInspector(graphConsumer, codeInspector);
+
+    // The only root should be the method rule.
+    assertEquals(1, graphInspector.getRoots().size());
+    assertTrue(graphInspector.isPresent(fooMethod));
+    assertTrue(graphInspector.isRenamed(barMethod));
+    assertFalse(graphInspector.isPresent(bazMethod));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
index 48cdb05..95963a0 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
@@ -48,7 +48,7 @@
     Path flagToKeepTestRunner = Paths.get(ToolHelper.EXAMPLES_DIR, M_I_PKG, "keep-rules.txt");
     R8TestBuilder builder = testForR8(backend)
         .addProgramFiles(MOCKITO_INTERFACE_JAR)
-        .addKeepRules(flagToKeepTestRunner);
+        .addKeepRuleFiles(flagToKeepTestRunner);
     if (!minify) {
       builder.noMinification();
     }
@@ -65,7 +65,7 @@
         Paths.get(ToolHelper.EXAMPLES_DIR, M_I_PKG, "keep-rules-conditional-on-mock.txt");
     R8TestBuilder builder = testForR8(backend)
         .addProgramFiles(MOCKITO_INTERFACE_JAR)
-        .addKeepRules(flagToKeepInterfaceConditionally);
+        .addKeepRuleFiles(flagToKeepInterfaceConditionally);
     if (!minify) {
       builder.noMinification();
     }
diff --git a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
index e6de124..867d346 100644
--- a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
@@ -6,6 +6,8 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
@@ -20,17 +22,38 @@
 
 class A {
 
+  public void foo() {
+    bar();
+  }
+
+  @NeverInline
+  public void bar() {
+    baz();
+  }
+
+  @NeverInline
+  public void baz() {
+    System.out.println("called baz");
+  }
 }
 
 @RunWith(Parameterized.class)
 public class WhyAreYouKeepingTest extends TestBase {
 
   public static final String expected =
-      StringUtils.joinLines(
+      StringUtils.lines(
           "com.android.tools.r8.shaking.whyareyoukeeping.A",
           "|- is referenced in keep rule:",
-          "|  -keep class com.android.tools.r8.shaking.whyareyoukeeping.A { *; }",
-          "");
+          "|  -keep class com.android.tools.r8.shaking.whyareyoukeeping.A { foo(); }");
+
+  // TODO(b/120959039): This should be "- is invoked from:\n  com.android.....A.bar()" etc.
+  public static final String expectedPathToBaz =
+      StringUtils.lines(
+          "void com.android.tools.r8.shaking.whyareyoukeeping.A.baz()",
+          "|- is reachable from:",
+          "|  com.android.tools.r8.shaking.whyareyoukeeping.A",
+          "|- is referenced in keep rule:",
+          "|  -keep class com.android.tools.r8.shaking.whyareyoukeeping.A { foo(); }");
 
   @Parameters(name = "{0}")
   public static Backend[] parameters() {
@@ -48,11 +71,8 @@
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     testForR8(backend)
         .addProgramClasses(A.class)
-        .addKeepClassAndMembersRules(A.class)
+        .addKeepMethodRules(Reference.methodFromMethod(A.class.getMethod("foo")))
         .addKeepRules("-whyareyoukeeping class " + A.class.getTypeName())
-        // Clear the default library and ignore missing classes to avoid processing the library.
-        .addLibraryFiles()
-        .addOptionsModification(o -> o.ignoreMissingClasses = true)
         // Redirect the compilers stdout to intercept the '-whyareyoukeeping' output
         .redirectStdOut(new PrintStream(baos))
         .compile();
@@ -65,10 +85,7 @@
     WhyAreYouKeepingConsumer graphConsumer = new WhyAreYouKeepingConsumer(null);
     testForR8(backend)
         .addProgramClasses(A.class)
-        .addKeepClassAndMembersRules(A.class)
-        // Clear the default library and ignore missing classes to avoid processing the library.
-        .addLibraryFiles()
-        .addOptionsModification(o -> o.ignoreMissingClasses = true)
+        .addKeepMethodRules(Reference.methodFromMethod(A.class.getMethod("foo")))
         .setKeptGraphConsumer(graphConsumer)
         .compile();
 
@@ -77,4 +94,37 @@
     String output = new String(baos.toByteArray(), StandardCharsets.UTF_8);
     assertEquals(expected, output);
   }
+
+  @Test
+  public void testWhyAreYouKeepingPathViaProguardConfig()
+      throws NoSuchMethodException, CompilationFailedException {
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    testForR8(backend)
+        .addProgramClasses(A.class)
+        .enableInliningAnnotations()
+        .addKeepRules("-whyareyoukeeping class " + A.class.getTypeName() + " { baz(); }")
+        .addKeepMethodRules(Reference.methodFromMethod(A.class.getMethod("foo")))
+        // Redirect the compilers stdout to intercept the '-whyareyoukeeping' output
+        .redirectStdOut(new PrintStream(baos))
+        .compile();
+    String output = new String(baos.toByteArray(), StandardCharsets.UTF_8);
+    assertEquals(expected + expectedPathToBaz, output);
+  }
+
+  @Test
+  public void testWhyAreYouKeepingPathViaConsumer()
+      throws NoSuchMethodException, CompilationFailedException {
+    WhyAreYouKeepingConsumer graphConsumer = new WhyAreYouKeepingConsumer(null);
+    testForR8(backend)
+        .addProgramClasses(A.class)
+        .setKeptGraphConsumer(graphConsumer)
+        .enableInliningAnnotations()
+        .addKeepMethodRules(Reference.methodFromMethod(A.class.getMethod("foo")))
+        .compile();
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    graphConsumer.printWhyAreYouKeeping(
+        Reference.methodFromMethod(A.class.getMethod("baz")), new PrintStream(baos));
+    String output = new String(baos.toByteArray(), StandardCharsets.UTF_8);
+    assertEquals(expectedPathToBaz, output);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 4242ca5..dfab990 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
diff --git a/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java b/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
index c44d1cf..d578ae2 100644
--- a/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
+++ b/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
@@ -3,100 +3,177 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
 
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import java.util.Map;
+import java.util.function.Function;
 import org.junit.Test;
 
 public class ArrayUtilsTest {
 
-  @Test
-  public void testCopyWithSparseChanges_identical() throws Exception {
-    int size = 3;
+  private Integer[] createInputData(int size) {
     Integer[] input = new Integer[size];
     for (int i = 0; i < size; i++) {
       input[i] = i;
     }
+    return input;
+  }
+
+  @Test
+  public void testCopyWithSparseChanges_identical() {
+    int size = 3;
+    Integer[] input = createInputData(size);
     Integer[] output =
         ArrayUtils.copyWithSparseChanges(Integer[].class, input, new Int2IntArrayMap());
     assertNotEquals(input, output);
     for (int i = 0; i < size; i++) {
-      assertTrue(i == output[i]);
+      assertEquals(i, (int) output[i]);
     }
   }
 
   @Test
-  public void testCopyWithSparseChanges_oneChangeAtBeginning() throws Exception {
+  public void testCopyWithSparseChanges_oneChangeAtBeginning() {
     int size = 3;
-    Integer[] input = new Integer[size];
-    for (int i = 0; i < size; i++) {
-      input[i] = i;
-    }
+    Integer[] input = createInputData(size);
     Map<Integer, Integer> changes = new Int2IntArrayMap();
     changes.put(0, size);
     Integer[] output = ArrayUtils.copyWithSparseChanges(Integer[].class, input, changes);
     assertNotEquals(input, output);
-    assertTrue(size == output[0]);
+    assertEquals(size, (int) output[0]);
     for (int i = 1; i < size; i++) {
-      assertTrue(i == output[i]);
+      assertEquals(i, (int) output[i]);
     }
   }
 
   @Test
-  public void testCopyWithSparseChanges_oneChangeInMiddle() throws Exception {
+  public void testCopyWithSparseChanges_oneChangeInMiddle() {
     int size = 3;
-    Integer[] input = new Integer[size];
-    for (int i = 0; i < size; i++) {
-      input[i] = i;
-    }
+    Integer[] input = createInputData(size);
     Map<Integer, Integer> changes = new Int2IntArrayMap();
     changes.put(1, size);
     Integer[] output = ArrayUtils.copyWithSparseChanges(Integer[].class, input, changes);
     assertNotEquals(input, output);
-    assertTrue(size == output[1]);
+    assertEquals(size, (int) output[1]);
     for (int i = 0; i < size; i++) {
       if (i == 1) {
         continue;
       }
-      assertTrue(i == output[i]);
+      assertEquals(i, (int) output[i]);
     }
   }
 
   @Test
-  public void testCopyWithSparseChanges_oneChangeAtEnd() throws Exception {
+  public void testCopyWithSparseChanges_oneChangeAtEnd() {
     int size = 3;
-    Integer[] input = new Integer[size];
-    for (int i = 0; i < size; i++) {
-      input[i] = i;
-    }
+    Integer[] input = createInputData(size);
     Map<Integer, Integer> changes = new Int2IntArrayMap();
     changes.put(2, size);
     Integer[] output = ArrayUtils.copyWithSparseChanges(Integer[].class, input, changes);
     assertNotEquals(input, output);
-    assertTrue(size == output[2]);
+    assertEquals(size, (int) output[2]);
     for (int i = 0; i < size - 1; i++) {
-      assertTrue(i == output[i]);
+      assertEquals(i, (int) output[i]);
     }
   }
 
   @Test
-  public void testCopyWithSparseChanges_twoChangesAtEnds() throws Exception {
+  public void testCopyWithSparseChanges_twoChangesAtEnds() {
     int size = 3;
-    Integer[] input = new Integer[size];
-    for (int i = 0; i < size; i++) {
-      input[i] = i;
-    }
+    Integer[] input = createInputData(size);
     Map<Integer, Integer> changes = new Int2IntArrayMap();
     changes.put(0, size);
     changes.put(2, size);
     Integer[] output = ArrayUtils.copyWithSparseChanges(Integer[].class, input, changes);
     assertNotEquals(input, output);
-    assertTrue(size == output[0]);
-    assertFalse(size == output[1]);
-    assertTrue(size == output[2]);
+    assertEquals(size, (int) output[0]);
+    assertNotEquals(size, (int) output[1]);
+    assertEquals(size, (int) output[2]);
+  }
+
+  @Test
+  public void testFilter_identity() {
+    int size = 3;
+    Integer[] input = createInputData(size);
+    Integer[] output = ArrayUtils.filter(Integer[].class, input, x -> true);
+    assertEquals(input, output);
+  }
+
+  @Test
+  public void testFilter_dropOdd() {
+    int size = 3;
+    Integer[] input = createInputData(size);
+    Integer[] output = ArrayUtils.filter(Integer[].class, input, x -> x % 2 == 0);
+    assertNotEquals(input, output);
+    assertEquals(2, output.length);
+    assertEquals(0, (int) output[0]);
+    assertEquals(2, (int) output[1]);
+  }
+
+  @Test
+  public void testFilter_dropAll() {
+    int size = 3;
+    Integer[] input = createInputData(size);
+    Integer[] output = ArrayUtils.filter(Integer[].class, input, x -> false);
+    assertNotEquals(input, output);
+    assertEquals(0, output.length);
+  }
+
+  @Test
+  public void testMap_identity() {
+    int size = 3;
+    Integer[] input = createInputData(size);
+    Integer[] output = ArrayUtils.map(Integer[].class, input, Function.identity());
+    assertEquals(input, output);
+  }
+
+  @Test
+  public void testMap_dropOdd() {
+    int size = 3;
+    Integer[] input = createInputData(size);
+    Integer[] output = ArrayUtils.map(Integer[].class, input, x -> x % 2 != 0 ? null : x);
+    assertNotEquals(input, output);
+    assertEquals(2, output.length);
+    assertEquals(0, (int) output[0]);
+    assertEquals(2, (int) output[1]);
+  }
+
+  @Test
+  public void testMap_dropAll() {
+    int size = 3;
+    Integer[] input = createInputData(size);
+    Integer[] output = ArrayUtils.map(Integer[].class, input, x -> null);
+    assertNotEquals(input, output);
+    assertEquals(0, output.length);
+  }
+
+  @Test
+  public void testMap_double() {
+    int size = 3;
+    Integer[] input = createInputData(size);
+    Integer[] output = ArrayUtils.map(Integer[].class, input, x -> 2 * x);
+    assertNotEquals(input, output);
+    assertEquals(size, output.length);
+    for (int i = 0; i < size; i++) {
+      assertEquals(i * 2, (int) output[i]);
+    }
+  }
+
+  @Test
+  public void testMap_double_onlyOdd() {
+    int size = 3;
+    Integer[] input = createInputData(size);
+    Integer[] output = ArrayUtils.map(Integer[].class, input, x -> x % 2 != 0 ? 2 * x : x);
+    assertNotEquals(input, output);
+    assertEquals(size, output.length);
+    for (int i = 0; i < size; i++) {
+      if (i % 2 != 0) {
+        assertEquals(i * 2, (int) output[i]);
+      } else {
+        assertEquals(i, (int) output[i]);
+      }
+    }
   }
 
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 1c9fe9f..f5ff639 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -6,7 +6,10 @@
 
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableList;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
@@ -23,6 +26,13 @@
     return builder.build();
   }
 
+  public MethodSubject method(MethodReference method) {
+    return method(
+        (method.getReturnType() == null ? "void" : method.getReturnType().getTypeName()),
+        method.getMethodName(),
+        ListUtils.map(method.getFormalTypes(), TypeReference::getTypeName));
+  }
+
   public MethodSubject method(Method method) {
     List<String> parameters = new ArrayList<>();
     for (Class<?> parameterType : method.getParameterTypes()) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 660156a..0b73966 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -27,6 +27,9 @@
 import com.android.tools.r8.naming.signature.GenericSignatureAction;
 import com.android.tools.r8.naming.signature.GenericSignatureParser;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -223,11 +226,17 @@
   }
 
   public ClassSubject clazz(Class clazz) {
-    return clazz(clazz.getTypeName());
+    return clazz(Reference.classFromClass(clazz));
   }
 
   /** Lookup a class by name. This allows both original and obfuscated names. */
   public ClassSubject clazz(String name) {
+    return clazz(Reference.classFromTypeName(name));
+  }
+
+  public ClassSubject clazz(ClassReference reference) {
+    String descriptor = reference.getDescriptor();
+    String name = DescriptorUtils.descriptorToJavaType(descriptor);
     ClassNamingForNameMapper naming = null;
     if (mapping != null) {
       String obfuscated = originalToObfuscatedMapping.get(name);
@@ -266,8 +275,14 @@
     return builder.build();
   }
 
+
+
   public MethodSubject method(Method method) {
-    ClassSubject clazz = clazz(method.getDeclaringClass());
+    return method(Reference.methodFromMethod(method));
+  }
+
+  public MethodSubject method(MethodReference method) {
+    ClassSubject clazz = clazz(method.getHolderClass());
     if (!clazz.isPresent()) {
       return new AbsentMethodSubject();
     }
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
new file mode 100644
index 0000000..b138e50
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -0,0 +1,80 @@
+// 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.graphinspector;
+
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
+import com.android.tools.r8.experimental.graphinfo.FieldGraphNode;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.experimental.graphinfo.GraphNode;
+import com.android.tools.r8.experimental.graphinfo.MethodGraphNode;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.shaking.CollectingGraphConsumer;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class GraphInspector {
+
+  private final CollectingGraphConsumer consumer;
+  private final CodeInspector inspector;
+
+  private final Set<GraphNode> roots = new HashSet<>();
+  private final Map<ClassReference, ClassGraphNode> classes;
+  private final Map<MethodReference, MethodGraphNode> methods;
+  private final Map<FieldReference, FieldGraphNode> fields;
+
+  public GraphInspector(CollectingGraphConsumer consumer, CodeInspector inspector) {
+    this.consumer = consumer;
+    this.inspector = inspector;
+
+    Set<GraphNode> targets = consumer.getTargets();
+    classes = new IdentityHashMap<>(targets.size());
+    methods = new IdentityHashMap<>(targets.size());
+    fields = new IdentityHashMap<>(targets.size());
+
+    for (GraphNode target : targets) {
+      if (target instanceof ClassGraphNode) {
+        ClassGraphNode node = (ClassGraphNode) target;
+        classes.put(node.getReference(), node);
+      } else if (target instanceof MethodGraphNode) {
+        MethodGraphNode node = (MethodGraphNode) target;
+        methods.put(node.getReference(), node);
+      } else if (target instanceof FieldGraphNode) {
+        FieldGraphNode node = (FieldGraphNode) target;
+        fields.put(node.getReference(), node);
+      } else {
+        throw new Unimplemented("Incomplet support for graph node type: " + target.getClass());
+      }
+      Map<GraphNode, Set<GraphEdgeInfo>> sources = consumer.getSourcesTargeting(target);
+      for (GraphNode source : sources.keySet()) {
+        if (!targets.contains(source)) {
+          roots.add(source);
+        }
+      }
+    }
+  }
+
+  public boolean isRenamed(MethodReference method) {
+    assert isPresent(method);
+    return inspector.method(method).isRenamed();
+  }
+
+  public boolean isPresent(MethodReference method) {
+    if (methods.containsKey(method)) {
+      assert inspector.method(method).isPresent();
+      return true;
+    }
+    return false;
+  }
+
+  public Set<GraphNode> getRoots() {
+    return Collections.unmodifiableSet(roots);
+  }
+}
diff --git a/src/test/kotlinR8TestResources/minify_enum/main.kt b/src/test/kotlinR8TestResources/minify_enum/main.kt
new file mode 100644
index 0000000..d35ecf5
--- /dev/null
+++ b/src/test/kotlinR8TestResources/minify_enum/main.kt
@@ -0,0 +1,16 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package minify_enum
+
+enum class MinifyEnum(
+    val nullableStr1: String?,
+    val nullableStr2: String?,
+    val number3: String
+) {
+  UNKNOWN(null, null, "")
+}
+
+fun main(args: Array<String>) {
+  val a = MinifyEnum.UNKNOWN
+}
diff --git a/tests/r8_api_usage_sample.jar b/tests/r8_api_usage_sample.jar
index 9b7e3f5..aa8aac2 100644
--- a/tests/r8_api_usage_sample.jar
+++ b/tests/r8_api_usage_sample.jar
Binary files differ
diff --git a/tools/internal_test.py b/tools/internal_test.py
index 7d126b0..4713daf 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -293,7 +293,8 @@
   git_hash = utils.get_HEAD_sha1()
   log('Running once with hash %s' % git_hash)
   # Run test.py internal testing.
-  cmd = ['tools/test.py', '--only_internal']
+  # TODO(mkrogh) Change this to --r8lib when we have it working with dependencies relocated.
+  cmd = ['tools/test.py', '--only_internal', '--r8lib_no_deps']
   env = os.environ.copy()
   # Bot does not have a lot of memory.
   env['R8_GRADLE_CORES_PER_FORK'] = '8'
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 4b73b55..e2f70f0 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -8,7 +8,9 @@
 import optparse
 import subprocess
 import sys
+import time
 import utils
+import zipfile
 
 import as_utils
 
@@ -24,40 +26,55 @@
   #     'app_module': ... (default app)
   #     'archives_base_name': ... (default same as app_module)
   #     'flavor': ... (default no flavor)
+  #     'releaseTarget': ... (default <app_module>:assemble<flavor>Release
   # },
   'AnExplorer': {
+      'app_id': 'dev.dworks.apps.anexplorer.pro',
       'git_repo': 'https://github.com/1hakr/AnExplorer',
       'flavor': 'googleMobilePro',
       'signed-apk-name': 'AnExplorer-googleMobileProRelease-4.0.3.apk',
   },
   'AntennaPod': {
+      'app_id': 'de.danoeh.antennapod',
       'git_repo': 'https://github.com/AntennaPod/AntennaPod.git',
       'flavor': 'play',
   },
   'apps-android-wikipedia': {
+      'app_id': 'org.wikipedia',
       'git_repo': 'https://github.com/wikimedia/apps-android-wikipedia',
       'flavor': 'prod',
       'signed-apk-name': 'app-prod-universal-release.apk'
   },
+  'friendlyeats-android': {
+      'app_id': 'com.google.firebase.example.fireeats',
+      'git_repo': 'https://github.com/firebase/friendlyeats-android.git'
+  },
   'KISS': {
+      'app_id': 'fr.neamar.kiss',
       'git_repo': 'https://github.com/Neamar/KISS',
   },
   'materialistic': {
+      'app_id': 'io.github.hidroh.materialistic',
       'git_repo': 'https://github.com/hidroh/materialistic',
   },
   'Minimal-Todo': {
+      'app_id': 'com.avjindersinghsekhon.minimaltodo',
       'git_repo': 'https://github.com/avjinder/Minimal-Todo',
   },
   'NewPipe': {
+      'app_id': 'org.schabi.newpipe',
       'git_repo': 'https://github.com/TeamNewPipe/NewPipe',
   },
   'Simple-Calendar': {
+      'app_id': 'com.simplemobiletools.calendar.pro',
       'git_repo': 'https://github.com/SimpleMobileTools/Simple-Calendar',
       'signed-apk-name': 'calendar-release.apk'
   },
   'tachiyomi': {
+      'app_id': 'eu.kanade.tachiyomi',
       'git_repo': 'https://github.com/sgjesse/tachiyomi.git',
       'flavor': 'standard',
+      'releaseTarget': 'app:assembleRelease',
   },
   # This does not build yet.
   'muzei': {
@@ -75,6 +92,17 @@
 android_build_tools = os.path.join(
     android_home, 'build-tools', android_build_tools_version)
 
+# TODO(christofferqa): Do not rely on 'emulator-5554' name
+emulator_id = 'emulator-5554'
+
+def ComputeSizeOfDexFilesInApk(apk):
+  dex_size = 0
+  z = zipfile.ZipFile(apk, 'r')
+  for filename in z.namelist():
+    if filename.endswith('.dex'):
+      dex_size += z.getinfo(filename).file_size
+  return dex_size
+
 def IsBuiltWithR8(apk):
   script = os.path.join(utils.TOOLS_DIR, 'extractmarker.py')
   return '~~R8' in subprocess.check_output(['python', script, apk]).strip()
@@ -98,6 +126,30 @@
     os.remove(apk_dest)
   os.rename(apk, apk_dest)
 
+def InstallApkOnEmulator(apk_dest):
+  subprocess.check_call(
+      ['adb', '-s', emulator_id, 'install', '-r', '-d', apk_dest])
+
+def WaitForEmulator():
+  stdout = subprocess.check_output(['adb', 'devices'])
+  if '{}\tdevice'.format(emulator_id) in stdout:
+    return
+
+  print('Emulator \'{}\' not connected; waiting for connection'.format(
+      emulator_id))
+
+  time_waited = 0
+  while True:
+    time.sleep(10)
+    time_waited += 10
+    stdout = subprocess.check_output(['adb', 'devices'])
+    if '{}\tdevice'.format(emulator_id) not in stdout:
+      print('... still waiting for connection')
+      if time_waited >= 5 * 60:
+        raise Exception('No emulator connected for 5 minutes')
+    else:
+      return
+
 def BuildAppWithSelectedShrinkers(app, config, options):
   git_repo = config['git_repo']
 
@@ -116,16 +168,38 @@
   else:
     as_utils.remove_r8_dependency(checkout_dir)
 
+  result_per_shrinker = {}
+
   with utils.ChangedWorkingDirectory(checkout_dir):
     for shrinker in SHRINKERS:
       if options.shrinker is not None and shrinker != options.shrinker:
         continue
 
-      BuildAppWithShrinker(app, config, shrinker, checkout_dir, options)
+      apk_dest = None
+      result = {}
+      try:
+        apk_dest = BuildAppWithShrinker(
+          app, config, shrinker, checkout_dir, options)
+        dex_size = ComputeSizeOfDexFilesInApk(apk_dest)
+        result['apk_dest'] = apk_dest,
+        result['build_status'] = 'success'
+        result['dex_size'] = dex_size
+      except:
+        warn('Failed to build {} with {}'.format(app, shrinker))
+        result['build_status'] = 'failed'
+
+      if options.monkey:
+        if result.get('build_status') == 'success':
+          result['monkey_status'] = 'success' if RunMonkey(
+              app, config, apk_dest) else 'failed'
+
+      result_per_shrinker[shrinker] = result
 
     if IsTrackedByGit('gradle.properties'):
       GitCheckout('gradle.properties')
 
+  return result_per_shrinker
+
 def BuildAppWithShrinker(app, config, shrinker, checkout_dir, options):
   print('Building {} with {}'.format(app, shrinker))
 
@@ -153,8 +227,10 @@
   env = os.environ.copy()
   env['ANDROID_HOME'] = android_home
   env['JAVA_OPTS'] = '-ea'
-  releaseTarget = app_module + ':' + 'assemble' + (
-      flavor.capitalize() if flavor else '') + 'Release'
+  releaseTarget = config.get('releaseTarget')
+  if not releaseTarget:
+    releaseTarget = app_module + ':' + 'assemble' + (
+        flavor.capitalize() if flavor else '') + 'Release'
 
   cmd = ['./gradlew', '--no-daemon', 'clean', releaseTarget, '--stacktrace']
   utils.PrintCmd(cmd)
@@ -197,11 +273,54 @@
   assert IsBuiltWithR8(apk_dest) == ('r8' in shrinker), (
       'Unexpected marker in generated APK for {}'.format(shrinker))
 
+  return apk_dest
+
+def RunMonkey(app, config, apk_dest):
+  WaitForEmulator()
+  InstallApkOnEmulator(apk_dest)
+
+  app_id = config.get('app_id')
+  number_of_events_to_generate = 50
+
+  stdout = subprocess.check_output(['adb', 'shell', 'monkey', '-p', app_id,
+      str(number_of_events_to_generate)])
+  return 'Events injected: {}'.format(number_of_events_to_generate) in stdout
+
+def LogResults(result_per_shrinker_per_app, options):
+  for app, result_per_shrinker in result_per_shrinker_per_app.iteritems():
+    print(app + ':')
+    baseline = result_per_shrinker.get('proguard', {}).get('dex_size', -1)
+    for shrinker, result in result_per_shrinker.iteritems():
+      build_status = result.get('build_status')
+      if build_status != 'success':
+        warn('  {}: {}'.format(shrinker, build_status))
+      else:
+        print('  {}:'.format(shrinker))
+        dex_size = result.get('dex_size')
+        if dex_size != baseline and baseline >= 0:
+          if dex_size < baseline:
+            success('    dex size: {} ({})'.format(
+              dex_size, dex_size - baseline))
+          elif dex_size > baseline:
+            warn('    dex size: {} ({})'.format(dex_size, dex_size - baseline))
+        else:
+          print('    dex size: {}'.format(dex_size))
+        if options.monkey:
+          monkey_status = result.get('monkey_status')
+          if monkey_status != 'success':
+            warn('    monkey: {}'.format(monkey_status))
+          else:
+            success('    monkey: {}'.format(monkey_status))
+
 def ParseOptions(argv):
   result = optparse.OptionParser()
   result.add_option('--app',
                     help='What app to run on',
                     choices=APPS.keys())
+  result.add_option('--monkey',
+                    help='Whether to install and run app(s) with monkey',
+                    default=False,
+                    action='store_true')
   result.add_option('--sign_apks',
                     help='Whether the APKs should be signed',
                     default=False,
@@ -210,9 +329,9 @@
                     help='The shrinker to use (by default, all are run)',
                     choices=SHRINKERS)
   result.add_option('--use_tot',
-                    help='Whether to use the ToT version of R8',
-                    default=False,
-                    action='store_true')
+                    help='Whether to disable the use of the ToT version of R8',
+                    default=True,
+                    action='store_false')
   return result.parse_args(argv)
 
 def main(argv):
@@ -220,12 +339,28 @@
   assert not options.use_tot or os.path.isfile(utils.R8_JAR), (
       'Cannot build from ToT without r8.jar')
 
+  result_per_shrinker_per_app = {}
+
   if options.app:
-    BuildAppWithSelectedShrinkers(options.app, APPS.get(options.app), options)
+    result_per_shrinker_per_app[options.app] = BuildAppWithSelectedShrinkers(
+        options.app, APPS.get(options.app), options)
   else:
     for app, config in APPS.iteritems():
       if not config.get('skip', False):
-        BuildAppWithSelectedShrinkers(app, config, options)
+        result_per_shrinker_per_app[app] = BuildAppWithSelectedShrinkers(
+            app, config, options)
+
+  LogResults(result_per_shrinker_per_app, options)
+
+def success(message):
+  CGREEN = '\033[32m'
+  CEND = '\033[0m'
+  print(CGREEN + message + CEND)
+
+def warn(message):
+  CRED = '\033[91m'
+  CEND = '\033[0m'
+  print(CRED + message + CEND)
 
 if __name__ == '__main__':
   sys.exit(main(sys.argv[1:]))