Merge "Add test for object and global Kotlin properties"
diff --git a/build.gradle b/build.gradle
index 94c002f..0f782b2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -514,7 +514,7 @@
     classifier = null
     version = null
     manifest {
-        attributes 'Main-Class': 'com.android.tools.r8.R8'
+        attributes 'Main-Class': 'com.android.tools.r8.SwissArmyKnife'
     }
     // In order to build without dependencies, pass the exclude_deps property using:
     // gradle -Pexclude_deps R8
@@ -527,164 +527,27 @@
 }
 
 task D8(type: ShadowJar) {
-    from consolidatedLicense.outputs.files
-    exclude { path ->
-      path.getRelativePath().getPathString().startsWith("META-INF")
-    }
+    from R8.outputs.files
     baseName 'd8'
-    classifier = null
-    version = null
     manifest {
         attributes 'Main-Class': 'com.android.tools.r8.D8'
     }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps D8
-    if (!project.hasProperty('exclude_deps')) {
-        from repackageSources.outputs.files
-        from repackageDeps.outputs.files
-    } else {
-        from sourceSets.main.output
-    }
 }
 
-task CompatDx(type: Jar) {
-    from sourceSets.main.output
+task CompatDx(type: ShadowJar) {
+    from R8.outputs.files
     baseName 'compatdx'
     manifest {
       attributes 'Main-Class': 'com.android.tools.r8.compatdx.CompatDx'
     }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps CompatDx
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
 }
 
-task DexFileMerger(type: Jar) {
-    from sourceSets.main.output
-    baseName 'dexfilemerger'
-    manifest {
-      attributes 'Main-Class': 'com.android.tools.r8.dexfilemerger.DexFileMerger'
-    }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps CompatDx
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
-}
-
-task DexSplitter(type: Jar) {
-    from sourceSets.main.output
-    baseName 'dexsplitter'
-    manifest {
-      attributes 'Main-Class': 'com.android.tools.r8.dexsplitter.DexSplitter'
-    }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps CompatDx
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
-}
-
-task CompatProguard(type: Jar) {
-    from sourceSets.main.output
+task CompatProguard(type: ShadowJar) {
+    from R8.outputs.files
     baseName 'compatproguard'
     manifest {
         attributes 'Main-Class': 'com.android.tools.r8.compatproguard.CompatProguard'
     }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps CompatProguard
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
-}
-
-task D8Logger(type: Jar) {
-    from sourceSets.main.output
-    baseName 'd8logger'
-    manifest {
-      attributes 'Main-Class': 'com.android.tools.r8.D8Logger'
-    }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps D8Logger
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
-}
-
-task disasm(type: Jar) {
-    from sourceSets.main.output
-    baseName 'disasm'
-    manifest {
-        attributes 'Main-Class': 'com.android.tools.r8.Disassemble'
-    }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps D8
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
-}
-
-task bisect(type: Jar) {
-    from sourceSets.main.output
-    baseName 'bisect'
-    manifest {
-      attributes 'Main-Class': 'com.android.tools.r8.bisect.Bisect'
-    }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps R8
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
-}
-
-task DexSegments(type: Jar) {
-    from sourceSets.main.output
-    baseName 'dexsegments'
-    manifest {
-      attributes 'Main-Class': 'com.android.tools.r8.DexSegments'
-    }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps DexSegments
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
-}
-
-task maindex(type: Jar) {
-    from sourceSets.main.output
-    baseName 'maindex'
-    manifest {
-      attributes 'Main-Class': 'com.android.tools.r8.GenerateMainDexList'
-    }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps maindex
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
-}
-
-task ExtractMarker(type: Jar) {
-    from sourceSets.main.output
-    baseName 'extractmarker'
-    manifest {
-      attributes 'Main-Class': 'com.android.tools.r8.ExtractMarker'
-    }
-    // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps ExtractMarker
-    if (!project.hasProperty('exclude_deps')) {
-        // Also include dependencies
-        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
-    }
 }
 
 task bspatch(type: Jar) {
@@ -694,7 +557,7 @@
         attributes 'Main-Class': 'com.android.tools.r8.dex.BSPatch'
     }
     // In order to build without dependencies, pass the exclude_deps property using:
-    // gradle -Pexclude_deps maindex
+    // gradle -Pexclude_deps bspatch
     if (!project.hasProperty('exclude_deps')) {
         // Also include dependencies
         from {
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 3db9699..26097c1 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -59,7 +59,7 @@
     // Print -whyareyoukeeping results if any.
     if (mainDexRootSet.reasonAsked.size() > 0) {
       // Print reasons on the application after pruning, so that we reflect the actual result.
-      TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
+      TreePruner pruner = new TreePruner(application, mainDexAppInfo.withLiveness(), options);
       application = pruner.run();
       ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(mainDexRootSet.reasonAsked);
       reasonPrinter.run(application);
diff --git a/src/main/java/com/android/tools/r8/JarDiff.java b/src/main/java/com/android/tools/r8/JarDiff.java
new file mode 100644
index 0000000..dad57d4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/JarDiff.java
@@ -0,0 +1,260 @@
+// 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;
+
+import static com.google.common.io.ByteStreams.toByteArray;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.graph.ClassKind;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.JarApplicationReader;
+import com.android.tools.r8.graph.JarClassFileReader;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.InternalOptions;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+/**
+ * Command-line program to compare two JARs. Given two JARs as input, the program first outputs a
+ * list of classes only in one of the input JARs. Then, for each common class, the program outputs a
+ * list of methods only in one of the input JARs. For each common method, the output of
+ * CfInstruction.toString() on each instruction is compared to find instruction-level differences. A
+ * simple diffing algorithm is used that simply removes the common prefix and common suffix and
+ * prints everything from the first difference to the last difference in the method code.
+ */
+public class JarDiff {
+
+  private static final String USAGE =
+      "Arguments: <input1.jar> <input2.jar>\n"
+          + "\n"
+          + "JarDiff computes the difference between two JAR files that contain Java classes.\n"
+          + "\n"
+          + "Only method codes are compared. Fields, parameters, annotations, generic\n"
+          + "signatures etc. are ignored.\n"
+          + "\n"
+          + "Note: Jump targets are ignored, so if two methods differ only in what label an\n"
+          + "IF or GOTO instruction jumps to, no difference is output.";
+
+  public static void main(String[] args) throws Exception {
+    JarDiff jarDiff = JarDiff.parse(args);
+    if (jarDiff == null) {
+      System.out.println(USAGE);
+    } else {
+      jarDiff.run();
+    }
+  }
+
+  public static JarDiff parse(String[] args) {
+    int arg = 0;
+    int before = 3;
+    int after = 3;
+    while (arg + 1 < args.length) {
+      if (args[arg].equals("-B")) {
+        before = Integer.parseInt(args[arg + 1]);
+        arg += 2;
+      } else if (args[arg].equals("-A")) {
+        after = Integer.parseInt(args[arg + 1]);
+        arg += 2;
+      } else {
+        break;
+      }
+    }
+    if (args.length != arg + 2) {
+      return null;
+    }
+    return new JarDiff(Paths.get(args[arg]), Paths.get(args[arg + 1]), before, after);
+  }
+
+  private final Path path1;
+  private final Path path2;
+  private final int before;
+  private final int after;
+  private final JarApplicationReader applicationReader;
+  private ArchiveClassFileProvider archive1;
+  private ArchiveClassFileProvider archive2;
+
+  public JarDiff(Path path1, Path path2, int before, int after) {
+    this.path1 = path1;
+    this.path2 = path2;
+    this.before = before;
+    this.after = after;
+    InternalOptions options = new InternalOptions();
+    options.enableCfFrontend = true;
+    applicationReader = new JarApplicationReader(options);
+  }
+
+  public void run() throws Exception {
+    archive1 = new ArchiveClassFileProvider(path1);
+    archive2 = new ArchiveClassFileProvider(path2);
+    for (String descriptor : getCommonDescriptors()) {
+      byte[] bytes1 = getClassAsBytes(archive1, descriptor);
+      byte[] bytes2 = getClassAsBytes(archive2, descriptor);
+      if (Arrays.equals(bytes1, bytes2)) {
+        continue;
+      }
+      DexProgramClass class1 = getDexProgramClass(path1, bytes1);
+      DexProgramClass class2 = getDexProgramClass(path2, bytes2);
+      compareMethods(class1, class2);
+    }
+  }
+
+  private List<String> getCommonDescriptors() {
+    List<String> descriptors1 = getSortedDescriptorList(archive1);
+    List<String> descriptors2 = getSortedDescriptorList(archive2);
+    if (descriptors1.equals(descriptors2)) {
+      return descriptors1;
+    }
+    List<String> only1 = setMinus(descriptors1, descriptors2);
+    List<String> only2 = setMinus(descriptors2, descriptors1);
+    if (!only1.isEmpty()) {
+      System.out.println("Only in " + path1 + ": " + only1);
+    }
+    if (!only2.isEmpty()) {
+      System.out.println("Only in " + path2 + ": " + only2);
+    }
+    return setIntersection(descriptors1, descriptors2);
+  }
+
+  private List<String> getSortedDescriptorList(ArchiveClassFileProvider inputJar) {
+    ArrayList<String> descriptorList = new ArrayList<>(inputJar.getClassDescriptors());
+    Collections.sort(descriptorList);
+    return descriptorList;
+  }
+
+  private byte[] getClassAsBytes(ArchiveClassFileProvider inputJar, String descriptor)
+      throws Exception {
+    return toByteArray(inputJar.getProgramResource(descriptor).getByteStream());
+  }
+
+  private DexProgramClass getDexProgramClass(Path path, byte[] bytes) throws IOException {
+
+    class Collector implements Consumer<DexClass> {
+
+      private DexClass dexClass;
+
+      @Override
+      public void accept(DexClass dexClass) {
+        this.dexClass = dexClass;
+      }
+
+      public DexClass get() {
+        assert dexClass != null;
+        return dexClass;
+      }
+    }
+
+    Collector collector = new Collector();
+    JarClassFileReader reader = new JarClassFileReader(applicationReader, collector);
+    reader.read(new PathOrigin(path), ClassKind.PROGRAM, new ByteArrayInputStream(bytes));
+    return collector.get().asProgramClass();
+  }
+
+  private void compareMethods(DexProgramClass class1, DexProgramClass class2) {
+    class1.forEachMethod(
+        method1 -> {
+          DexEncodedMethod method2 = class2.lookupMethod(method1.method);
+          if (method2 == null) {
+            compareMethods(method1, method2);
+          }
+        });
+    class2.forEachMethod(
+        method2 -> {
+          DexEncodedMethod method1 = class1.lookupMethod(method2.method);
+          compareMethods(method1, method2);
+        });
+  }
+
+  private void compareMethods(DexEncodedMethod m1, DexEncodedMethod m2) {
+    if (m1 == null) {
+      System.out.println("Only in " + path2 + ": " + m2.method.toSourceString());
+      return;
+    }
+    if (m2 == null) {
+      System.out.println("Only in " + path1 + ": " + m1.method.toSourceString());
+      return;
+    }
+    List<String> code1 = getInstructionStrings(m1);
+    List<String> code2 = getInstructionStrings(m2);
+    if (code1.equals(code2)) {
+      return;
+    }
+    int i = getCommonPrefix(code1, code2);
+    int j = getCommonSuffix(code1, code2);
+    int length1 = code1.size() - i - j;
+    int length2 = code2.size() - i - j;
+    int before = Math.min(i, this.before);
+    int after = Math.min(j, this.after);
+    int context = before + after;
+    System.out.println("--- " + path1 + "/" + m1.method.toSmaliString());
+    System.out.println("+++ " + path2 + "/" + m2.method.toSmaliString());
+    System.out.println(
+        "@@ -" + (i - before) + "," + (length1 + context)
+            + " +" + (i - before) + "," + (length2 + context) + " @@ "
+            + m1.method.toSourceString());
+    for (int k = 0; k < before; k++) {
+      System.out.println(" " + code1.get(i - before + k));
+    }
+    for (int k = 0; k < length1; k++) {
+      System.out.println("-" + code1.get(i + k));
+    }
+    for (int k = 0; k < length2; k++) {
+      System.out.println("+" + code2.get(i + k));
+    }
+    for (int k = 0; k < after; k++) {
+      System.out.println(" " + code1.get(i + length1 + k));
+    }
+  }
+
+  private static List<String> getInstructionStrings(DexEncodedMethod method) {
+    List<CfInstruction> instructions = method.getCode().asCfCode().getInstructions();
+    return instructions.stream().map(CfInstruction::toString).collect(Collectors.toList());
+  }
+
+  private static List<String> setIntersection(List<String> set1, List<String> set2) {
+    ArrayList<String> result = new ArrayList<>(set1);
+    result.retainAll(new HashSet<>(set2));
+    return result;
+  }
+
+  private static List<String> setMinus(List<String> set, List<String> toRemove) {
+    ArrayList<String> result = new ArrayList<>(set);
+    result.removeAll(new HashSet<>(toRemove));
+    return result;
+  }
+
+  private static int getCommonPrefix(List<String> code1, List<String> code2) {
+    int i = 0;
+    while (i < code1.size() && i < code2.size()) {
+      if (code1.get(i).equals(code2.get(i))) {
+        i++;
+      } else {
+        break;
+      }
+    }
+    return i;
+  }
+
+  private static int getCommonSuffix(List<String> code1, List<String> code2) {
+    int j = 0;
+    while (j < code1.size() && j < code2.size()) {
+      if (code1.get(code1.size() - j - 1).equals(code2.get(code2.size() - j - 1))) {
+        j++;
+      } else {
+        break;
+      }
+    }
+    return j;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index a52d694..9d6e40d 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -12,9 +12,11 @@
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.shaking.ProguardConfigurationSource;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceBytes;
 import com.android.tools.r8.shaking.ProguardConfigurationSourceFile;
 import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
@@ -22,15 +24,17 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Consumer;
 
 /**
- * Immutable command structure for an invocation of the {@link D8} compiler.
+ * Immutable command structure for an invocation of the {@link R8} compiler.
  *
  * <p>To build a R8 command use the {@link R8Command.Builder} class. For example:
  *
@@ -233,7 +237,7 @@
         Path path,
         OutputMode mode,
         boolean consumeDataResources) {
-      return super.createProgramOutputConsumer(path, mode, true);
+      return super.createProgramOutputConsumer(path, mode, false);
     }
 
     @Override
@@ -282,21 +286,54 @@
         mainDexKeepRules = parser.getConfig().getRules();
       }
 
-      ProguardConfiguration.Builder configurationBuilder;
-      if (proguardConfigs.isEmpty()) {
-        configurationBuilder = ProguardConfiguration.builder(factory, reporter);
-      } else {
-        ProguardConfigurationParser parser =
-            new ProguardConfigurationParser(factory, reporter);
+      ProguardConfigurationParser parser = new ProguardConfigurationParser(factory, reporter);
+      if (!proguardConfigs.isEmpty()) {
         parser.parse(proguardConfigs);
-        configurationBuilder = parser.getConfigurationBuilder();
-        configurationBuilder.setForceProguardCompatibility(forceProguardCompatibility);
       }
+      ProguardConfiguration.Builder configurationBuilder = parser.getConfigurationBuilder();
+      configurationBuilder.setForceProguardCompatibility(forceProguardCompatibility);
 
       if (proguardConfigurationConsumer != null) {
         proguardConfigurationConsumer.accept(configurationBuilder);
       }
 
+      // Process Proguard configurations supplied through data resources in the input.
+      DataResourceProvider.Visitor embeddedProguardConfigurationVisitor =
+          new DataResourceProvider.Visitor() {
+            @Override
+            public void visit(DataDirectoryResource directory) {
+              // Don't do anything.
+            }
+
+            @Override
+            public void visit(DataEntryResource resource) {
+              if (resource.getName().startsWith("META-INF/proguard/")) {
+                try (InputStream in = resource.getByteStream()) {
+                  ProguardConfigurationSource source =
+                      new ProguardConfigurationSourceBytes(in, resource.getOrigin());
+                  parser.parse(source);
+                } catch (ResourceException e) {
+                  reporter.error(new StringDiagnostic("Failed to open input: " + e.getMessage(),
+                      resource.getOrigin()));
+                } catch (Exception e) {
+                  reporter.error(new ExceptionDiagnostic(e, resource.getOrigin()));
+                }
+              }
+            }
+          };
+
+      getAppBuilder().getProgramResourceProviders()
+          .stream()
+          .map(ProgramResourceProvider::getDataResourceProvider)
+          .filter(Objects::nonNull)
+          .forEach(dataResourceProvider -> {
+              try {
+                dataResourceProvider.accept(embeddedProguardConfigurationVisitor);
+              } catch (ResourceException e) {
+                reporter.error(new ExceptionDiagnostic(e));
+              }
+          });
+
       if (disableTreeShaking) {
         configurationBuilder.disableShrinking();
       }
@@ -369,6 +406,11 @@
       }
       return resources;
     }
+
+    @Override
+    public DataResourceProvider getDataResourceProvider() {
+      return provider.getDataResourceProvider();
+    }
   }
 
   // Internal state to verify parsing properties not enforced by the builder.
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
new file mode 100644
index 0000000..0e35847
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -0,0 +1,83 @@
+// 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;
+
+import com.android.tools.r8.bisect.Bisect;
+import com.android.tools.r8.compatdx.CompatDx;
+import com.android.tools.r8.compatproguard.CompatProguard;
+import com.android.tools.r8.dexfilemerger.DexFileMerger;
+import com.android.tools.r8.dexsplitter.DexSplitter;
+import java.util.Arrays;
+
+/**
+ * Common entry point to everything in the R8 project.
+ *
+ * <p>This class is used as the main class in {@code r8.jar}. It checks the first command-line
+ * argument to find the tool to run, or runs {@link R8} if the first argument is not a recognized
+ * tool name.
+ *
+ * <p>The set of tools recognized by this class is defined by a switch statement in {@link
+ * SwissArmyKnife#main(String[])}.
+ */
+public class SwissArmyKnife {
+
+  public static void main(String[] args) throws Exception {
+    if (args.length == 0) {
+      runDefault(args);
+      return;
+    }
+    switch (args[0]) {
+      case "r8":
+        R8.main(shift(args));
+        break;
+      case "d8":
+        D8.main(shift(args));
+        break;
+      case "compatdx":
+        CompatDx.main(shift(args));
+        break;
+      case "dexfilemerger":
+        DexFileMerger.main(shift(args));
+        break;
+      case "dexsplitter":
+        DexSplitter.main(shift(args));
+        break;
+      case "compatproguard":
+        CompatProguard.main(shift(args));
+        break;
+      case "d8logger":
+        D8Logger.main(shift(args));
+        break;
+      case "disasm":
+        Disassemble.main(shift(args));
+        break;
+      case "bisect":
+        Bisect.main(shift(args));
+        break;
+      case "dexsegments":
+        DexSegments.main(shift(args));
+        break;
+      case "maindex":
+        GenerateMainDexList.main(shift(args));
+        break;
+      case "extractmarker":
+        ExtractMarker.main(shift(args));
+        break;
+      case "jardiff":
+        JarDiff.main(shift(args));
+        break;
+      default:
+        runDefault(args);
+        break;
+    }
+  }
+
+  private static void runDefault(String[] args) {
+    R8.main(args);
+  }
+
+  private static String[] shift(String[] args) {
+    return Arrays.copyOfRange(args, 1, args.length);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 124bccc..98ed5e9 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.2.16-dev";
+  public static final String LABEL = "1.2.17-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 df16622..382bc66 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationDirectory;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugInfo;
@@ -30,6 +29,7 @@
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.ProguardMapSupplier;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -106,7 +106,7 @@
     }
 
     @Override
-    public boolean add(DexAnnotationSetRefList annotationSetRefList) {
+    public boolean add(ParameterAnnotationsList parameterAnnotationsList) {
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 8042a1a..d762646 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -19,7 +19,6 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexCode;
@@ -56,6 +55,7 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
@@ -340,11 +340,11 @@
     return result;
   }
 
-  private DexAnnotationSetRefList annotationSetRefListAt(int offset) {
-    return (DexAnnotationSetRefList) cacheAt(offset, this::parseAnnotationSetRefList);
+  private ParameterAnnotationsList annotationSetRefListAt(int offset) {
+    return (ParameterAnnotationsList) cacheAt(offset, this::parseAnnotationSetRefList);
   }
 
-  private DexAnnotationSetRefList parseAnnotationSetRefList() {
+  private ParameterAnnotationsList parseAnnotationSetRefList() {
     int size = dexReader.getUint();
     int[] annotationOffsets = new int[size];
     for (int i = 0; i < size; i++) {
@@ -354,7 +354,7 @@
     for (int i = 0; i < size; i++) {
       values[i] = annotationSetAt(annotationOffsets[i]);
     }
-    return new DexAnnotationSetRefList(values);
+    return new ParameterAnnotationsList(values);
   }
 
   private DexParameterAnnotation[] parseParameterAnnotations(int size) {
@@ -373,7 +373,8 @@
       DexMethod method = indexedItems.getMethod(methodIndices[i]);
       result[i] = new DexParameterAnnotation(
           method,
-          annotationSetRefListAt(annotationOffsets[i]));
+          annotationSetRefListAt(annotationOffsets[i])
+              .withParameterCount(method.proto.parameters.size()));
     }
     dexReader.position(saved);
     return result;
@@ -589,8 +590,8 @@
     int methodIndex = 0;
     MemberAnnotationIterator<DexMethod, DexAnnotationSet> annotationIterator =
         new MemberAnnotationIterator<>(annotations, DexAnnotationSet::empty);
-    MemberAnnotationIterator<DexMethod, DexAnnotationSetRefList> parameterAnnotationsIterator =
-        new MemberAnnotationIterator<>(parameters, DexAnnotationSetRefList::empty);
+    MemberAnnotationIterator<DexMethod, ParameterAnnotationsList> parameterAnnotationsIterator =
+        new MemberAnnotationIterator<>(parameters, ParameterAnnotationsList::empty);
     for (int i = 0; i < size; i++) {
       methodIndex += dexReader.getUleb128();
       MethodAccessFlags accessFlags = MethodAccessFlags.fromDexAccessFlags(dexReader.getUleb128());
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index ced33f6..4767c64 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.graph.DexAnnotationDirectory;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
@@ -40,6 +39,7 @@
 import com.android.tools.r8.graph.IndexedDexItem;
 import com.android.tools.r8.graph.KeyedDexItem;
 import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.graph.ProgramClassVisitor;
 import com.android.tools.r8.logging.Log;
@@ -510,12 +510,17 @@
     }
   }
 
-  private void writeAnnotationSetRefList(DexAnnotationSetRefList setRefList) {
-    assert !setRefList.isEmpty();
-    mixedSectionOffsets.setOffsetFor(setRefList, dest.align(4));
-    dest.putInt(setRefList.values.length);
-    for (DexAnnotationSet set : setRefList.values) {
-      dest.putInt(mixedSectionOffsets.getOffsetFor(set));
+  private void writeAnnotationSetRefList(ParameterAnnotationsList parameterAnnotationsList) {
+    assert !parameterAnnotationsList.isEmpty();
+    mixedSectionOffsets.setOffsetFor(parameterAnnotationsList, dest.align(4));
+    dest.putInt(parameterAnnotationsList.countNonMissing());
+    for (int i = 0; i < parameterAnnotationsList.size(); i++) {
+      if (parameterAnnotationsList.isMissing(i)) {
+        // b/62300145: Maintain broken ParameterAnnotations attribute by only outputting the
+        // non-missing annotation lists.
+        continue;
+      }
+      dest.putInt(mixedSectionOffsets.getOffsetFor(parameterAnnotationsList.get(i)));
     }
   }
 
@@ -541,7 +546,7 @@
     writeMemberAnnotations(methodAnnotations,
         item -> mixedSectionOffsets.getOffsetFor(item.annotations));
     writeMemberAnnotations(parameterAnnotations,
-        item -> mixedSectionOffsets.getOffsetFor(item.parameterAnnotations));
+        item -> mixedSectionOffsets.getOffsetFor(item.parameterAnnotationsList));
   }
 
   private void writeEncodedFields(DexEncodedField[] fields) {
@@ -990,7 +995,7 @@
     private final Reference2IntMap<DexString> stringData = createReference2IntMap();
     private final Object2IntMap<DexAnnotation> annotations = createObject2IntMap();
     private final Object2IntMap<DexAnnotationSet> annotationSets = createObject2IntMap();
-    private final Object2IntMap<DexAnnotationSetRefList> annotationSetRefLists
+    private final Object2IntMap<ParameterAnnotationsList> annotationSetRefLists
         = createObject2IntMap();
     private final Object2IntMap<DexAnnotationDirectory> annotationDirectories
         = createObject2IntMap();
@@ -1072,7 +1077,7 @@
     }
 
     @Override
-    public boolean add(DexAnnotationSetRefList annotationSetRefList) {
+    public boolean add(ParameterAnnotationsList annotationSetRefList) {
       if (annotationSetRefList.isEmpty()) {
         return false;
       }
@@ -1120,7 +1125,7 @@
       return annotationSets.keySet();
     }
 
-    public Collection<DexAnnotationSetRefList> getAnnotationSetRefLists() {
+    public Collection<ParameterAnnotationsList> getAnnotationSetRefLists() {
       return annotationSetRefLists.keySet();
     }
 
@@ -1200,7 +1205,7 @@
       return lookup(annotationSet, annotationSets);
     }
 
-    public int getOffsetFor(DexAnnotationSetRefList annotationSetRefList) {
+    public int getOffsetFor(ParameterAnnotationsList annotationSetRefList) {
       if (annotationSetRefList.isEmpty()) {
         return 0;
       }
@@ -1261,7 +1266,7 @@
       setOffsetFor(encodedArray, offset, encodedArrays);
     }
 
-    void setOffsetFor(DexAnnotationSetRefList annotationSetRefList, int offset) {
+    void setOffsetFor(ParameterAnnotationsList annotationSetRefList, int offset) {
       assert offset != 0 && !annotationSetRefList.isEmpty();
       setOffsetFor(annotationSetRefList, offset, annotationSetRefLists);
     }
diff --git a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
index eb7ae98..dee12fc 100644
--- a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
+++ b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
@@ -6,13 +6,13 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationDirectory;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.DexEncodedArray;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 
 /**
  * Collection of the various components of the mixed section of a dex file.
@@ -86,7 +86,7 @@
    *
    * @return true if the item was not added before
    */
-  public abstract boolean add(DexAnnotationSetRefList annotationSetRefList);
+  public abstract boolean add(ParameterAnnotationsList annotationSetRefList);
 
   /**
    * Adds the given annotation to the collection.
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 1ac2246..61595b9 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -5,23 +5,15 @@
 
 import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.cf.code.CfReturnVoid;
-import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.Throw;
-import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
-import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.CfSourceCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -29,7 +21,6 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.Collections;
-import java.util.LinkedList;
 import java.util.List;
 import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
@@ -211,21 +202,6 @@
   @Override
   public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
       throws ApiLevelException {
-    if (instructions.size() == 2
-        && instructions.get(0) instanceof CfConstNull
-        && instructions.get(1) instanceof CfThrow) {
-      BasicBlock block = new BasicBlock();
-      block.setNumber(1);
-      Value nullValue = new Value(0, ValueType.OBJECT, null);
-      block.add(new ConstNumber(nullValue, 0L));
-      block.add(new Throw(nullValue));
-      block.close(null);
-      for (Instruction insn : block.getInstructions()) {
-        insn.setPosition(Position.none());
-      }
-      LinkedList<BasicBlock> blocks = new LinkedList<>(Collections.singleton(block));
-      return new IRCode(options, encodedMethod, blocks, null, false);
-    }
     return internalBuild(encodedMethod, options, null, null, origin);
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
index aa5d145..fa5d8a8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
@@ -33,7 +33,7 @@
       if (!method.annotations.isEmpty()) {
         methodAnnotations.add(method);
       }
-      if (!method.parameterAnnotations.isEmpty()) {
+      if (!method.parameterAnnotationsList.isEmpty()) {
         parameterAnnotations.add(method);
       }
     }
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 9ee8fba..d093a20 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
@@ -5,7 +5,9 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.function.Predicate;
 
 public class DexAnnotationSet extends CachedHashValueDexItem {
 
@@ -115,4 +117,30 @@
     extendedArray[annotations.length] = newAnnotation;
     return new DexAnnotationSet(extendedArray);
   }
+
+  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 this;
+    } else if (filtered.isEmpty()) {
+      return DexAnnotationSet.empty();
+    } else {
+      return new DexAnnotationSet(filtered.toArray(new DexAnnotation[filtered.size()]));
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSetRefList.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSetRefList.java
deleted file mode 100644
index 4bb1314..0000000
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSetRefList.java
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graph;
-
-import com.android.tools.r8.dex.IndexedItemCollection;
-import com.android.tools.r8.dex.MixedSectionCollection;
-import java.util.Arrays;
-
-public class DexAnnotationSetRefList extends DexItem {
-
-  private static final DexAnnotationSetRefList theEmptyTypeList = new DexAnnotationSetRefList();
-
-  public final DexAnnotationSet[] values;
-  private final int missingParameterAnnotations;
-
-  public static DexAnnotationSetRefList empty() {
-    return theEmptyTypeList;
-  }
-
-  private DexAnnotationSetRefList() {
-    this.values = new DexAnnotationSet[0];
-    this.missingParameterAnnotations = 0;
-  }
-
-  public DexAnnotationSetRefList(DexAnnotationSet[] values) {
-    this(values, 0);
-  }
-
-  public DexAnnotationSetRefList(DexAnnotationSet[] values, int missingParameterAnnotations) {
-    assert values != null && values.length > 0;
-    this.values = values;
-    this.missingParameterAnnotations = missingParameterAnnotations;
-  }
-
-  @Override
-  public int hashCode() {
-    return Arrays.hashCode(values);
-  }
-
-  @Override
-  public boolean equals(Object other) {
-    if (this == other) {
-      return true;
-    }
-    if (other instanceof DexAnnotationSetRefList) {
-      return Arrays.equals(values, ((DexAnnotationSetRefList) other).values);
-    }
-    return false;
-  }
-
-  @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems,
-      DexMethod method, int instructionOffset) {
-    collectAll(indexedItems, values);
-  }
-
-  @Override
-  void collectMixedSectionItems(MixedSectionCollection mixedItems) {
-    // Collect values first so that the annotation sets have sorted themselves before adding this.
-    collectAll(mixedItems, values);
-    mixedItems.add(this);
-  }
-
-  public boolean isEmpty() {
-    return values.length == 0;
-  }
-
-  public int getMissingParameterAnnotations() {
-    return missingParameterAnnotations;
-  }
-}
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 7b09cab..23139c5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -159,21 +159,13 @@
       for (DexAnnotation annotation : method.annotations.annotations) {
         consumer.accept(annotation);
       }
-      for (DexAnnotationSet parameterAnnotations : method.parameterAnnotations.values) {
-        for (DexAnnotation annotation : parameterAnnotations.annotations) {
-          consumer.accept(annotation);
-        }
-      }
+      method.parameterAnnotationsList.forEachAnnotation(consumer);
     }
     for (DexEncodedMethod method : virtualMethods()) {
       for (DexAnnotation annotation : method.annotations.annotations) {
         consumer.accept(annotation);
       }
-      for (DexAnnotationSet parameterAnnotations : method.parameterAnnotations.values) {
-        for (DexAnnotation annotation : parameterAnnotations.annotations) {
-          consumer.accept(annotation);
-        }
-      }
+      method.parameterAnnotationsList.forEachAnnotation(consumer);
     }
     for (DexEncodedField field : instanceFields()) {
       for (DexAnnotation annotation : field.annotations.annotations) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index d514a9a..0cabaf1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -93,7 +93,7 @@
   public final DexMethod method;
   public final MethodAccessFlags accessFlags;
   public DexAnnotationSet annotations;
-  public DexAnnotationSetRefList parameterAnnotations;
+  public ParameterAnnotationsList parameterAnnotationsList;
   private Code code;
   private CompilationState compilationState = CompilationState.NOT_PROCESSED;
   private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT;
@@ -102,12 +102,12 @@
       DexMethod method,
       MethodAccessFlags accessFlags,
       DexAnnotationSet annotations,
-      DexAnnotationSetRefList parameterAnnotations,
+      ParameterAnnotationsList parameterAnnotationsList,
       Code code) {
     this.method = method;
     this.accessFlags = accessFlags;
     this.annotations = annotations;
-    this.parameterAnnotations = parameterAnnotations;
+    this.parameterAnnotationsList = parameterAnnotationsList;
     this.code = code;
     assert code == null || !accessFlags.isAbstract();
   }
@@ -272,7 +272,7 @@
       code.collectIndexedItems(indexedItems, this.method);
     }
     annotations.collectIndexedItems(indexedItems);
-    parameterAnnotations.collectIndexedItems(indexedItems);
+    parameterAnnotationsList.collectIndexedItems(indexedItems);
   }
 
   @Override
@@ -281,7 +281,7 @@
       code.collectMixedSectionItems(mixedItems);
     }
     annotations.collectMixedSectionItems(mixedItems);
-    parameterAnnotations.collectMixedSectionItems(mixedItems);
+    parameterAnnotationsList.collectMixedSectionItems(mixedItems);
   }
 
   public Code getCode() {
@@ -537,7 +537,7 @@
   }
 
   public boolean hasAnnotation() {
-    return !annotations.isEmpty() || !parameterAnnotations.isEmpty();
+    return !annotations.isEmpty() || !parameterAnnotationsList.isEmpty();
   }
 
   public void registerCodeReferences(UseRegistry registry) {
@@ -727,7 +727,7 @@
     private DexMethod method;
     private final MethodAccessFlags accessFlags;
     private final DexAnnotationSet annotations;
-    private final DexAnnotationSetRefList parameterAnnotations;
+    private final ParameterAnnotationsList parameterAnnotations;
     private Code code;
     private CompilationState compilationState = CompilationState.NOT_PROCESSED;
     private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT;
@@ -737,7 +737,7 @@
       method = from.method;
       accessFlags = from.accessFlags.copy();
       annotations = from.annotations;
-      parameterAnnotations = from.parameterAnnotations;
+      parameterAnnotations = from.parameterAnnotationsList;
       code = from.code;
       compilationState = from.compilationState;
       optimizationInfo = from.optimizationInfo.copy();
diff --git a/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java
index f005588..6cc2fb4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java
@@ -60,9 +60,9 @@
   }
 
   public static class DexParameterAnnotation extends
-      DexMemberAnnotation<DexMethod, DexAnnotationSetRefList> {
+      DexMemberAnnotation<DexMethod, ParameterAnnotationsList> {
 
-    public DexParameterAnnotation(DexMethod item, DexAnnotationSetRefList annotations) {
+    public DexParameterAnnotation(DexMethod item, ParameterAnnotationsList annotations) {
       super(item, annotations);
     }
   }
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 e403c14..02f0801 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -446,7 +446,7 @@
     private List<DexAnnotation> annotations = null;
     private DexValue defaultAnnotation = null;
     private int fakeParameterAnnotations = 0;
-    private List<List<DexAnnotation>> parameterAnnotations = null;
+    private List<List<DexAnnotation>> parameterAnnotationsLists = null;
     private List<DexValue> parameterNames = null;
     private List<DexValue> parameterFlags = null;
     final DexMethod method;
@@ -508,21 +508,21 @@
         // We can iterate through all the parameters twice. Once for visible and once for
         // invisible parameter annotations. We only record the number of fake parameter
         // annotations once.
-        if (parameterAnnotations == null) {
+        if (parameterAnnotationsLists == null) {
           fakeParameterAnnotations++;
         }
         return null;
       }
-      if (parameterAnnotations == null) {
+      if (parameterAnnotationsLists == null) {
         int adjustedParameterCount = parameterCount - fakeParameterAnnotations;
-        parameterAnnotations = new ArrayList<>(adjustedParameterCount);
+        parameterAnnotationsLists = new ArrayList<>(adjustedParameterCount);
         for (int i = 0; i < adjustedParameterCount; i++) {
-          parameterAnnotations.add(new ArrayList<>());
+          parameterAnnotationsLists.add(new ArrayList<>());
         }
       }
       assert mv == null;
       return createAnnotationVisitor(desc, visible,
-          parameterAnnotations.get(parameter - fakeParameterAnnotations), parent.application);
+          parameterAnnotationsLists.get(parameter - fakeParameterAnnotations), parent.application);
     }
 
     @Override
@@ -573,15 +573,15 @@
     public void visitEnd() {
       assert flags.isAbstract() || flags.isNative() || parent.classKind != ClassKind.PROGRAM
           || code != null;
-      DexAnnotationSetRefList parameterAnnotationSets;
-      if (parameterAnnotations == null) {
-        parameterAnnotationSets = DexAnnotationSetRefList.empty();
+      ParameterAnnotationsList annotationsList;
+      if (parameterAnnotationsLists == null) {
+        annotationsList = ParameterAnnotationsList.empty();
       } else {
-        DexAnnotationSet[] sets = new DexAnnotationSet[parameterAnnotations.size()];
-        for (int i = 0; i < parameterAnnotations.size(); i++) {
-          sets[i] = createAnnotationSet(parameterAnnotations.get(i));
+        DexAnnotationSet[] sets = new DexAnnotationSet[parameterAnnotationsLists.size()];
+        for (int i = 0; i < parameterAnnotationsLists.size(); i++) {
+          sets[i] = createAnnotationSet(parameterAnnotationsLists.get(i));
         }
-        parameterAnnotationSets = new DexAnnotationSetRefList(sets, fakeParameterAnnotations);
+        annotationsList = new ParameterAnnotationsList(sets, fakeParameterAnnotations);
       }
       InternalOptions internalOptions = parent.application.options;
       if (parameterNames != null && internalOptions.canUseParameterNameAnnotations()) {
@@ -596,7 +596,7 @@
             parent.application.getFactory()));
       }
       DexEncodedMethod dexMethod = new DexEncodedMethod(method, flags,
-          createAnnotationSet(annotations), parameterAnnotationSets, code);
+          createAnnotationSet(annotations), annotationsList, code);
       if (flags.isStatic() || flags.isConstructor() || flags.isPrivate()) {
         parent.directMethods.add(dexMethod);
       } else {
diff --git a/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
new file mode 100644
index 0000000..23d5a38
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
@@ -0,0 +1,184 @@
+// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.dex.MixedSectionCollection;
+import java.util.Arrays;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * List of parameter annotations.
+ *
+ * <p>Due to a javac bug that went unfixed for multiple Java versions, the JVM specification does
+ * not require that the number of entries in the ParameterAnnotations attribute of a method matches
+ * the number of parameters in the method prototype; the number of ParameterAnnotations entries may
+ * be less than the number of prototype parameters for methods on inner classes.
+ *
+ * <p>There are two ways of accessing the parameter annotations:
+ *
+ * <ul>
+ *   <li>Using {@link ParameterAnnotationsList#forEachAnnotation(Consumer)}
+ *   <li>Using {@link ParameterAnnotationsList#size()}, {@link
+ *       ParameterAnnotationsList#isMissing(int)} and {@link ParameterAnnotationsList#get(int)}
+ * </ul>
+ *
+ * <p>The {@link ParameterAnnotationsList#forEachAnnotation(Consumer)} method visits all the {@link
+ * DexAnnotation}s specified in the ParameterAnnotations attribute. In contrast, the {@link
+ * ParameterAnnotationsList#size()} and {@link ParameterAnnotationsList#get(int)} methods may be
+ * used to access the annotations on individual parameters; these methods automatically shift
+ * parameter annotations up to mitigate the javac bug. The {@link
+ * ParameterAnnotationsList#isMissing(int)} accessor is used to determine whether a given parameter
+ * is missing in the ParameterAnnotations attribute.
+ */
+public class ParameterAnnotationsList extends DexItem {
+
+  private static final ParameterAnnotationsList EMPTY_PARAMETER_ANNOTATIONS_LIST =
+      new ParameterAnnotationsList();
+
+  private final DexAnnotationSet[] values;
+  private final int missingParameterAnnotations;
+
+  public static ParameterAnnotationsList empty() {
+    return EMPTY_PARAMETER_ANNOTATIONS_LIST;
+  }
+
+  private ParameterAnnotationsList() {
+    this.values = new DexAnnotationSet[0];
+    this.missingParameterAnnotations = 0;
+  }
+
+  public ParameterAnnotationsList(DexAnnotationSet[] values) {
+    this(values, 0);
+  }
+
+  public ParameterAnnotationsList(DexAnnotationSet[] values, int missingParameterAnnotations) {
+    assert values != null && values.length > 0;
+    this.values = values;
+    this.missingParameterAnnotations = missingParameterAnnotations;
+  }
+
+  @Override
+  public int hashCode() {
+    return Arrays.hashCode(values);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (this == other) {
+      return true;
+    }
+    if (other instanceof ParameterAnnotationsList) {
+      return Arrays.equals(values, ((ParameterAnnotationsList) other).values);
+    }
+    return false;
+  }
+
+  @Override
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
+    collectAll(indexedItems, values);
+  }
+
+  @Override
+  void collectMixedSectionItems(MixedSectionCollection mixedItems) {
+    // Collect values first so that the annotation sets have sorted themselves before adding this.
+    collectAll(mixedItems, values);
+    mixedItems.add(this);
+  }
+
+  public boolean isEmpty() {
+    return values.length == 0;
+  }
+
+  /** Iterate over the {@link DexAnnotation}s of all parameters. */
+  public void forEachAnnotation(Consumer<DexAnnotation> consumer) {
+    for (DexAnnotationSet parameterAnnotations : values) {
+      for (DexAnnotation annotation : parameterAnnotations.annotations) {
+        consumer.accept(annotation);
+      }
+    }
+  }
+
+  /**
+   * Return the number of parameters in the method prototype, or zero if the method's parameters
+   * have no annotations.
+   */
+  public int size() {
+    return missingParameterAnnotations + values.length;
+  }
+
+  /**
+   * Return the number of parameters specified in the ParameterAnnotations attribute, that is, the
+   * number of parameters for which {@link ParameterAnnotationsList#isMissing(int)} returns false.
+   */
+  public int countNonMissing() {
+    return values.length;
+  }
+
+  /**
+   * Return true if the ParameterAnnotations attribute is missing an entry for this parameter. This
+   * is sometimes the case for the first parameter in a method on an inner class.
+   *
+   * @param i Index of the parameter in the method prototype.
+   */
+  public boolean isMissing(int i) {
+    assert i >= 0;
+    return i < missingParameterAnnotations;
+  }
+
+  /**
+   * Return the annotations on the {@code i}th parameter (indexed according to the method
+   * prototype). If the parameter's annotation list is missing, or {@code i} is not less than the
+   * number of parameters (see {@link ParameterAnnotationsList#isMissing(int)}), {@link
+   * DexAnnotationSet#empty()} is returned.
+   *
+   * @param i Index of the parameter in the method prototype.
+   */
+  public DexAnnotationSet get(int i) {
+    assert i >= 0;
+    int adjustedIndex = i - missingParameterAnnotations;
+    return (0 <= adjustedIndex && adjustedIndex < values.length)
+        ? values[adjustedIndex]
+        : DexAnnotationSet.empty();
+  }
+
+  /** Return a ParameterAnnotationsList extended to the given number of parameters. */
+  public ParameterAnnotationsList withParameterCount(int parameterCount) {
+    assert parameterCount >= size();
+    if (this == EMPTY_PARAMETER_ANNOTATIONS_LIST || parameterCount == size()) {
+      return this;
+    }
+    return new ParameterAnnotationsList(values, parameterCount - values.length);
+  }
+
+  /**
+   * Return a new ParameterAnnotationsList that keeps only the annotations matched by {@code
+   * filter}.
+   */
+  public ParameterAnnotationsList keepIf(Predicate<DexAnnotation> filter) {
+    DexAnnotationSet[] filtered = null;
+    boolean allEmpty = true;
+    for (int i = 0; i < values.length; i++) {
+      DexAnnotationSet updated = values[i].keepIf(filter);
+      if (updated != values[i]) {
+        if (filtered == null) {
+          filtered = values.clone();
+        }
+        filtered[i] = updated;
+      }
+      if (!updated.isEmpty()) {
+        allEmpty = false;
+      }
+    }
+    if (filtered == null) {
+      return this;
+    }
+    if (allEmpty) {
+      return ParameterAnnotationsList.empty();
+    }
+    return new ParameterAnnotationsList(filtered, missingParameterAnnotations);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 7befc69..c75456d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -111,7 +111,7 @@
     // Some debuggers (like IntelliJ) automatically skip synthetic methods on single step.
     newFlags.setSynthetic();
     return new DexEncodedMethod(newMethod, newFlags,
-        defaultMethod.annotations, defaultMethod.parameterAnnotations,
+        defaultMethod.annotations, defaultMethod.parameterAnnotationsList,
         new SynthesizedCode(new ForwardMethodSourceCode(
             clazz.type, method.proto, /* static method */ null,
             rewriter.defaultAsMethodOfCompanionClass(method),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 898a2b2..1e8d57a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -70,7 +70,7 @@
             || (companionMethod.getArity() == dexCode.getDebugInfo().parameters.length);
 
         companionMethods.add(new DexEncodedMethod(companionMethod,
-            newFlags, virtual.annotations, virtual.parameterAnnotations, code));
+            newFlags, virtual.annotations, virtual.parameterAnnotationsList, code));
 
         // Make the method abstract.
         virtual.accessFlags.setAbstract();
@@ -106,7 +106,7 @@
             + "either be public or private in " + iface.origin;
         companionMethods.add(new DexEncodedMethod(
             rewriter.staticAsMethodOfCompanionClass(direct.method), newFlags,
-            direct.annotations, direct.parameterAnnotations, direct.getCode()));
+            direct.annotations, direct.parameterAnnotationsList, direct.getCode()));
 
       } else {
         if (originalFlags.isPrivate()) {
@@ -129,7 +129,7 @@
               || (companionMethod.getArity() == dexCode.getDebugInfo().parameters.length);
 
           companionMethods.add(new DexEncodedMethod(companionMethod,
-              newFlags, direct.annotations, direct.parameterAnnotations, code));
+              newFlags, direct.annotations, direct.parameterAnnotationsList, code));
 
         } else {
           // Since there are no interface constructors at this point,
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 82958da..d9bd4bd 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
@@ -10,7 +10,6 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -27,6 +26,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueNull;
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.origin.SynthesizedOrigin;
@@ -171,7 +171,7 @@
             MethodAccessFlags.fromSharedAccessFlags(
                 Constants.ACC_PUBLIC | Constants.ACC_FINAL, false),
             DexAnnotationSet.empty(),
-            DexAnnotationSetRefList.empty(),
+            ParameterAnnotationsList.empty(),
             new SynthesizedCode(new LambdaMainMethodSourceCode(this, mainMethod)));
 
     // Synthesize bridge methods.
@@ -187,7 +187,7 @@
                       | Constants.ACC_BRIDGE,
                   false),
               DexAnnotationSet.empty(),
-              DexAnnotationSetRefList.empty(),
+              ParameterAnnotationsList.empty(),
               new SynthesizedCode(
                   new LambdaBridgeMethodSourceCode(this, mainMethod, bridgeMethod)));
     }
@@ -208,7 +208,7 @@
                     | Constants.ACC_SYNTHETIC,
                 true),
             DexAnnotationSet.empty(),
-            DexAnnotationSetRefList.empty(),
+            ParameterAnnotationsList.empty(),
             new SynthesizedCode(new LambdaConstructorSourceCode(this)));
 
     // Class constructor for stateless lambda classes.
@@ -219,7 +219,7 @@
               MethodAccessFlags.fromSharedAccessFlags(
                   Constants.ACC_SYNTHETIC | Constants.ACC_STATIC, true),
               DexAnnotationSet.empty(),
-              DexAnnotationSetRefList.empty(),
+              ParameterAnnotationsList.empty(),
               new SynthesizedCode(new LambdaClassConstructorSourceCode(this)));
     }
     return methods;
@@ -492,7 +492,7 @@
           // relax its accessibility without making it virtual.
           DexEncodedMethod newMethod = new DexEncodedMethod(
               callTarget, encodedMethod.accessFlags, encodedMethod.annotations,
-              encodedMethod.parameterAnnotations, encodedMethod.getCode());
+              encodedMethod.parameterAnnotationsList, encodedMethod.getCode());
           // TODO(ager): Should we give the new first parameter an actual name? Maybe 'this'?
           encodedMethod.accessFlags.setStatic();
           encodedMethod.accessFlags.unsetPrivate();
@@ -532,7 +532,7 @@
               Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC,
               false);
       DexEncodedMethod accessorEncodedMethod = new DexEncodedMethod(
-          callTarget, accessorFlags, DexAnnotationSet.empty(), DexAnnotationSetRefList.empty(),
+          callTarget, accessorFlags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(),
           new SynthesizedCode(new AccessorMethodSourceCode(LambdaClass.this)));
       accessorClass.setDirectMethods(appendMethod(
           accessorClass.directMethods(), accessorEncodedMethod));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 5a257df..7af555c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -22,6 +21,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -1057,7 +1057,7 @@
       DexString methodName = dexItemFactory.createString(OutlineOptions.METHOD_PREFIX + count);
       DexMethod method = outline.buildMethod(type, methodName);
       direct[count] = new DexEncodedMethod(method, methodAccess, DexAnnotationSet.empty(),
-          DexAnnotationSetRefList.empty(), new OutlineCode(outline));
+          ParameterAnnotationsList.empty(), new OutlineCode(outline));
       generatedOutlines.put(outline, method);
       count++;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 03daf44..466117f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -327,12 +327,12 @@
 
     for (DexEncodedMethod method : clazz.directMethods()) {
       lambdaInvalidator.accept(method.annotations);
-      lambdaInvalidator.accept(method.parameterAnnotations);
+      lambdaInvalidator.accept(method.parameterAnnotationsList);
       lambdaInvalidator.accept(method.method, clazz.type);
     }
     for (DexEncodedMethod method : clazz.virtualMethods()) {
       lambdaInvalidator.accept(method.annotations);
-      lambdaInvalidator.accept(method.parameterAnnotations);
+      lambdaInvalidator.accept(method.parameterAnnotationsList);
       lambdaInvalidator.accept(method.method, clazz.type);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java
index 4f56306..05c71e6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
 import com.android.tools.r8.graph.DexField;
@@ -24,6 +23,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
 import com.android.tools.r8.graph.DexValue.DexValueMethodType;
 import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
@@ -116,10 +116,8 @@
     }
   }
 
-  void accept(DexAnnotationSetRefList annotationSetRefList) {
-    for (DexAnnotationSet annotationSet : annotationSetRefList.values) {
-      accept(annotationSet);
-    }
+  void accept(ParameterAnnotationsList parameterAnnotationsList) {
+    parameterAnnotationsList.forEachAnnotation(this::accept);
   }
 
   private void accept(DexAnnotation annotation) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
index d441a00..1fe0317 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -19,6 +18,7 @@
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroupClassBuilder;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
@@ -113,7 +113,7 @@
             factory.createMethod(group.getGroupClassType(), methodProto, methodName),
             accessFlags,
             isMainMethod ? id.mainMethodAnnotations : DexAnnotationSet.empty(),
-            isMainMethod ? id.mainMethodParamAnnotations : DexAnnotationSetRefList.empty(),
+            isMainMethod ? id.mainMethodParamAnnotations : ParameterAnnotationsList.empty(),
             new SynthesizedCode(
                 new KotlinLambdaVirtualMethodSourceCode(factory, group.getGroupClassType(),
                     methodProto, group.getLambdaIdField(factory), implMethods))));
@@ -159,7 +159,7 @@
         factory.createMethod(groupClassType, initializerProto, factory.constructorMethodName),
         CONSTRUCTOR_FLAGS_RELAXED,  // always create access-relaxed constructor.
         DexAnnotationSet.empty(),
-        DexAnnotationSetRefList.empty(),
+        ParameterAnnotationsList.empty(),
         new SynthesizedCode(createInstanceInitializerSourceCode(groupClassType, initializerProto)));
 
     // Static class initializer for stateless lambdas.
@@ -170,7 +170,7 @@
               factory.classConstructorMethodName),
           CLASS_INITIALIZER_FLAGS,
           DexAnnotationSet.empty(),
-          DexAnnotationSetRefList.empty(),
+          ParameterAnnotationsList.empty(),
           new SynthesizedCode(new ClassInitializerSourceCode(factory, group)));
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
index 0540bef..996099b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
@@ -5,13 +5,13 @@
 package com.android.tools.r8.ir.optimize.lambda.kotlin;
 
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 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.ParameterAnnotationsList;
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
 
@@ -41,7 +41,7 @@
   final DexString mainMethodName;
   final DexProto mainMethodProto;
   final DexAnnotationSet mainMethodAnnotations;
-  final DexAnnotationSetRefList mainMethodParamAnnotations;
+  final ParameterAnnotationsList mainMethodParamAnnotations;
 
   final EnclosingMethodAttribute enclosing;
 
@@ -61,7 +61,7 @@
     this.mainMethodName = mainMethod.method.name;
     this.mainMethodProto = mainMethod.method.proto;
     this.mainMethodAnnotations = mainMethod.annotations;
-    this.mainMethodParamAnnotations = mainMethod.parameterAnnotations;
+    this.mainMethodParamAnnotations = mainMethod.parameterAnnotationsList;
     this.innerClassAccess = inner != null ? inner.getAccess() : MISSING_INNER_CLASS_ATTRIBUTE;
     this.enclosing = enclosing;
     this.hash = computeHashCode();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
index f5698aa..42e0bb1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
@@ -191,7 +191,7 @@
       throw new LambdaStructureError("unexpected method annotations [" +
           method.annotations.toSmaliString() + "] on " + method.method.toSourceString());
     }
-    if (!method.parameterAnnotations.isEmpty()) {
+    if (!method.parameterAnnotationsList.isEmpty()) {
       throw new LambdaStructureError("unexpected method parameters annotations [" +
           method.annotations.toSmaliString() + "] on " + method.method.toSourceString());
     }
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index e49ad85..2970fb4 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -34,6 +33,7 @@
 import com.android.tools.r8.graph.DexValue.UnknownDexValue;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.JarClassFileReader;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.ProguardMapSupplier;
 import com.android.tools.r8.utils.ExceptionUtils;
@@ -252,7 +252,7 @@
       }
     }
     writeAnnotations(visitor::visitAnnotation, method.annotations.annotations);
-    writeParameterAnnotations(visitor, method.parameterAnnotations);
+    writeParameterAnnotations(visitor, method.parameterAnnotationsList);
     if (!method.accessFlags.isAbstract() && !method.accessFlags.isNative()) {
       writeCode(method.getCode(), visitor);
     }
@@ -260,21 +260,21 @@
   }
 
   private void writeParameterAnnotations(
-      MethodVisitor visitor, DexAnnotationSetRefList parameterAnnotations) {
-    int missingParameterAnnotations = parameterAnnotations.getMissingParameterAnnotations();
-    for (int i = 0; i < missingParameterAnnotations; i++) {
-      AnnotationVisitor av =
-          visitor.visitParameterAnnotation(i, JarClassFileReader.SYNTHETIC_ANNOTATION, false);
-      if (av != null) {
-        av.visitEnd();
+      MethodVisitor visitor, ParameterAnnotationsList parameterAnnotations) {
+    for (int i = 0; i < parameterAnnotations.size(); i++) {
+      if (parameterAnnotations.isMissing(i)) {
+        AnnotationVisitor av =
+            visitor.visitParameterAnnotation(i, JarClassFileReader.SYNTHETIC_ANNOTATION, false);
+        if (av != null) {
+          av.visitEnd();
+        }
+      } else {
+        int iFinal = i;
+        writeAnnotations(
+            (d, vis) -> visitor.visitParameterAnnotation(iFinal, d, vis),
+            parameterAnnotations.get(i).annotations);
       }
     }
-    for (int i = 0; i < parameterAnnotations.values.length; i++) {
-      int parameterIndex = i + missingParameterAnnotations;
-      writeAnnotations(
-          (d, vis) -> visitor.visitParameterAnnotation(parameterIndex, d, vis),
-          parameterAnnotations.values[i].annotations);
-    }
   }
 
   private interface AnnotationConsumer {
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 871812a..28ca9fc 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -5,8 +5,6 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -15,9 +13,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.function.Predicate;
 
 public class AnnotationRemover {
 
@@ -127,20 +122,20 @@
     keep.ensureValid(options.forceProguardCompatibility, compatibility);
     for (DexProgramClass clazz : appInfo.classes()) {
       stripAttributes(clazz);
-      clazz.annotations = stripAnnotations(clazz.annotations, this::filterAnnotations);
+      clazz.annotations = clazz.annotations.keepIf(this::filterAnnotations);
       clazz.forEachMethod(this::processMethod);
       clazz.forEachField(this::processField);
     }
   }
 
   private void processMethod(DexEncodedMethod method) {
-    method.annotations = stripAnnotations(method.annotations, this::filterAnnotations);
-    method.parameterAnnotations = stripAnnotations(method.parameterAnnotations,
-        this::filterParameterAnnotations);
+    method.annotations = method.annotations.keepIf(this::filterAnnotations);
+    method.parameterAnnotationsList =
+        method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations);
   }
 
   private void processField(DexEncodedField field) {
-      field.annotations = stripAnnotations(field.annotations, this::filterAnnotations);
+    field.annotations = field.annotations.keepIf(this::filterAnnotations);
   }
 
   private void stripAttributes(DexProgramClass clazz) {
@@ -152,52 +147,4 @@
     }
   }
 
-  private DexAnnotationSetRefList stripAnnotations(DexAnnotationSetRefList annotations,
-      Predicate<DexAnnotation> filter) {
-    DexAnnotationSet[] filtered = null;
-    for (int i = 0; i < annotations.values.length; i++) {
-      DexAnnotationSet updated = stripAnnotations(annotations.values[i], filter);
-      if (updated != annotations.values[i]) {
-        if (filtered == null) {
-          filtered = annotations.values.clone();
-          filtered[i] = updated;
-        }
-      }
-    }
-    if (filtered == null) {
-      return annotations;
-    } else {
-      if (Arrays.stream(filtered).allMatch(DexAnnotationSet::isEmpty)) {
-        return DexAnnotationSetRefList.empty();
-      }
-      return new DexAnnotationSetRefList(filtered);
-    }
-  }
-
-  private DexAnnotationSet stripAnnotations(DexAnnotationSet annotations,
-      Predicate<DexAnnotation> filter) {
-    ArrayList<DexAnnotation> filtered = null;
-    for (int i = 0; i < annotations.annotations.length; i++) {
-      DexAnnotation annotation = annotations.annotations[i];
-      if (filter.test(annotation)) {
-        if (filtered != null) {
-          filtered.add(annotation);
-        }
-      } else {
-        if (filtered == null) {
-          filtered = new ArrayList<>(annotations.annotations.length);
-          for (int j = 0; j < i; j++) {
-            filtered.add(annotations.annotations[j]);
-          }
-        }
-      }
-    }
-    if (filtered == null) {
-      return annotations;
-    } else if (filtered.isEmpty()) {
-      return DexAnnotationSet.empty();
-    } else {
-      return new DexAnnotationSet(filtered.toArray(new DexAnnotation[filtered.size()]));
-    }
-  }
 }
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 25df235..c359fc7 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 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;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
@@ -552,14 +551,18 @@
 
   private void processAnnotations(DexAnnotation[] annotations) {
     for (DexAnnotation annotation : annotations) {
-      DexType type = annotation.annotation.type;
-      if (liveTypes.contains(type)) {
-        // The type of this annotation is already live, so pick up its dependencies.
-        handleAnnotationOfLiveType(annotation);
-      } else {
-        // Remember this annotation for later.
-        deferredAnnotations.computeIfAbsent(type, ignore -> new HashSet<>()).add(annotation);
-      }
+      processAnnotation(annotation);
+    }
+  }
+
+  private void processAnnotation(DexAnnotation annotation) {
+    DexType type = annotation.annotation.type;
+    if (liveTypes.contains(type)) {
+      // The type of this annotation is already live, so pick up its dependencies.
+      handleAnnotationOfLiveType(annotation);
+    } else {
+      // Remember this annotation for later.
+      deferredAnnotations.computeIfAbsent(type, ignore -> new HashSet<>()).add(annotation);
     }
   }
 
@@ -1239,9 +1242,7 @@
         }
       }
       processAnnotations(method.annotations.annotations);
-      for (DexAnnotationSet parameterAnnotation : method.parameterAnnotations.values) {
-        processAnnotations(parameterAnnotation.annotations);
-      }
+      method.parameterAnnotationsList.forEachAnnotation(this::processAnnotation);
       if (protoLiteExtension != null && protoLiteExtension.appliesTo(method)) {
         protoLiteExtension.processMethod(method, new UseRegistry(method), protoLiteFields);
       } else {
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 5f5237b..91cab92 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -137,8 +137,7 @@
     parse(ImmutableList.of(new ProguardConfigurationSourceFile(path)));
   }
 
-  // package visible for testing
-  void parse(ProguardConfigurationSource source) {
+  public void parse(ProguardConfigurationSource source) {
     parse(ImmutableList.of(source));
   }
 
@@ -968,6 +967,11 @@
     private Path parseFileName() throws ProguardRuleParserException {
       TextPosition start = getPosition();
       skipWhitespace();
+
+      if (baseDirectory == null) {
+        throw parseError("Options with file names are not supported", start);
+      }
+
       String fileName = acceptString(character ->
           character != File.pathSeparatorChar
               && !Character.isWhitespace(character)
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceBytes.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceBytes.java
new file mode 100644
index 0000000..3f379a4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceBytes.java
@@ -0,0 +1,47 @@
+// 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.origin.Origin;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+
+public class ProguardConfigurationSourceBytes implements ProguardConfigurationSource {
+  private final byte[] bytes;
+  private final Origin origin;
+
+  public ProguardConfigurationSourceBytes(byte[] bytes, Origin origin) {
+    this.bytes = bytes;
+    this.origin = origin;
+  }
+
+  public ProguardConfigurationSourceBytes(InputStream in, Origin origin) throws IOException {
+    this(ByteStreams.toByteArray(in), origin);
+  }
+
+  @Override
+  public String get() throws IOException {
+    return new String(bytes, StandardCharsets.UTF_8);
+  }
+
+  @Override
+  public Path getBaseDirectory() {
+    // Options with relative names (including -include) is not supported.
+    return null;
+  }
+
+  @Override
+  public String getName() {
+    return origin.toString();
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return origin;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index d8dffea..1b026c0 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -589,5 +589,9 @@
         throw new CompilationError("Unsupported source file type", new PathOrigin(file));
       }
     }
+
+    public List<ProgramResourceProvider> getProgramResourceProviders() {
+      return programResourceProviders;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java b/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
index 9fd91d9..99149ff 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.utils;
 
+import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
 import java.io.FileNotFoundException;
@@ -19,6 +20,10 @@
     this.origin = origin;
   }
 
+  public ExceptionDiagnostic(ResourceException e) {
+    this(e, e.getOrigin());
+  }
+
   @Override
   public Origin getOrigin() {
     return origin;
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 3086662..4c40d61 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -4,10 +4,10 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.ClassFileConsumer;
-import com.android.tools.r8.DexFilePerClassFileConsumer;
-import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.DataResourceConsumer;
 import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.ProgramConsumer;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.dex.Marker;
@@ -183,6 +183,9 @@
   public OffOrAuto interfaceMethodDesugaring = OffOrAuto.Auto;
   // Defines try-with-resources rewriter behavior.
   public OffOrAuto tryWithResourcesDesugaring = OffOrAuto.Auto;
+  // Flag to turn on/off processing of @dalvik.annotation.codegen.CovariantReturnType and
+  // @dalvik.annotation.codegen.CovariantReturnType$CovariantReturnTypes.
+  public boolean processCovariantReturnTypeAnnotations = true;
 
   // Whether or not to check for valid multi-dex builds.
   //
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index 63b95b3..ebb7ce6 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -8,6 +8,8 @@
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
 import java.util.ArrayList;
 import java.util.Collection;
 
@@ -80,7 +82,15 @@
       if (errorCount != 0) {
         AbortException abort;
         if (lastError != null && lastError.getDiagnosticMessage() != null) {
-          abort = new AbortException("Error: " + lastError.getDiagnosticMessage());
+          StringBuilder builder = new StringBuilder("Error: ");
+          if (lastError.getOrigin() != Origin.unknown()) {
+            builder.append(lastError.getOrigin()).append(", ");
+          }
+          if (lastError.getPosition() != Position.UNKNOWN) {
+            builder.append(lastError.getPosition()).append(", ");
+          }
+          builder.append(lastError.getDiagnosticMessage());
+          abort = new AbortException(builder.toString());
         } else {
           abort = new AbortException();
         }
diff --git a/src/test/debugTestResourcesKotlin/KotlinInline.kt b/src/test/debugTestResourcesKotlin/KotlinInline.kt
index 7f914e4..4b55c57 100644
--- a/src/test/debugTestResourcesKotlin/KotlinInline.kt
+++ b/src/test/debugTestResourcesKotlin/KotlinInline.kt
@@ -47,6 +47,30 @@
         emptyMethod(-1)
     }
 
+    // Double inlining
+    fun testNestedInlining() {
+        val l1 = Int.MAX_VALUE
+        val l2 = Int.MIN_VALUE
+        inlinee1(l1, l2)
+    }
+    inline fun inlinee1(a: Int, b: Int) {
+        val c = a - 2
+        inlinee2(1) {
+            val left = a + b
+            val right = a - b
+            foo(left, right)
+        }
+        inlinee2(c) {
+            foo(b, a)
+        }
+    }
+
+    inline fun inlinee2(p: Int, block: () -> Unit) {
+        if (p > 0) {
+            block()
+        }
+    }
+
     companion object {
         @JvmStatic fun main(args: Array<String>) {
             println("Hello world!")
@@ -54,6 +78,7 @@
             instance.processObject(instance, instance::printObject)
             instance.invokeInlinedFunctions()
             instance.singleInline()
+            instance.testNestedInlining()
         }
     }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
index fc22895..2265c3e 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
@@ -3,14 +3,23 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.graph.invokesuper.Consumer;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+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.utils.ListUtils;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
 
 // Helper to check that a particular error occurred.
-class DiagnosticsChecker implements DiagnosticsHandler {
+public class DiagnosticsChecker implements DiagnosticsHandler {
   public List<Diagnostic> errors = new ArrayList<>();
   public List<Diagnostic> warnings = new ArrayList<>();
   public List<Diagnostic> infos = new ArrayList<>();
@@ -51,4 +60,64 @@
       throw e;
     }
   }
+
+  public static Diagnostic checkDiagnostic(Diagnostic diagnostic, Consumer<Origin> originChecker,
+      int lineStart, int columnStart, String... messageParts) {
+    if (originChecker != null) {
+      originChecker.accept(diagnostic.getOrigin());
+    }
+    TextPosition position;
+    if (diagnostic.getPosition() instanceof TextRange) {
+      position = ((TextRange) diagnostic.getPosition()).getStart();
+    } else {
+      position = ((TextPosition) diagnostic.getPosition());
+    }
+    assertEquals(lineStart, position.getLine());
+    assertEquals(columnStart, position.getColumn());
+    for (String part : messageParts) {
+      assertTrue(diagnostic.getDiagnosticMessage() + " doesn't contain \"" + part + "\"",
+          diagnostic.getDiagnosticMessage().contains(part));
+    }
+    return diagnostic;
+  }
+
+  public static Diagnostic checkDiagnostic(Diagnostic diagnostic, Consumer<Origin> originChecker,
+      String... messageParts) {
+    if (originChecker != null) {
+      originChecker.accept(diagnostic.getOrigin());
+    }
+    assertEquals(diagnostic.getPosition(), Position.UNKNOWN);
+    for (String part : messageParts) {
+      assertTrue(diagnostic.getDiagnosticMessage() + " doesn't contain \"" + part + "\"",
+          diagnostic.getDiagnosticMessage().contains(part));
+    }
+    return diagnostic;
+  }
+
+  static class PathOriginChecker implements Consumer<Origin> {
+    private final Path path;
+    PathOriginChecker(Path path) {
+      this.path = path;
+    }
+
+    public void accept(Origin origin) {
+      if (path != null) {
+        assertEquals(path, ((PathOrigin) origin).getPath());
+      } else {
+        assertSame(Origin.unknown(), origin);
+      }
+    }
+  }
+
+  public static Diagnostic checkDiagnostic(Diagnostic diagnostic, Path path,
+      int lineStart, int columnStart, String... messageParts) {
+    return checkDiagnostic(diagnostic, new PathOriginChecker(path), lineStart, columnStart,
+        messageParts);
+  }
+
+  public static Diagnostic checkDiagnostics(List<Diagnostic> diagnostics, Path path,
+      int lineStart, int columnStart, String... messageParts) {
+    assertEquals(1, diagnostics.size());
+    return checkDiagnostic(diagnostics.get(0), path, lineStart, columnStart, messageParts);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 2bb8d75..96591d8 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -153,19 +153,26 @@
   protected Path jarTestClasses(Class... classes) throws IOException {
     Path jar = File.createTempFile("junit", ".jar", temp.getRoot()).toPath();
     try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar.toFile()))) {
-      for (Class clazz : classes) {
-        try (FileInputStream in =
-            new FileInputStream(ToolHelper.getClassFileForTestClass(clazz).toFile())) {
-          out.putNextEntry(new ZipEntry(ToolHelper.getJarEntryForTestClass(clazz)));
-          ByteStreams.copy(in, out);
-          out.closeEntry();
-        }
-      }
+      addTestClassesToJar(out, classes);
     }
     return jar;
   }
 
   /**
+   * Create a temporary JAR file containing the specified test classes.
+   */
+  protected void addTestClassesToJar(JarOutputStream out, Class... classes) throws IOException {
+    for (Class clazz : classes) {
+      try (FileInputStream in =
+          new FileInputStream(ToolHelper.getClassFileForTestClass(clazz).toFile())) {
+        out.putNextEntry(new ZipEntry(ToolHelper.getJarEntryForTestClass(clazz)));
+        ByteStreams.copy(in, out);
+        out.closeEntry();
+      }
+    }
+  }
+
+  /**
    * Create a temporary JAR file containing all test classes in a package.
    */
   protected Path jarTestClassesInPackage(Package pkg) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java b/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
index 45e3c96..eca0441 100644
--- a/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
@@ -51,8 +51,9 @@
       .add(KotlinD8Config.DEBUGGEE_KOTLIN_JAR, ContinuousSteppingTest::allVersions)
       .addAll(findAllJarsIn(Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR)),
           ContinuousSteppingTest::fromAndroidN)
-      .addAll(findAllJarsIn(Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR)),
-          ContinuousSteppingTest::fromAndroidO)
+      // TODO(b/79911828) Investigate timeout issues for Android O examples.
+      //  .addAll(findAllJarsIn(Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR)),
+      //      ContinuousSteppingTest::fromAndroidO)
       .build();
 
   private static final Map<Path, DebugTestConfig> compiledJarConfig = new HashMap<>();
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index e8d21f2..25356ae 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -390,6 +390,14 @@
     return inspect(t -> t.checkLocal(localName));
   }
 
+  protected final JUnit3Wrapper.Command checkLocals(String... localNames) {
+    return inspect(t -> {
+      for (String str : localNames) {
+        t.checkLocal(str);
+      }
+    });
+  }
+
   protected final JUnit3Wrapper.Command checkLocal(String localName, Value expectedValue) {
     return inspect(t -> t.checkLocal(localName, expectedValue));
   }
@@ -398,6 +406,14 @@
     return inspect(t -> t.checkNoLocal(localName));
   }
 
+  protected final JUnit3Wrapper.Command checkNoLocals(String... localNames) {
+    return inspect(t -> {
+      for (String str : localNames) {
+        t.checkNoLocal(str);
+      }
+    });
+  }
+
   protected final JUnit3Wrapper.Command checkNoLocal() {
     return inspect(t -> {
       List<String> localNames = t.getLocalNames();
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinDebugTestBase.java b/src/test/java/com/android/tools/r8/debug/KotlinDebugTestBase.java
index 5ddd98c..63bc007 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinDebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinDebugTestBase.java
@@ -4,9 +4,6 @@
 
 package com.android.tools.r8.debug;
 
-import com.android.tools.r8.ToolHelper;
-import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.List;
 import org.apache.harmony.jpda.tests.framework.jdwp.Frame.Variable;
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
index cdba21b..3837bb1 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
@@ -6,40 +6,46 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.ToolHelper;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import org.apache.harmony.jpda.tests.framework.jdwp.Value;
 import org.junit.Test;
 
-// TODO check double-depth inline (an inline in another inline)
 public class KotlinInlineTest extends KotlinDebugTestBase {
 
+  public static final String DEBUGGEE_CLASS = "KotlinInline";
+  public static final String SOURCE_FILE = "KotlinInline.kt";
+
   @Test
   public void testStepOverInline() throws Throwable {
     String methodName = "singleInline";
     runDebugTest(
         getD8Config(),
-        "KotlinInline",
-        breakpoint("KotlinInline", methodName),
+        DEBUGGEE_CLASS,
+        breakpoint(DEBUGGEE_CLASS, methodName),
         run(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
-          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(SOURCE_FILE, s.getSourceFile());
           assertEquals(41, s.getLineNumber());
           s.checkLocal("this");
         }),
         stepOver(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
-          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(SOURCE_FILE, s.getSourceFile());
           assertEquals(42, s.getLineNumber());
           s.checkLocal("this");
         }),
         kotlinStepOver(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
-          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(SOURCE_FILE, s.getSourceFile());
           assertEquals(43, s.getLineNumber());
           s.checkLocal("this");
         }),
@@ -51,29 +57,29 @@
     String methodName = "singleInline";
     runDebugTest(
         getD8Config(),
-        "KotlinInline",
-        breakpoint("KotlinInline", methodName),
+        DEBUGGEE_CLASS,
+        breakpoint(DEBUGGEE_CLASS, methodName),
         run(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
-          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(SOURCE_FILE, s.getSourceFile());
           assertEquals(41, s.getLineNumber());
           s.checkLocal("this");
         }),
         stepOver(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
-          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(SOURCE_FILE, s.getSourceFile());
           assertEquals(42, s.getLineNumber());
           s.checkLocal("this");
         }),
         stepInto(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
-          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(SOURCE_FILE, s.getSourceFile());
           // The actual line number (the one encoded in debug information) is different than the
           // source file one.
           // TODO(shertz) extract original line number from JSR-45's SMAP (only supported on
@@ -89,47 +95,65 @@
     String methodName = "singleInline";
     runDebugTest(
         getD8Config(),
-        "KotlinInline",
-        breakpoint("KotlinInline", methodName),
+        DEBUGGEE_CLASS,
+        breakpoint(DEBUGGEE_CLASS, methodName),
         run(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
-          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(SOURCE_FILE, s.getSourceFile());
           assertEquals(41, s.getLineNumber());
           s.checkLocal("this");
         }),
         stepOver(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
-          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(SOURCE_FILE, s.getSourceFile());
           assertEquals(42, s.getLineNumber());
           s.checkLocal("this");
         }),
         stepInto(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
         }),
         kotlinStepOut(),
         inspect(s -> {
-          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(DEBUGGEE_CLASS, s.getClassName());
           assertEquals(methodName, s.getMethodName());
-          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(SOURCE_FILE, s.getSourceFile());
           assertEquals(43, s.getLineNumber());
           s.checkLocal("this");
         }),
         run());
   }
 
+  private static String mangleFunctionNameFromInlineScope(String functionName) {
+    return "$i$f$" + functionName;
+  }
+
+  private static String mangleLambdaNameFromInlineScope(String functionName, int lambdaId) {
+    assert lambdaId > 0;
+    return "$i$a$" + lambdaId + "$" + functionName;
+  }
+
+  private static String mangleLvNameFromInlineScope(String lvName, int inlineDepth) {
+    assert inlineDepth > 0;
+    StringBuilder builder = new StringBuilder(lvName);
+    for (int i = 0; i < inlineDepth; ++i) {
+      builder.append("$iv");
+    }
+    return builder.toString();
+  }
+
   @Test
   public void testKotlinInline() throws Throwable {
     final String inliningMethodName = "invokeInlinedFunctions";
     runDebugTest(
         getD8Config(),
-        "KotlinInline",
-        breakpoint("KotlinInline", inliningMethodName),
+        DEBUGGEE_CLASS,
+        breakpoint(DEBUGGEE_CLASS, inliningMethodName),
         run(),
         inspect(s -> {
           assertEquals(inliningMethodName, s.getMethodName());
@@ -157,8 +181,8 @@
           s.checkLocal("this");
           s.checkLocal("inA", Value.createInt(1));
           // This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
-          s.checkLocal("$i$f$inlinedA");
-          s.checkLocal("$i$a$1$inlinedA");
+          s.checkLocal(mangleFunctionNameFromInlineScope("inlinedA"));
+          s.checkLocal(mangleLambdaNameFromInlineScope("inlinedA", 1));
         }),
         stepInto(),
         inspect(s -> {
@@ -181,10 +205,144 @@
           s.checkLocal("this");
           s.checkLocal("inB", Value.createInt(2));
           // This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
-          s.checkLocal("$i$f$inlinedB");
-          s.checkLocal("$i$a$1$inlinedB");
+          s.checkLocal(mangleFunctionNameFromInlineScope("inlinedB"));
+          s.checkLocal(mangleLambdaNameFromInlineScope("inlinedB", 1));
         }),
         run());
   }
 
+  @Test
+  public void testNestedInlining() throws Throwable {
+    // Count the number of lines in the source file. This is needed to check that inlined code
+    // refers to non-existing line numbers.
+    Path sourceFilePath = Paths.get(ToolHelper.TESTS_DIR, "debugTestResourcesKotlin", SOURCE_FILE);
+    assert sourceFilePath.toFile().exists();
+    final int maxLineNumber = Files.readAllLines(sourceFilePath).size();
+    final String inliningMethodName = "testNestedInlining";
+
+    // Local variables that represent the scope (start,end) of function's code that has been
+    // inlined.
+    final String inlinee1_inlineScope = mangleFunctionNameFromInlineScope("inlinee1");
+    final String inlinee2_inlineScope = mangleFunctionNameFromInlineScope("inlinee2");
+
+    // Local variables that represent the scope (start,end) of lambda's code that has been inlined.
+    final String inlinee2_lambda1_inlineScope = mangleLambdaNameFromInlineScope("inlinee2", 1);
+    final String inlinee2_lambda2_inlineScope = mangleLambdaNameFromInlineScope("inlinee2", 2);
+    final String c_mangledLvName = mangleLvNameFromInlineScope("c", 1);
+    final String left_mangledLvName = mangleLvNameFromInlineScope("left", 1);
+    final String right_mangledLvName = mangleLvNameFromInlineScope("right", 1);
+    final String p_mangledLvName = mangleLvNameFromInlineScope("p", 2);
+
+    runDebugTest(
+        getD8Config(),
+        DEBUGGEE_CLASS,
+        breakpoint(DEBUGGEE_CLASS, inliningMethodName),
+        run(),
+        inspect(s -> {
+          assertEquals(inliningMethodName, s.getMethodName());
+          assertEquals(52, s.getLineNumber());
+          s.checkLocal("this");
+        }),
+        checkLocal("this"),
+        checkNoLocals("l1", "l2"),
+        stepOver(),
+        checkLine(SOURCE_FILE, 53),
+        checkLocals("this", "l1"),
+        checkNoLocal("l2"),
+        stepOver(),
+        checkLine(SOURCE_FILE, 54),
+        checkLocals("this", "l1", "l2"),
+        stepInto(),
+        // We jumped into 1st inlinee but the current method is the same
+        checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+        checkLocal(inlinee1_inlineScope),
+        inspect(state -> {
+          assertEquals(SOURCE_FILE, state.getSourceFile());
+          assertTrue(state.getLineNumber() > maxLineNumber);
+        }),
+        checkNoLocal(c_mangledLvName),
+        stepInto(),
+        checkLocal(c_mangledLvName),
+        stepInto(),
+        // We jumped into 2nd inlinee which is nested in the 1st inlinee
+        checkLocal(inlinee2_inlineScope),
+        checkLocal(inlinee1_inlineScope),
+        inspect(state -> {
+          assertEquals(SOURCE_FILE, state.getSourceFile());
+          assertTrue(state.getLineNumber() > maxLineNumber);
+        }),
+        // We must see the local variable "p" with a 2-level inline depth.
+        checkLocal(p_mangledLvName),
+        checkNoLocals(left_mangledLvName, right_mangledLvName),
+        // Enter the if block of inlinee2
+        stepInto(),
+        checkLocal(p_mangledLvName),
+        checkNoLocals(left_mangledLvName, right_mangledLvName),
+        // Enter the inlined lambda
+        stepInto(),
+        checkLocal(p_mangledLvName),
+        checkLocal(inlinee2_lambda1_inlineScope),
+        checkNoLocals(left_mangledLvName, right_mangledLvName),
+        stepInto(),
+        checkLocal(inlinee2_lambda1_inlineScope),
+        checkLocal(left_mangledLvName),
+        checkNoLocal(right_mangledLvName),
+        stepInto(),
+        checkLocals(left_mangledLvName, right_mangledLvName),
+        // Enter "foo"
+        stepInto(),
+        checkMethod(DEBUGGEE_CLASS, "foo"),
+        checkLine(SOURCE_FILE, 34),
+        stepOut(),
+        // We're back to the inline section, at the end of the lambda
+        inspect(state -> {
+          assertEquals(SOURCE_FILE, state.getSourceFile());
+          assertTrue(state.getLineNumber() > maxLineNumber);
+        }),
+        checkLocal(inlinee1_inlineScope),
+        checkLocal(inlinee2_inlineScope),
+        checkLocal(inlinee2_lambda1_inlineScope),
+        checkNoLocal(inlinee2_lambda2_inlineScope),
+        stepInto(),
+        // We're in inlinee2, after the call to the inlined lambda.
+        checkLocal(inlinee1_inlineScope),
+        checkLocal(inlinee2_inlineScope),
+        checkNoLocal(inlinee2_lambda1_inlineScope),
+        checkNoLocal(inlinee2_lambda2_inlineScope),
+        checkLocal(p_mangledLvName),
+        stepInto(),
+        // We're out of inlinee2
+        checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+        checkLocal(inlinee1_inlineScope),
+        checkNoLocal(inlinee2_inlineScope),
+        checkNoLocal(inlinee2_lambda1_inlineScope),
+        // Enter the new call to "inlinee2"
+        stepInto(),
+        checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+        checkLocal(inlinee1_inlineScope),
+        checkLocal(inlinee2_inlineScope),
+        checkNoLocal(inlinee2_lambda1_inlineScope),
+        checkNoLocal(inlinee2_lambda2_inlineScope),
+        checkNoLocal(p_mangledLvName),
+        stepInto(),
+        checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+        checkLocal(inlinee1_inlineScope),
+        checkLocal(inlinee2_inlineScope),
+        checkNoLocal(inlinee2_lambda1_inlineScope),
+        checkNoLocal(inlinee2_lambda2_inlineScope),
+        checkNoLocal(p_mangledLvName),
+        // We enter the 2nd lambda
+        stepInto(),
+        checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+        checkLocal(inlinee1_inlineScope),
+        checkLocal(inlinee2_inlineScope),
+        checkNoLocal(inlinee2_lambda1_inlineScope),
+        checkLocal(inlinee2_lambda2_inlineScope),
+        // Enter the call to "foo"
+        stepInto(),
+        checkMethod(DEBUGGEE_CLASS, "foo"),
+        checkLine(SOURCE_FILE, 34),
+        run());
+  }
+
 }
diff --git a/src/test/java/com/android/tools/r8/debug/UnusedCheckCastTargetOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/UnusedCheckCastTargetOptimizationTest.java
new file mode 100644
index 0000000..8fd6cd8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/UnusedCheckCastTargetOptimizationTest.java
@@ -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 com.android.tools.r8.debug;
+
+public class UnusedCheckCastTargetOptimizationTest {
+
+  class Super {}
+
+  class Subclass extends Super {}
+
+  public static void main(String[] args) {
+    Super[] b = new Subclass[10];
+    Subclass[] c = (Subclass[]) b;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/UnusedCheckCastTargetOptimizationTestRunner.java b/src/test/java/com/android/tools/r8/debug/UnusedCheckCastTargetOptimizationTestRunner.java
new file mode 100644
index 0000000..2b93218
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/UnusedCheckCastTargetOptimizationTestRunner.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+import org.junit.Test;
+
+public class UnusedCheckCastTargetOptimizationTestRunner extends DebugTestBase {
+
+  private static final Class MAIN_CLASS = UnusedCheckCastTargetOptimizationTest.class;
+  private static final Class SUPER_CLASS = UnusedCheckCastTargetOptimizationTest.Super.class;
+  private static final Class SUBCLASS_CLASS = UnusedCheckCastTargetOptimizationTest.Subclass.class;
+  private static final String FILE = MAIN_CLASS.getSimpleName() + ".java";
+  private static final String NAME = MAIN_CLASS.getCanonicalName();
+
+  @Test
+  public void test() throws Throwable {
+    runDebugTest(
+        new D8DebugTestConfig().compileAndAddClasses(temp, MAIN_CLASS, SUPER_CLASS, SUBCLASS_CLASS),
+        NAME,
+        breakpoint(NAME, "main", 14),
+        run(),
+        checkLine(FILE, 14),
+        checkLocal("b"),
+        checkNoLocal("c"),
+        stepOver(),
+        checkLine(FILE, 15),
+        checkLocal("b"),
+        checkLocal("c"),
+        run());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index 9edc735..89935a7 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexCode.Try;
@@ -25,6 +24,7 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
@@ -80,7 +80,7 @@
             holder, dexItemFactory.createProto(dexItemFactory.voidType), "theMethod"),
         MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC, false),
         DexAnnotationSet.empty(),
-        DexAnnotationSetRefList.empty(),
+        ParameterAnnotationsList.empty(),
         code);
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/A.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/A.java
new file mode 100644
index 0000000..c700163
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/A.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations;
+
+public class A {
+  public A method() {
+    return new A();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/B.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/B.java
new file mode 100644
index 0000000..7d8134f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/B.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations;
+
+public class B extends A {
+  @Override
+  public A method() {
+    return new B();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/C.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/C.java
new file mode 100644
index 0000000..90030d5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/C.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations;
+
+public class C extends B {
+  @Override
+  public A method() {
+    return new C();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/Client.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/Client.java
new file mode 100644
index 0000000..0c6bd44
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/Client.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations;
+
+public class Client {
+  public static void main(String[] args) {
+    A a = new A().method();
+    A b = new B().method();
+    A c = new C().method();
+
+    System.out.println("a=" + a.getClass().getSimpleName());
+    System.out.println("b=" + b.getClass().getSimpleName());
+    System.out.println("c=" + c.getClass().getSimpleName());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnType.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnType.java
new file mode 100644
index 0000000..3d7a7fa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnType.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+// This is a copy of dalvik.annotation.codegen.CovariantReturnType.
+@Repeatable(CovariantReturnType.CovariantReturnTypes.class)
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.METHOD})
+public @interface CovariantReturnType {
+  Class<?> returnType();
+
+  int presentAfter();
+
+  @Retention(RetentionPolicy.CLASS)
+  @Target({ElementType.METHOD})
+  @interface CovariantReturnTypes {
+    CovariantReturnType[] value();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
new file mode 100644
index 0000000..9c58d25
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
@@ -0,0 +1,191 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import java.util.Collections;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class CovariantReturnTypeAnnotationTransformerTest extends AsmTestBase {
+  public static final String PACKAGE_NAME = "com/android/tools/r8/ir/desugar/annotations";
+  public static final String CRT_NAME = "dalvik/annotation/codegen/CovariantReturnType";
+  public static final String CRTS_SIMPLE_NAME = "CovariantReturnTypes";
+
+  @Test
+  public void testVersion1WithClient1And2() throws Exception {
+    AndroidApp input =
+        buildAndroidApp(
+            ToolHelper.getClassAsBytes(Client.class),
+            ToolHelper.getClassAsBytes(A.class),
+            ToolHelper.getClassAsBytes(B.class),
+            ToolHelper.getClassAsBytes(C.class));
+
+    // Version 1 of the library should always work.
+    succeedsIndependentOfFlag(input, false);
+  }
+
+  @Test
+  public void testVersion1WithClient3() throws Exception {
+    AndroidApp input =
+        buildAndroidApp(
+            com.android.tools.r8.ir.desugar.annotations.version3.ClientDump.dump(),
+            ToolHelper.getClassAsBytes(A.class),
+            ToolHelper.getClassAsBytes(B.class),
+            ToolHelper.getClassAsBytes(C.class));
+
+    // There will be no methods with the signature "L.../B;->method()L.../B;" and
+    // "L.../C;->method()L.../C;".
+    failsIndependentOfFlag(input);
+  }
+
+  @Ignore("b/78618808")
+  @Test
+  public void testVersion2WithClient1And2() throws Exception {
+    AndroidApp input =
+        buildAndroidApp(
+            ToolHelper.getClassAsBytes(Client.class),
+            ToolHelper.getClassAsBytes(A.class),
+            com.android.tools.r8.ir.desugar.annotations.version2.BDump.dump(),
+            com.android.tools.r8.ir.desugar.annotations.version2.CDump.dump());
+
+    // Version 2 of the library should always work.
+    succeedsIndependentOfFlag(input, true);
+  }
+
+  @Ignore("b/78618808")
+  @Test
+  public void testVersion2WithClient3() throws Exception {
+    AndroidApp input =
+        buildAndroidApp(
+            com.android.tools.r8.ir.desugar.annotations.version3.ClientDump.dump(),
+            ToolHelper.getClassAsBytes(A.class),
+            com.android.tools.r8.ir.desugar.annotations.version2.BDump.dump(),
+            com.android.tools.r8.ir.desugar.annotations.version2.CDump.dump());
+
+    // If CovariantReturnType annotations are processed, then synthetic methods with the signatures
+    // "L.../B;->method()L.../B;" and "L.../C;->method()L.../C;" will be added by D8.
+    succeedsWithOption(input, true, true);
+
+    // If CovariantReturnType annotations are ignored, then there will be no methods with the
+    // signatures "L.../B;->method()L.../B;" and "L.../C;->method()L.../C;".
+    failsWithOption(input, false);
+  }
+
+  @Test
+  public void testVersion3WithClient3() throws Exception {
+    AndroidApp input =
+        buildAndroidApp(
+            com.android.tools.r8.ir.desugar.annotations.version3.ClientDump.dump(),
+            ToolHelper.getClassAsBytes(A.class),
+            com.android.tools.r8.ir.desugar.annotations.version3.BDump.dump(),
+            com.android.tools.r8.ir.desugar.annotations.version3.CDump.dump());
+
+    // Version 3 of the library should always work.
+    succeedsIndependentOfFlag(input, false);
+  }
+
+  @Test
+  public void testVersion3WithClient1And2() throws Exception {
+    AndroidApp input =
+        buildAndroidApp(
+            ToolHelper.getClassAsBytes(Client.class),
+            ToolHelper.getClassAsBytes(A.class),
+            com.android.tools.r8.ir.desugar.annotations.version3.BDump.dump(),
+            com.android.tools.r8.ir.desugar.annotations.version3.CDump.dump());
+
+    // Version 3 of the library should always work with client 1.
+    succeedsIndependentOfFlag(input, false);
+  }
+
+  private void succeedsWithOption(
+      AndroidApp input, boolean option, boolean checkPresenceOfSyntheticMethods) throws Exception {
+    AndroidApp output =
+        compileWithD8(input, options -> options.processCovariantReturnTypeAnnotations = option);
+    String stdout = runOnArt(output, Client.class.getCanonicalName());
+    Assert.assertEquals(getExpectedOutput(), stdout);
+
+    if (option && checkPresenceOfSyntheticMethods) {
+      checkPresenceOfSyntheticMethods(output);
+    }
+  }
+
+  private void failsWithOption(AndroidApp input, boolean option) throws Exception {
+    AndroidApp output =
+        compileWithD8(input, options -> options.processCovariantReturnTypeAnnotations = option);
+    ToolHelper.ProcessResult result = runOnArtRaw(output, Client.class.getCanonicalName());
+    assertThat(result.stderr, containsString("java.lang.NoSuchMethodError"));
+  }
+
+  private void succeedsIndependentOfFlag(AndroidApp input, boolean checkPresenceOfSyntheticMethods)
+      throws Exception {
+    succeedsWithOption(input, true, checkPresenceOfSyntheticMethods);
+    succeedsWithOption(input, false, checkPresenceOfSyntheticMethods);
+  }
+
+  private void failsIndependentOfFlag(AndroidApp input) throws Exception {
+    failsWithOption(input, true);
+    failsWithOption(input, false);
+  }
+
+  private void checkPresenceOfSyntheticMethods(AndroidApp output) throws Exception {
+    DexInspector inspector = new DexInspector(output);
+
+    // Get classes A, B, and C.
+    DexInspector.ClassSubject clazzA = inspector.clazz(A.class.getCanonicalName());
+    assertThat(clazzA, isPresent());
+
+    DexInspector.ClassSubject clazzB = inspector.clazz(B.class.getCanonicalName());
+    assertThat(clazzB, isPresent());
+
+    DexInspector.ClassSubject clazzC = inspector.clazz(C.class.getCanonicalName());
+    assertThat(clazzC, isPresent());
+
+    // Check that the original methods are there, and that they are not synthetic.
+    DexInspector.MethodSubject methodA =
+        clazzB.method(A.class.getCanonicalName(), "method", Collections.emptyList());
+    assertThat(methodA, isPresent());
+    Assert.assertTrue(!methodA.getMethod().isSyntheticMethod());
+
+    DexInspector.MethodSubject methodB =
+        clazzB.method(A.class.getCanonicalName(), "method", Collections.emptyList());
+    assertThat(methodB, isPresent());
+    Assert.assertTrue(!methodB.getMethod().isSyntheticMethod());
+
+    DexInspector.MethodSubject methodC =
+        clazzC.method(A.class.getCanonicalName(), "method", Collections.emptyList());
+    assertThat(methodC, isPresent());
+    Assert.assertTrue(!methodC.getMethod().isSyntheticMethod());
+
+    // Check that a synthetic method has been added to class B.
+    DexInspector.MethodSubject methodB2 =
+        clazzB.method(B.class.getCanonicalName(), "method", Collections.emptyList());
+    assertThat(methodB2, isPresent());
+    Assert.assertTrue(methodB2.getMethod().isSyntheticMethod());
+
+    // Check that two synthetic methods have been added to class C.
+    DexInspector.MethodSubject methodC2 =
+        clazzC.method(B.class.getCanonicalName(), "method", Collections.emptyList());
+    assertThat(methodC2, isPresent());
+    Assert.assertTrue(methodC2.getMethod().isSyntheticMethod());
+
+    DexInspector.MethodSubject methodC3 =
+        clazzC.method(C.class.getCanonicalName(), "method", Collections.emptyList());
+    assertThat(methodC3, isPresent());
+    Assert.assertTrue(methodC3.getMethod().isSyntheticMethod());
+  }
+
+  private String getExpectedOutput() {
+    return "a=A\nb=B\nc=C\n";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/B.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/B.java
new file mode 100644
index 0000000..342e3d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/B.java
@@ -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 com.android.tools.r8.ir.desugar.annotations.version2;
+
+import com.android.tools.r8.ir.desugar.annotations.A;
+import com.android.tools.r8.ir.desugar.annotations.CovariantReturnType;
+
+public class B extends A {
+  @CovariantReturnType(returnType = B.class, presentAfter = 25)
+  @Override
+  public A method() {
+    return new B();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/BDump.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/BDump.java
new file mode 100644
index 0000000..3dadc2b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/BDump.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations.version2;
+
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.CRT_NAME;
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.PACKAGE_NAME;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+// Generated by running tools/asmifier.py on build/classes/test/com/android/tools/r8/ir/desugar/
+// annotations/version2/B.class, removing the subpackage "version2" from all class names, and
+// manually changing the name of the CovariantReturnType annotation to
+// "Ldalvik/annotation/codegen/CovariantReturnType;".
+public class BDump implements Opcodes {
+  public static byte[] dump() throws Exception {
+
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+    AnnotationVisitor av0;
+
+    cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, PACKAGE_NAME + "/B", null, PACKAGE_NAME + "/A", null);
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/A", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "method", "()L" + PACKAGE_NAME + "/A;", null, null);
+      {
+        av0 = mv.visitAnnotation("L" + CRT_NAME + ";", false);
+        av0.visit("returnType", Type.getType("L" + PACKAGE_NAME + "/B;"));
+        av0.visit("presentAfter", new Integer(25));
+        av0.visitEnd();
+      }
+      mv.visitCode();
+      mv.visitTypeInsn(NEW, PACKAGE_NAME + "/B");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/B", "<init>", "()V", false);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(2, 1);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/C.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/C.java
new file mode 100644
index 0000000..52fe17f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/C.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations.version2;
+
+import com.android.tools.r8.ir.desugar.annotations.A;
+import com.android.tools.r8.ir.desugar.annotations.CovariantReturnType;
+
+public class C extends B {
+  @CovariantReturnType(returnType = B.class, presentAfter = 25)
+  @CovariantReturnType(returnType = C.class, presentAfter = 28)
+  @Override
+  public A method() {
+    return new C();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.java
new file mode 100644
index 0000000..bc06250
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.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.ir.desugar.annotations.version2;
+
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.*;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+// Generated by running tools/asmifier.py on build/classes/test/com/android/tools/r8/ir/desugar/
+// annotations/version2/C.class, removing the subpackage "version2" from all class names, and
+// manually changing the name of the CovariantReturnType and CovariantReturnTypes annotations to
+// "Ldalvik/annotation/codegen/CovariantReturnType;" and
+// "Ldalvik/annotation/codegen/CovariantReturnType$CovariantReturnTypes;", respectively.
+public class CDump implements Opcodes {
+  public static byte[] dump() throws Exception {
+
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+    AnnotationVisitor av0;
+
+    cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, PACKAGE_NAME + "/C", null, PACKAGE_NAME + "/B", null);
+
+    cw.visitInnerClass(
+        CRT_NAME + "$" + CRTS_SIMPLE_NAME,
+        CRT_NAME,
+        CRTS_SIMPLE_NAME,
+        ACC_PUBLIC + ACC_STATIC + ACC_ANNOTATION + ACC_ABSTRACT + ACC_INTERFACE);
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/B", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "method", "()L" + PACKAGE_NAME + "/A;", null, null);
+      {
+        av0 = mv.visitAnnotation("L" + CRT_NAME + "$" + CRTS_SIMPLE_NAME + ";", false);
+        {
+          AnnotationVisitor av1 = av0.visitArray("value");
+          {
+            AnnotationVisitor av2 = av1.visitAnnotation(null, "L" + CRT_NAME + ";");
+            av2.visit("returnType", Type.getType("L" + PACKAGE_NAME + "/B;"));
+            av2.visit("presentAfter", new Integer(25));
+            av2.visitEnd();
+          }
+          {
+            AnnotationVisitor av2 = av1.visitAnnotation(null, "L" + CRT_NAME + ";");
+            av2.visit("returnType", Type.getType("L" + PACKAGE_NAME + "/C;"));
+            av2.visit("presentAfter", new Integer(28));
+            av2.visitEnd();
+          }
+          av1.visitEnd();
+        }
+        av0.visitEnd();
+      }
+      mv.visitCode();
+      mv.visitTypeInsn(NEW, PACKAGE_NAME + "/C");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/C", "<init>", "()V", false);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(2, 1);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/B.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/B.java
new file mode 100644
index 0000000..1b1fa1c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/B.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations.version3;
+
+import com.android.tools.r8.ir.desugar.annotations.A;
+
+public class B extends A {
+  @Override
+  public B method() {
+    return new B();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/BDump.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/BDump.java
new file mode 100644
index 0000000..91c16c3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/BDump.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations.version3;
+
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.PACKAGE_NAME;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running tools/asmifier.py on build/classes/test/com/android/tools/r8/ir/desugar/
+// annotations/version3/B.class, and removing the subpackage "version3" from all class names.
+public class BDump implements Opcodes {
+  public static byte[] dump() throws Exception {
+
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+    AnnotationVisitor av0;
+
+    cw.visit(
+        V1_8,
+        ACC_PUBLIC + ACC_SUPER,
+        "" + PACKAGE_NAME + "/B",
+        null,
+        "" + PACKAGE_NAME + "/A",
+        null);
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, "" + PACKAGE_NAME + "/A", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "method", "()L" + PACKAGE_NAME + "/B;", null, null);
+      mv.visitCode();
+      mv.visitTypeInsn(NEW, "" + PACKAGE_NAME + "/B");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, "" + PACKAGE_NAME + "/B", "<init>", "()V", false);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(2, 1);
+      mv.visitEnd();
+    }
+    {
+      mv =
+          cw.visitMethod(
+              ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC,
+              "method",
+              "()L" + PACKAGE_NAME + "/A;",
+              null,
+              null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "" + PACKAGE_NAME + "/B", "method", "()L" + PACKAGE_NAME + "/B;", false);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/C.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/C.java
new file mode 100644
index 0000000..fd3bedc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/C.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations.version3;
+
+public class C extends B {
+  @Override
+  public C method() {
+    return new C();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/CDump.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/CDump.java
new file mode 100644
index 0000000..219c3f1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/CDump.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations.version3;
+
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.PACKAGE_NAME;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running tools/asmifier.py on build/classes/test/com/android/tools/r8/ir/desugar/
+// annotations/version3/C.class, and removing the subpackage "version3" from all class names.
+public class CDump implements Opcodes {
+  public static byte[] dump() throws Exception {
+
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+    AnnotationVisitor av0;
+
+    cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, PACKAGE_NAME + "/C", null, PACKAGE_NAME + "/B", null);
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/B", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "method", "()L" + PACKAGE_NAME + "/C;", null, null);
+      mv.visitCode();
+      mv.visitTypeInsn(NEW, PACKAGE_NAME + "/C");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/C", "<init>", "()V", false);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(2, 1);
+      mv.visitEnd();
+    }
+    {
+      mv =
+          cw.visitMethod(
+              ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC,
+              "method",
+              "()L" + PACKAGE_NAME + "/B;",
+              null,
+              null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, PACKAGE_NAME + "/C", "method", "()L" + PACKAGE_NAME + "/C;", false);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    {
+      mv =
+          cw.visitMethod(
+              ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC,
+              "method",
+              "()L" + PACKAGE_NAME + "/A;",
+              null,
+              null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, PACKAGE_NAME + "/C", "method", "()L" + PACKAGE_NAME + "/C;", false);
+      mv.visitInsn(ARETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/Client.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/Client.java
new file mode 100644
index 0000000..f2feffa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/Client.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations.version3;
+
+import com.android.tools.r8.ir.desugar.annotations.A;
+
+public class Client {
+  public static void main(String[] args) {
+    A a = new A().method();
+    B b = new B().method();
+    C c = new C().method();
+
+    System.out.println("a=" + a.getClass().getSimpleName());
+    System.out.println("b=" + b.getClass().getSimpleName());
+    System.out.println("c=" + c.getClass().getSimpleName());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/ClientDump.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/ClientDump.java
new file mode 100644
index 0000000..84971e4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/ClientDump.java
@@ -0,0 +1,144 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations.version3;
+
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.PACKAGE_NAME;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running tools/asmifier.py on build/classes/test/com/android/tools/r8/ir/desugar/
+// annotations/version3/Client.class, and removing the subpackage "version3" from all class names.
+public class ClientDump implements Opcodes {
+  public static byte[] dump() throws Exception {
+
+    ClassWriter cw = new ClassWriter(0);
+    FieldVisitor fv;
+    MethodVisitor mv;
+    AnnotationVisitor av0;
+
+    cw.visit(
+        V1_8, ACC_PUBLIC + ACC_SUPER, PACKAGE_NAME + "/Client", null, "java/lang/Object", null);
+
+    {
+      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      mv.visitCode();
+      mv.visitVarInsn(ALOAD, 0);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(1, 1);
+      mv.visitEnd();
+    }
+    {
+      mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+      mv.visitCode();
+      mv.visitTypeInsn(NEW, PACKAGE_NAME + "/A");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/A", "<init>", "()V", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, PACKAGE_NAME + "/A", "method", "()L" + PACKAGE_NAME + "/A;", false);
+      mv.visitVarInsn(ASTORE, 1);
+      mv.visitTypeInsn(NEW, PACKAGE_NAME + "/B");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/B", "<init>", "()V", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, PACKAGE_NAME + "/B", "method", "()L" + PACKAGE_NAME + "/B;", false);
+      mv.visitVarInsn(ASTORE, 2);
+      mv.visitTypeInsn(NEW, PACKAGE_NAME + "/C");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/C", "<init>", "()V", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, PACKAGE_NAME + "/C", "method", "()L" + PACKAGE_NAME + "/C;", false);
+      mv.visitVarInsn(ASTORE, 3);
+      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
+      mv.visitLdcInsn("a=");
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+          false);
+      mv.visitVarInsn(ALOAD, 1);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Class", "getSimpleName", "()Ljava/lang/String;", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+          false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
+      mv.visitLdcInsn("b=");
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+          false);
+      mv.visitVarInsn(ALOAD, 2);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Class", "getSimpleName", "()Ljava/lang/String;", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+          false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
+      mv.visitInsn(DUP);
+      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
+      mv.visitLdcInsn("c=");
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+          false);
+      mv.visitVarInsn(ALOAD, 3);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Class", "getSimpleName", "()Ljava/lang/String;", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+          false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
+      mv.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      mv.visitInsn(RETURN);
+      mv.visitMaxs(3, 4);
+      mv.visitEnd();
+    }
+    cw.visitEnd();
+
+    return cw.toByteArray();
+  }
+}
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 2ff2f71..9de9f5a 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -29,7 +29,6 @@
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -40,6 +39,7 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Position;
@@ -617,7 +617,7 @@
                     DexString.EMPTY_ARRAY),
                 access,
                 DexAnnotationSet.empty(),
-                DexAnnotationSetRefList.empty(),
+                ParameterAnnotationsList.empty(),
                 code);
         IRCode ir = code.buildIR(method, options, Origin.unknown());
         RegisterAllocator allocator = new LinearScanRegisterAllocator(ir, options);
diff --git a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
index 490162a..3429c2a 100644
--- a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
+++ b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
@@ -12,7 +12,6 @@
 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.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
@@ -30,7 +29,6 @@
     builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
     builder.addProgramFiles(ToolHelper.getClassFileForTestClass(Utils.class));
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
     builder.setMode(CompilationMode.RELEASE);
     builder.addProguardConfiguration(
         ImmutableList.of(
diff --git a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
index 4a7ba49..1f9c20f 100644
--- a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
+++ b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
@@ -13,7 +13,6 @@
 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.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
@@ -31,7 +30,6 @@
     R8Command.Builder builder = R8Command.builder();
     builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
     builder.setMode(mode);
     builder.addProguardConfiguration(
         ImmutableList.of(
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 c616810..ba65d92 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
@@ -14,13 +14,16 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
 public class Regress72485384Test extends TestBase {
+  @Rule public ExpectedException thrown = ExpectedException.none();
 
   @Parameters(name = "{0}")
   public static Collection<Object[]> getParameters() {
@@ -53,7 +56,13 @@
   @Test
   public void testSignatureRewrite() throws Exception {
     AndroidApp app = compileWithR8(CLASSES, proguardConfig);
+
     if (expectedErrorMessage == null) {
+      if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(ToolHelper.DexVm.Version.V6_0_1)) {
+        // Resolution of java.util.function.Function fails.
+        thrown.expect(AssertionError.class);
+      }
+
       runOnArt(app, Main.class.getCanonicalName());
     } else {
       ToolHelper.ProcessResult result = runOnArtRaw(app, Main.class.getCanonicalName());
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 167262f..dbd4e23 100644
--- a/src/test/java/com/android/tools/r8/resource/DataResourceTest.java
+++ b/src/test/java/com/android/tools/r8/resource/DataResourceTest.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -35,9 +35,10 @@
     ProcessResult referenceResult = ToolHelper.runJava(inputJar, mainClassName);
 
     Path r8Out = temp.getRoot().toPath().resolve("r8out.jar");
-    R8Command.Builder builder = R8Command.builder()
-        .addProgramFiles(inputJar)
-        .setOutput(r8Out, OutputMode.DexIndexed);
+    R8Command.Builder builder =
+        R8Command.builder()
+            .addProgramFiles(inputJar)
+            .setProgramConsumer(new DexIndexedConsumer.ArchiveConsumer(r8Out, true));
     ToolHelper.runR8(builder.build());
 
     ProcessResult r8Result = ToolHelper.runArtRaw(r8Out.toString(), mainClassName);
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
new file mode 100644
index 0000000..6ecdb09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
@@ -0,0 +1,201 @@
+// 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 static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsChecker;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.CharSource;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+import org.junit.Test;
+
+class A {
+  private static String buildClassName(String className) {
+    return A.class.getPackage().getName() + "." + className;
+  }
+
+  public static void main(String[] args) {
+    try {
+      Class bClass = Class.forName(buildClassName("B"));
+      System.out.println("YES");
+    } catch (ClassNotFoundException e) {
+      System.out.println("NO");
+    }
+  }
+}
+
+class B {
+
+}
+
+public class LibraryProvidedProguardRulesTest extends TestBase {
+  private void addTextJarEntry(JarOutputStream out, String name, String content) throws Exception {
+    out.putNextEntry(new ZipEntry(name));
+    ByteStreams.copy(
+        CharSource.wrap(content).asByteSource(StandardCharsets.UTF_8).openBufferedStream(), out);
+    out.closeEntry();
+  }
+
+  private AndroidApp runTest(List<String> rules, DiagnosticsHandler handler) throws Exception {
+    Path jar = temp.newFile("test.jar").toPath();
+    try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar.toFile()))) {
+      addTestClassesToJar(out, A.class, B.class);
+      for (int i =  0; i < rules.size(); i++) {
+        String name = "META-INF/proguard/jar" + (i == 0 ? "" : i) + ".rules";
+        addTextJarEntry(out, name, rules.get(i));
+      }
+    }
+
+    try {
+      R8Command command = (handler != null ? R8Command.builder(handler) : R8Command.builder())
+          .addProgramFiles(jar)
+          .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+          .build();
+      return ToolHelper.runR8(command);
+    } catch (CompilationFailedException e) {
+      assertNotNull(handler);
+      return null;
+    }
+  }
+
+  private AndroidApp runTest(String rules, DiagnosticsHandler handler) throws Exception {
+    return runTest(ImmutableList.of(rules), handler);
+  }
+
+
+  @Test
+  public void keepOnlyA() throws Exception {
+    AndroidApp app = runTest("-keep class " + A.class.getTypeName() +" {}", null);
+    DexInspector inspector = new DexInspector(app);
+    assertThat(inspector.clazz(A.class), isPresent());
+    assertThat(inspector.clazz(B.class), not(isPresent()));
+  }
+
+  @Test
+  public void keepOnlyB() throws Exception {
+    AndroidApp app = runTest("-keep class **B {}", null);
+    DexInspector inspector = new DexInspector(app);
+    assertThat(inspector.clazz(A.class), not(isPresent()));
+    assertThat(inspector.clazz(B.class), isPresent());
+  }
+
+  @Test
+  public void keepBoth() throws Exception {
+    AndroidApp app = runTest("-keep class ** {}", null);
+    DexInspector inspector = new DexInspector(app);
+    assertThat(inspector.clazz(A.class), isPresent());
+    assertThat(inspector.clazz(B.class), isPresent());
+  }
+
+  @Test
+  public void multipleFiles() throws Exception {
+    AndroidApp app = runTest(ImmutableList.of("-keep class **A {}", "-keep class **B {}"), null);
+    DexInspector inspector = new DexInspector(app);
+    assertThat(inspector.clazz(A.class), isPresent());
+    assertThat(inspector.clazz(B.class), isPresent());
+  }
+
+  private void checkOrigin(Origin origin) {
+    assertTrue(origin instanceof ArchiveEntryOrigin);
+    assertEquals(origin.part(), "META-INF/proguard/jar.rules");
+    assertTrue(origin.parent() instanceof PathOrigin);
+  }
+
+  @Test
+  public void syntaxError() throws Exception {
+    DiagnosticsChecker checker = new DiagnosticsChecker();
+    AndroidApp app = runTest("error", checker);
+    assertNull(app);
+    DiagnosticsChecker.checkDiagnostic(
+        checker.errors.get(0), this::checkOrigin, 1, 1, "Expected char '-'");
+  }
+
+  @Test
+  public void includeError() throws Exception {
+    DiagnosticsChecker checker = new DiagnosticsChecker();
+    AndroidApp app = runTest("-include other.rules", checker);
+    assertNull(app);
+    DiagnosticsChecker.checkDiagnostic(checker.errors.get(0), this::checkOrigin, 1, 10,
+        "Options with file names are not supported");
+  }
+
+  class TestProvider implements ProgramResourceProvider, DataResourceProvider {
+
+    @Override
+    public Collection<ProgramResource> getProgramResources() throws ResourceException {
+      byte[] bytes;
+      try {
+        bytes = ByteStreams.toByteArray(A.class.getResourceAsStream("A.class"));
+      } catch (IOException e) {
+        throw new ResourceException(Origin.unknown(), "Unexpected");
+      }
+      return ImmutableList.of(
+          ProgramResource.fromBytes(Origin.unknown(), Kind.CF, bytes,
+              Collections.singleton(DescriptorUtils.javaTypeToDescriptor(A.class.getTypeName()))));
+    }
+
+    @Override
+    public DataResourceProvider getDataResourceProvider() {
+      return this;
+    }
+
+    @Override
+    public void accept(Visitor visitor) throws ResourceException {
+      throw new ResourceException(Origin.unknown(), "Cannot provide data resources after all");
+    }
+  }
+
+  @Test
+  public void throwingDataResourceProvider() throws Exception {
+    DiagnosticsChecker checker = new DiagnosticsChecker();
+    try {
+      R8Command command = R8Command.builder(checker)
+          .addProgramResourceProvider(new TestProvider())
+          .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+          .build();
+      fail("Should not succeed");
+    } catch (CompilationFailedException e) {
+      DiagnosticsChecker.checkDiagnostic(
+          checker.errors.get(0),
+          origin -> assertSame(origin, Origin.unknown()),
+          "Cannot provide data resources after all");
+    }
+  }
+}
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 b7277cb..1012421 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -1718,6 +1718,7 @@
     assertEquals(0, handler.errors.size());
   }
 
+  // TODO(sgjesse): Change to use DiagnosticsChecker.
   private Diagnostic checkDiagnostic(List<Diagnostic> diagnostics, Path path, int lineStart,
       int columnStart, String... messageParts) {
     assertEquals(1, diagnostics.size());
diff --git a/tools/archive.py b/tools/archive.py
index 815e416..07f138a 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -3,22 +3,25 @@
 # 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.
 
-import gradle
 import create_maven_release
-import d8
+import gradle
 import os
-import r8
+import shutil
 import subprocess
 import sys
+import toolhelper
 import utils
-import shutil
 import zipfile
 
 ARCHIVE_BUCKET = 'r8-releases'
 
+def GetToolVersion(jar_path):
+  output = subprocess.check_output(['java', '-jar', jar_path, '--version'])
+  return output.splitlines()[0].strip()
+
 def GetVersion():
-  r8_version = r8.run(['--version'], build = False).splitlines()[0].strip()
-  d8_version = d8.run(['--version'], build = False).splitlines()[0].strip()
+  r8_version = GetToolVersion(utils.R8_JAR)
+  d8_version = GetToolVersion(utils.D8_JAR)
   # The version printed is "D8 vVERSION_NUMBER" and "R8 vVERSION_NUMBER"
   # Sanity check that versions match.
   if d8_version.split()[1] != r8_version.split()[1]:
@@ -49,21 +52,30 @@
                       'in a commit that have -dev in version')
   return True
 
-def GetStorageDestination(storage_prefix, version, file_name, is_master):
+def GetStorageDestination(storage_prefix,
+                          version_or_path,
+                          file_name,
+                          is_master):
   # We archive master commits under raw/master instead of directly under raw
-  version_dir = GetVersionDestination(storage_prefix, version, is_master)
+  version_dir = GetVersionDestination(storage_prefix,
+                                      version_or_path,
+                                      is_master)
   return '%s/%s' % (version_dir, file_name)
 
-def GetVersionDestination(storage_prefix, version, is_master):
+def GetVersionDestination(storage_prefix, version_or_path, is_master):
   archive_dir = 'raw/master' if is_master else 'raw'
-  return '%s%s/%s/%s' % (storage_prefix, ARCHIVE_BUCKET, archive_dir, version)
+  return '%s%s/%s/%s' % (storage_prefix, ARCHIVE_BUCKET,
+                         archive_dir, version_or_path)
 
-def GetUploadDestination(version, file_name, is_master):
-  return GetStorageDestination('gs://', version, file_name, is_master)
+def GetUploadDestination(version_or_path, file_name, is_master):
+  return GetStorageDestination('gs://', version_or_path, file_name, is_master)
 
-def GetUrl(version, file_name, is_master):
+def GetUrl(version_or_path, file_name, is_master):
   return GetStorageDestination('http://storage.googleapis.com/',
-                               version, file_name, is_master)
+                               version_or_path, file_name, is_master)
+
+def GetMavenUrl(is_master):
+  return GetVersionDestination('http://storage.googleapis.com/', '', is_master)
 
 def Main():
   if not 'BUILDBOT_BUILDERNAME' in os.environ:
@@ -113,14 +125,13 @@
       print('Uploading %s to %s' % (tagged_jar, destination))
       utils.upload_file_to_cloud_storage(tagged_jar, destination)
       print('File available at: %s' % GetUrl(version, file_name, is_master))
-    # Upload extracted maven directory for easy testing in studio.
-    zip_ref = zipfile.ZipFile(utils.MAVEN_ZIP, 'r')
-    zip_ref.extractall(temp)
-    zip_ref.close()
-    utils.upload_dir_to_cloud_storage(
-        os.path.join(temp, 'com'),
-        GetUploadDestination(version, 'com', is_master))
-    print('Maven repo root available at: %s' % GetUrl(version, '', is_master))
+      if file == utils.R8_JAR:
+        # Upload R8 to a maven compatible location.
+        maven_dst = GetUploadDestination(utils.get_maven_path(version),
+                                         'r8-%s.jar' % version, is_master)
+        utils.upload_file_to_cloud_storage(tagged_jar, maven_dst)
+        print('Maven repo root available at: %s' % GetMavenUrl(is_master))
+
 
 if __name__ == '__main__':
   sys.exit(Main())
diff --git a/tools/bisect.py b/tools/bisect.py
index c8d1501..74c880e 100755
--- a/tools/bisect.py
+++ b/tools/bisect.py
@@ -3,33 +3,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.
 
-import gradle
-import os
-import subprocess
 import sys
-import utils
-
-JAR = os.path.join(utils.REPO_ROOT, 'build', 'libs', 'bisect.jar')
-
-def run(args, build, debug):
-  if build:
-    gradle.RunGradle(['bisect'])
-  cmd = ['java']
-  if debug:
-    cmd.append('-ea')
-  cmd.extend(['-jar', JAR])
-  cmd.extend(args)
-  subprocess.check_call(cmd)
-
-def main():
-  build = True
-  args = []
-  for arg in sys.argv[1:]:
-    if arg in ("--build", "--no-build"):
-      build = arg == "--build"
-    else:
-      args.append(arg)
-  run(args, build, True)
+import toolhelper
 
 if __name__ == '__main__':
-  sys.exit(main())
+  sys.exit(toolhelper.run('bisect', sys.argv[1:]))
diff --git a/tools/compatdx.py b/tools/compatdx.py
index 25a59db..c4cb320 100755
--- a/tools/compatdx.py
+++ b/tools/compatdx.py
@@ -3,42 +3,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.
 
-import gradle
-import os
-import subprocess
 import sys
-import utils
-
-def run(args, build = True, debug = True, profile = False, track_memory_file=None):
-  if build:
-    gradle.RunGradle(['CompatDX'])
-  cmd = []
-  if track_memory_file:
-    cmd.extend(['tools/track_memory.sh', track_memory_file])
-  cmd.append('java')
-  if debug:
-    cmd.append('-ea')
-  if profile:
-    cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
-  cmd.extend(['-jar', utils.COMPATDX_JAR])
-  cmd.extend(args)
-  subprocess.check_call(cmd)
-
-def main():
-  build = True
-  args = []
-  for arg in sys.argv[1:]:
-    if arg in ("--build", "--no-build"):
-      build = arg == "--build"
-    else:
-      args.append(arg)
-  try:
-    run(args, build)
-  except subprocess.CalledProcessError as e:
-    # In case anything relevant was printed to stdout, normally this is already
-    # on stderr.
-    print(e.output)
-    return e.returncode
+import toolhelper
 
 if __name__ == '__main__':
-  sys.exit(main())
+  sys.exit(toolhelper.run('compatdx', sys.argv[1:]))
diff --git a/tools/compatproguard.py b/tools/compatproguard.py
new file mode 100755
index 0000000..10542e3
--- /dev/null
+++ b/tools/compatproguard.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+  sys.exit(toolhelper.run('compatproguard', sys.argv[1:]))
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index 0a7f122..662d53e 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -210,7 +210,7 @@
   # Create directory structure for this version.
   version = determine_version()
   with utils.TempDir() as tmp_dir:
-    version_dir = join(tmp_dir, 'com', 'android', 'tools', 'r8', version)
+    version_dir = join(tmp_dir, utils.get_maven_path(version))
     makedirs(version_dir)
     # Write the pom file.
     pom_file = join(version_dir, 'r8-' + version + '.pom')
diff --git a/tools/d8.py b/tools/d8.py
index bf97845..18a4a67 100755
--- a/tools/d8.py
+++ b/tools/d8.py
@@ -3,46 +3,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.
 
-import gradle
-import os
-import subprocess
 import sys
-import utils
-
-def run(args, build=True, debug=True, profile=False,
-        track_memory_file=None):
-  if build:
-    gradle.RunGradle(['D8'])
-  cmd = []
-  if track_memory_file:
-    cmd.extend(['tools/track_memory.sh', track_memory_file])
-  cmd.append('java')
-  if debug:
-    cmd.append('-ea')
-  if profile:
-    cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
-  cmd.extend(['-jar', utils.D8_JAR])
-  cmd.extend(args)
-  utils.PrintCmd(cmd)
-  result = subprocess.check_output(cmd)
-  print(result)
-  return result
-
-def main():
-  build = True
-  args = []
-  for arg in sys.argv[1:]:
-    if arg in ("--build", "--no-build"):
-      build = arg == "--build"
-    else:
-      args.append(arg)
-  try:
-    run(args, build)
-  except subprocess.CalledProcessError as e:
-    # In case anything relevant was printed to stdout, normally this is already
-    # on stderr.
-    print(e.output)
-    return e.returncode
+import toolhelper
 
 if __name__ == '__main__':
-  sys.exit(main())
+  sys.exit(toolhelper.run('d8', sys.argv[1:]))
diff --git a/tools/d8logger.py b/tools/d8logger.py
new file mode 100755
index 0000000..9fb3508
--- /dev/null
+++ b/tools/d8logger.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+  sys.exit(toolhelper.run('d8logger', sys.argv[1:]))
diff --git a/tools/dexfilemerger.py b/tools/dexfilemerger.py
new file mode 100755
index 0000000..7bdfc22
--- /dev/null
+++ b/tools/dexfilemerger.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+  sys.exit(toolhelper.run('dexfilemerger', sys.argv[1:]))
diff --git a/tools/dexsegments.py b/tools/dexsegments.py
new file mode 100755
index 0000000..f984063
--- /dev/null
+++ b/tools/dexsegments.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+  sys.exit(toolhelper.run('dexsegments', sys.argv[1:]))
diff --git a/tools/dexsplitter.py b/tools/dexsplitter.py
new file mode 100755
index 0000000..415b149
--- /dev/null
+++ b/tools/dexsplitter.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+  sys.exit(toolhelper.run('dexsplitter', sys.argv[1:]))
diff --git a/tools/disasm.py b/tools/disasm.py
new file mode 100755
index 0000000..0d2599a
--- /dev/null
+++ b/tools/disasm.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+  sys.exit(toolhelper.run('disasm', sys.argv[1:]))
diff --git a/tools/extractmarker.py b/tools/extractmarker.py
new file mode 100755
index 0000000..36a9c88
--- /dev/null
+++ b/tools/extractmarker.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+  sys.exit(toolhelper.run('extractmarker', sys.argv[1:]))
diff --git a/tools/golem_build.py b/tools/golem_build.py
new file mode 100755
index 0000000..6d3a9b7
--- /dev/null
+++ b/tools/golem_build.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+# 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.
+
+# Utility script to make it easier to update what golem builds.
+
+import gradle
+import sys
+
+GRADLE_ARGS = ['--no-daemon']
+BUILD_TARGETS = ['R8', 'D8', 'buildExampleJars', 'CompatDx',
+                 'downloadAndroidCts', 'downloadDx']
+
+def Main():
+  gradle.RunGradle(GRADLE_ARGS + BUILD_TARGETS)
+
+if __name__ == '__main__':
+  sys.exit(Main())
diff --git a/tools/jardiff.py b/tools/jardiff.py
new file mode 100755
index 0000000..894131c
--- /dev/null
+++ b/tools/jardiff.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+  sys.exit(toolhelper.run('jardiff', sys.argv[1:]))
diff --git a/tools/maindex.py b/tools/maindex.py
new file mode 100755
index 0000000..ff5329a
--- /dev/null
+++ b/tools/maindex.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+# 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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+  sys.exit(toolhelper.run('maindex', sys.argv[1:]))
diff --git a/tools/minify_tool.py b/tools/minify_tool.py
new file mode 100755
index 0000000..08b2ada
--- /dev/null
+++ b/tools/minify_tool.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python
+# 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.
+
+'''
+Run R8 (with the class-file backend) to optimize a command-line program.
+
+Given an input JAR (default: r8.jar) and a main-class, generates a new input JAR
+with the given main-class in the manifest along with a -keep rule for keeping
+just the main entrypoint, and runs R8 in release+classfile mode on the JAR.
+'''
+
+import argparse
+import os
+import re
+import sys
+import time
+import toolhelper
+import utils
+import zipfile
+
+KEEP = '-keep public class %s { public static void main(...); }\n'
+MANIFEST_PATH = 'META-INF/MANIFEST.MF'
+MANIFEST = 'Manifest-Version: 1.0\nMain-Class: %s\n\n'
+MANIFEST_PATTERN = r'Main-Class:\s*(\S+)'
+RT = os.path.join(utils.REPO_ROOT, 'third_party/openjdk/openjdk-rt-1.8/rt.jar')
+
+parser = argparse.ArgumentParser(description=__doc__.strip(),
+                                 formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument(
+    '-i', '--input-jar', default=utils.R8_JAR,
+    help='Input JAR to use (default: build/libs/r8.jar)')
+parser.add_argument(
+    '-o', '--output-jar',
+    help='Path to output JAR (default: build/libs/<MainClass>-min.jar)')
+parser.add_argument(
+    '-l', '--lib', default=RT,
+    help='Path to rt.jar to use instead of OpenJDK 1.8')
+parser.add_argument(
+    '-m', '--mainclass',
+    help='Create/overwrite MANIFEST.MF with the given Main-Class')
+parser.add_argument(
+    '-O', '--no-debug', dest='debug', action='store_false',
+    help='Disable assertions when running R8')
+parser.add_argument(
+    '--print-runtimeraw', metavar='BENCHMARKNAME',
+    help='Print "<BENCHMARKNAME>(RunTimeRaw): <elapsed> ms" at the end')
+
+def generate_output_name(input_jar, mainclass):
+  if not mainclass:
+    input_base, input_ext = os.path.splitext(input_jar)
+    return '%s-min%s' % (input_base, input_ext)
+  base = mainclass[mainclass.rindex('.')+1:] if '.' in mainclass else mainclass
+  return os.path.join(utils.LIBS, '%s-min.jar' % base)
+
+def repackage(input_jar, output_jar, mainclass):
+  print("Repackaging %s to %s with Main-Class: %s..." %
+        (input_jar, output_jar, mainclass))
+  manifest = MANIFEST % mainclass
+  with zipfile.ZipFile(input_jar, 'r') as input_zf:
+    with zipfile.ZipFile(output_jar, 'w') as output_zf:
+      for zipinfo in input_zf.infolist():
+        if zipinfo.filename.upper() == MANIFEST_PATH:
+          assert manifest is not None
+          output_zf.writestr(MANIFEST_PATH, manifest)
+          manifest = None
+        else:
+          output_zf.writestr(zipinfo, input_zf.read(zipinfo))
+      if manifest is not None:
+        output_zf.writestr(MANIFEST_PATH, manifest)
+
+def extract_mainclass(input_jar):
+  with zipfile.ZipFile(input_jar, 'r') as input_zf:
+    try:
+      manifest = input_zf.getinfo(MANIFEST_PATH)
+    except KeyError:
+      raise SystemExit('No --mainclass specified and no manifest in input JAR.')
+    mo = re.search(MANIFEST_PATTERN, input_zf.read(manifest))
+    if not mo:
+      raise SystemExit(
+          'No --mainclass specified and no Main-Class in input JAR manifest.')
+    return mo.group(1)
+
+def minify_tool(mainclass=None, input_jar=utils.R8_JAR, output_jar=None, lib=RT,
+                debug=True, build=True, print_runtimeraw=None):
+  if output_jar is None:
+    output_jar = generate_output_name(input_jar, mainclass)
+  with utils.TempDir() as path:
+    if mainclass:
+      tmp_input_path = os.path.join(path, 'input.jar')
+      repackage(input_jar, tmp_input_path, mainclass)
+    else:
+      tmp_input_path = input_jar
+      mainclass = extract_mainclass(input_jar)
+    keep_path = os.path.join(path, 'keep.txt')
+    with open(keep_path, 'w') as fp:
+      fp.write(KEEP % mainclass)
+    args = ('--lib', lib,
+            '--classfile',
+            '--output', output_jar,
+            '--pg-conf', keep_path,
+            '--release',
+            tmp_input_path)
+    start_time = time.time()
+    return_code = toolhelper.run('r8', args, debug=debug, build=build)
+    if print_runtimeraw:
+      elapsed_ms = 1000 * (time.time() - start_time)
+      print('%s-Total(RunTimeRaw): %s ms' % (print_runtimeraw, elapsed_ms))
+    return return_code
+
+if __name__ == '__main__':
+  sys.exit(minify_tool(**vars(parser.parse_args())))
diff --git a/tools/r8.py b/tools/r8.py
index 3ae4da7..60c60a0 100755
--- a/tools/r8.py
+++ b/tools/r8.py
@@ -3,46 +3,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.
 
-import gradle
-import os
-import subprocess
 import sys
-import utils
-
-def run(args, build=True, debug=True, profile=False,
-        track_memory_file=None):
-  if build:
-    gradle.RunGradle(['r8'])
-  cmd = []
-  if track_memory_file:
-    cmd.extend(['tools/track_memory.sh', track_memory_file])
-  cmd.append('java')
-  if debug:
-    cmd.append('-ea')
-  if profile:
-    cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
-  cmd.extend(['-jar', utils.R8_JAR])
-  cmd.extend(args)
-  utils.PrintCmd(cmd)
-  result = subprocess.check_output(cmd)
-  print(result)
-  return result
-
-def main():
-  build = True
-  args = []
-  for arg in sys.argv[1:]:
-    if arg in ("--build", "--no-build"):
-      build = arg == "--build"
-    else:
-      args.append(arg)
-  try:
-    run(args, build)
-  except subprocess.CalledProcessError as e:
-    # In case anything relevant was printed to stdout, normally this is already
-    # on stderr.
-    print(e.output)
-    return e.returncode
+import toolhelper
 
 if __name__ == '__main__':
-  sys.exit(main())
+  sys.exit(toolhelper.run('r8', sys.argv[1:]))
diff --git a/tools/run-d8-on-gmscore.py b/tools/run-d8-on-gmscore.py
index 63865d0..b1bdc6e 100755
--- a/tools/run-d8-on-gmscore.py
+++ b/tools/run-d8-on-gmscore.py
@@ -3,11 +3,11 @@
 # 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.
 
-import d8
 import gmscore_data
 import optparse
 import os
 import sys
+import toolhelper
 
 def ParseOptions():
   result = optparse.OptionParser()
@@ -70,8 +70,13 @@
     with open(options.dump_args_file, 'w') as args_file:
       args_file.writelines([arg + os.linesep for arg in args])
   else:
-    d8.run(args, not options.no_build, not options.no_debug, options.profile,
-           options.track_memory_to_file)
+    toolhelper.run(
+        'd8',
+        args,
+        build=not options.no_build,
+        debug=not options.no_debug,
+        profile=options.profile,
+        track_memory_to_file=options.track_memory_to_file)
 
 if __name__ == '__main__':
   sys.exit(main())
diff --git a/tools/run_bootstrap_benchmark.py b/tools/run_bootstrap_benchmark.py
new file mode 100755
index 0000000..0c608f9
--- /dev/null
+++ b/tools/run_bootstrap_benchmark.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+# 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.
+
+import argparse
+import minify_tool
+import os
+import sys
+import utils
+
+
+PINNED_R8_JAR = os.path.join(utils.REPO_ROOT, 'third_party/r8/r8.jar')
+
+parser = argparse.ArgumentParser()
+parser.add_argument(
+    '--print-runtimeraw', metavar='BENCHMARKNAME',
+    help='Print "<BENCHMARKNAME>(RunTimeRaw): <elapsed> ms" at the end')
+
+
+if __name__ == '__main__':
+  sys.exit(minify_tool.minify_tool(input_jar=PINNED_R8_JAR, debug=False,
+                                   build=False, **vars(parser.parse_args())))
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index b9bcc60..571d96d 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -10,10 +10,9 @@
 import sys
 import time
 
-import d8
 import gmail_data
 import gmscore_data
-import r8
+import toolhelper
 import utils
 import youtube_data
 
@@ -191,22 +190,20 @@
       if options.print_memoryuse and not options.track_memory_to_file:
         options.track_memory_to_file = os.path.join(temp,
             utils.MEMORY_USE_TMP_FILE)
-      if options.compiler == 'd8':
-        d8.run(args, not options.no_build, not options.no_debug,
-            options.profile, options.track_memory_to_file)
-      else:
-        if app_provided_pg_conf:
-          # Ensure that output of -printmapping and -printseeds go to the output
-          # location and not where the app Proguard configuration places them.
-          if outdir.endswith('.zip') or outdir.endswith('.jar'):
-            pg_outdir = os.path.dirname(outdir)
-          else:
-            pg_outdir = outdir
-          additional_pg_conf = GenerateAdditionalProguardConfiguration(
-              temp, os.path.abspath(pg_outdir))
-          args.extend(['--pg-conf', additional_pg_conf])
-        r8.run(args, not options.no_build, not options.no_debug,
-            options.profile, options.track_memory_to_file)
+      if options.compiler == 'r8' and app_provided_pg_conf:
+        # Ensure that output of -printmapping and -printseeds go to the output
+        # location and not where the app Proguard configuration places them.
+        if outdir.endswith('.zip') or outdir.endswith('.jar'):
+          pg_outdir = os.path.dirname(outdir)
+        else:
+          pg_outdir = outdir
+        additional_pg_conf = GenerateAdditionalProguardConfiguration(
+            temp, os.path.abspath(pg_outdir))
+        args.extend(['--pg-conf', additional_pg_conf])
+      toolhelper.run(options.compiler, args, build=not options.no_build,
+                     debug=not options.no_debug,
+                     profile=options.profile,
+                     track_memory_to_file=options.track_memory_to_file)
       if options.print_memoryuse:
         print('{}(MemoryUse): {}'
             .format(options.print_memoryuse,
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
new file mode 100644
index 0000000..a7f509c
--- /dev/null
+++ b/tools/toolhelper.py
@@ -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.
+
+import gradle
+import os
+import subprocess
+import sys
+import utils
+
+def run(tool, args, build=None, debug=True,
+        profile=False, track_memory_file=None):
+  if build is None:
+    build, args = extract_build_from_args(args)
+  if build:
+    gradle.RunGradle(['r8'])
+  cmd = []
+  if track_memory_file:
+    cmd.extend(['tools/track_memory.sh', track_memory_file])
+  cmd.append('java')
+  if debug:
+    cmd.append('-ea')
+  if profile:
+    cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
+  cmd.extend(['-jar', utils.R8_JAR, tool])
+  cmd.extend(args)
+  utils.PrintCmd(cmd)
+  return subprocess.call(cmd)
+
+def extract_build_from_args(input_args):
+  build = True
+  args = []
+  for arg in input_args:
+    if arg in ("--build", "--no-build"):
+      build = arg == "--build"
+    else:
+      args.append(arg)
+  return build, args
diff --git a/tools/utils.py b/tools/utils.py
index a138461..9f6959f 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -15,8 +15,6 @@
 TOOLS_DIR = os.path.abspath(os.path.normpath(os.path.join(__file__, '..')))
 REPO_ROOT = os.path.realpath(os.path.join(TOOLS_DIR, '..'))
 MEMORY_USE_TMP_FILE = 'memory_use.tmp'
-DEX_SEGMENTS_JAR = os.path.join(REPO_ROOT, 'build', 'libs',
-    'dexsegments.jar')
 DEX_SEGMENTS_RESULT_PATTERN = re.compile('- ([^:]+): ([0-9]+)')
 BUILD = os.path.join(REPO_ROOT, 'build')
 LIBS = os.path.join(BUILD, 'libs')
@@ -189,7 +187,7 @@
 # Return a dictionary: {segment_name -> segments_size}
 def getDexSegmentSizes(dex_files):
   assert len(dex_files) > 0
-  cmd = ['java', '-jar', DEX_SEGMENTS_JAR]
+  cmd = ['java', '-jar', R8_JAR, 'dexsegments']
   cmd.extend(dex_files)
   PrintCmd(cmd)
   output = subprocess.check_output(cmd)
@@ -207,6 +205,9 @@
 
   return result
 
+def get_maven_path(version):
+  return os.path.join('com', 'android', 'tools', 'r8', version)
+
 def print_dexsegments(prefix, dex_files):
   for segment_name, size in getDexSegmentSizes(dex_files).items():
     print('{}-{}(CodeSize): {}'