Merge "Add more ifz tests using Kotlin !! and ?. operators."
diff --git a/build.gradle b/build.gradle
index 759e475..33605a1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -63,7 +63,6 @@
         }
     }
     dependencies {
-        classpath 'com.cookpad.android.licensetools:license-tools-plugin:0.23.0'
         classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.4'
         classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
         classpath "net.ltgt.gradle:gradle-apt-plugin:0.12"
@@ -80,7 +79,6 @@
 
 apply plugin: 'java'
 apply plugin: 'idea'
-apply plugin: 'com.cookpad.android.licensetools'
 apply plugin: 'net.ltgt.errorprone-base'
 apply plugin: "net.ltgt.apt"
 
@@ -210,24 +208,24 @@
 }
 
 dependencies {
-    compile "net.sf.jopt-simple:jopt-simple:$joptSimpleVersion"
-    compile "com.google.code.gson:gson:$gsonVersion"
+    implementation "net.sf.jopt-simple:jopt-simple:$joptSimpleVersion"
+    implementation "com.google.code.gson:gson:$gsonVersion"
     // Include all of guava when compiling the code, but exclude annotations that we don't
     // need from the packaging.
     compileOnly("com.google.guava:guava:$guavaVersion")
-    compile("com.google.guava:guava:$guavaVersion", {
+    implementation("com.google.guava:guava:$guavaVersion", {
         exclude group: 'com.google.errorprone'
         exclude group: 'com.google.code.findbugs'
         exclude group: 'com.google.j2objc'
         exclude group: 'org.codehaus.mojo'
     })
-    compile group: 'it.unimi.dsi', name: 'fastutil', version: fastutilVersion
-    compile "org.jetbrains.kotlinx:kotlinx-metadata-jvm:$kotlinExtMetadataJVMVersion"
-    compile group: 'org.ow2.asm', name: 'asm', version: asmVersion
-    compile group: 'org.ow2.asm', name: 'asm-commons', version: asmVersion
-    compile group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
-    compile group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
-    compile group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
+    implementation group: 'it.unimi.dsi', name: 'fastutil', version: fastutilVersion
+    implementation "org.jetbrains.kotlinx:kotlinx-metadata-jvm:$kotlinExtMetadataJVMVersion"
+    implementation group: 'org.ow2.asm', name: 'asm', version: asmVersion
+    implementation group: 'org.ow2.asm', name: 'asm-commons', version: asmVersion
+    implementation group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
+    implementation group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
+    implementation group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
     testCompile sourceSets.examples.output
     testCompile "junit:junit:$junitVersion"
     testCompile group: 'org.smali', name: 'smali', version: smaliVersion
@@ -253,10 +251,6 @@
     apt "com.google.auto.value:auto-value:$autoValueVersion"
 }
 
-licenseTools {
-    licensesYaml = file('LIBRARY-LICENSE')
-}
-
 def osString = OperatingSystem.current().isLinux() ? "linux" :
         OperatingSystem.current().isMacOsX() ? "mac" : "windows"
 
@@ -458,13 +452,27 @@
 }
 
 task consolidatedLicense {
-    // checkLicenses verifies that the list of libraries referenced in 'LIBRARY-LICENSE' is
-    // corresponding to the effective list of embedded libraries.
-    dependsOn 'checkLicenses'
     def license = new File(new File(buildDir, 'generatedLicense'), 'LICENSE')
+
     inputs.files files('LICENSE', 'LIBRARY-LICENSE') + fileTree(dir: 'library-licensing')
+    def runtimeClasspath = configurations.findByName("runtimeClasspath")
+    inputs.files { runtimeClasspath.getResolvedConfiguration().files }
+
     outputs.files license
+
     doLast {
+        def dependencies = []
+        runtimeClasspath.resolvedConfiguration.resolvedArtifacts.each {
+            def identifier = (ModuleComponentIdentifier) it.id.componentIdentifier
+            dependencies.add("${identifier.group}:${identifier.module}")
+        }
+        def libraryLicenses = file('LIBRARY-LICENSE').text
+        dependencies.each {
+            if (!libraryLicenses.contains("- artifact: $it")) {
+                 throw new GradleException("No license for $it in LIBRARY_LICENSE")
+            }
+        }
+
         license.getParentFile().mkdirs()
         license.createNewFile()
         license.text = "This file lists all licenses for code distributed.\n"
@@ -476,7 +484,7 @@
         license.text += "\n"
         license.text += "Summary of distributed libraries:\n"
         license.text += "\n"
-        license.text += file('LIBRARY-LICENSE').text
+        license.text += libraryLicenses
         license.text += "\n"
         license.text += "\n"
         license.text += "Licenses details:\n"
@@ -511,7 +519,7 @@
 }
 
 task repackageDeps(type: ShadowJar) {
-    configurations = [project.configurations.compile]
+    configurations = [project.configurations.runtimeClasspath]
     mergeServiceFiles(it)
     if (!project.hasProperty('lib_no_relocate')) {
         configureRelocations(it)
@@ -522,7 +530,7 @@
 }
 
 task repackageDepsForLib(type: ShadowJar) {
-    configurations = [project.configurations.compile]
+    configurations = [project.configurations.runtimeClasspath]
     mergeServiceFiles(it)
     configureRelocations(it)
     exclude { it.getRelativePath().getPathString() == "module-info.class" }
@@ -569,6 +577,21 @@
     }
 }
 
+task R8NoManifest(type: ShadowJar) {
+    from consolidatedLicense.outputs.files
+    baseName 'r8nomanifest'
+    classifier = null
+    version = null
+    // In order to build without dependencies, pass the exclude_deps property using:
+    // gradle -Pexclude_deps R8
+    if (!project.hasProperty('exclude_deps')) {
+        from repackageSources.outputs.files
+        from repackageDeps.outputs.files
+    } else {
+        from sourceSets.main.output
+    }
+}
+
 task D8(type: ShadowJar) {
     from R8.outputs.files
     baseName 'd8'
@@ -599,16 +622,17 @@
         input,
         "--lib", "third_party/openjdk/openjdk-rt-1.8/rt.jar",
         "--output", output,
-        "--pg-conf", pgconf]
+        "--pg-conf", pgconf,
+        "--pg-map-output", output + ".map"]
 }
 
 task R8Lib(type: Exec) {
     def pgconf = "src/main/keep.txt"
     def output = "build/libs/r8lib.jar"
-    inputs.files files(pgconf, R8.outputs)
+    inputs.files files(pgconf, R8.outputs, R8NoManifest.outputs)
     outputs.file output
     dependsOn downloadOpenJDKrt
-    commandLine r8CfCommandLine(R8.outputs.files[0], output, pgconf)
+    commandLine r8CfCommandLine(R8NoManifest.outputs.files[0], output, pgconf)
     workingDir = projectDir
 }
 
@@ -618,7 +642,7 @@
     inputs.files files(pgconf, CompatDx.outputs, R8.outputs)
     outputs.file output
     dependsOn downloadOpenJDKrt
-    commandLine r8CfCommandLine(R8.outputs.files[0], output, pgconf)
+    commandLine r8CfCommandLine(CompatDx.outputs.files[0], output, pgconf)
     workingDir = projectDir
 }
 
@@ -628,7 +652,7 @@
     inputs.files files(pgconf, CompatProguard.outputs, R8.outputs)
     outputs.file output
     dependsOn downloadOpenJDKrt
-    commandLine r8CfCommandLine(R8.outputs.files[0], output, pgconf)
+    commandLine r8CfCommandLine(CompatProguard.outputs.files[0], output, pgconf)
     workingDir = projectDir
 }
 
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 40d31d2..be3c93b 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.IRCode.LiveAtEntrySets;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.Move;
@@ -222,6 +223,15 @@
     // and we do not actually want locals information in the output.
     if (options.debug) {
       computeDebugInfo(blocks);
+    } else if (code.method.getOptimizationInfo().isReachabilitySensitive()) {
+      InstructionIterator it = code.instructionIterator();
+      while (it.hasNext()) {
+        Instruction instruction = it.next();
+        if (instruction.isDebugLocalRead()) {
+          instruction.clearDebugValues();
+          it.remove();
+        }
+      }
     }
     clearUserInfo();
     clearState();
@@ -372,11 +382,11 @@
           }
           // Remove the end markers now that local liveness is computed.
           instruction.clearDebugValues();
-          if (instruction.isDebugLocalRead()) {
-            Instruction prev = instructionIterator.previous();
-            assert prev == instruction;
-            instructionIterator.remove();
-          }
+        }
+        if (instruction.isDebugLocalRead()) {
+          Instruction prev = instructionIterator.previous();
+          assert prev == instruction;
+          instructionIterator.remove();
         }
 
         Instruction nextInstruction = instructionIterator.peekNext();
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index f95e666..9c9a977 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.debug.CfDebugTestConfig;
 import com.android.tools.r8.debug.DebugTestConfig;
@@ -124,20 +125,7 @@
 
   @Override
   public JvmTestBuilder addProgramClasses(Collection<Class<?>> classes) {
-    // Adding a collection of classes will build a jar of exactly those classes so that no other
-    // classes are made available via a too broad classpath directory.
-    List<ProgramResource> resources = ListUtils.map(classes, ClassFileResource::new);
-    AndroidApp build = AndroidApp.builder()
-        .addProgramResourceProvider(new ClassFileResourceProvider(resources)).build();
-    Path out;
-    try {
-      out = getState().getNewTempFolder().resolve("out.zip");
-      build.writeToZip(out, OutputMode.ClassFile);
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
-    classpath.add(out);
-    builder.addProgramFiles(out);
+    addProgramResources(ListUtils.map(classes, ClassFileResource::new));
     return self();
   }
 
@@ -149,8 +137,14 @@
 
   @Override
   public JvmTestBuilder addProgramClassFileData(Collection<byte[]> files) {
-    throw new Unimplemented(
-        "No support for adding classfile data directly (we need to compute the descriptor)");
+    addProgramResources(
+        ListUtils.map(files, data ->
+            ProgramResource.fromBytes(
+                Origin.unknown(),
+                Kind.CF,
+                data,
+                Collections.singleton(TestBase.extractClassDescriptor(data)))));
+    return self();
   }
 
   public JvmTestBuilder addClasspath(Path... paths) {
@@ -168,4 +162,22 @@
   public JvmTestBuilder addTestClasspath() {
     return addClasspath(ToolHelper.getClassPathForTests());
   }
+
+  // Adding a collection of resources will build a jar of exactly those classes so that no other
+  // classes are made available via a too broad classpath directory.
+  private void addProgramResources(List<ProgramResource> resources) {
+    AndroidApp build =
+        AndroidApp.builder()
+            .addProgramResourceProvider(new ClassFileResourceProvider(resources))
+            .build();
+    Path out;
+    try {
+      out = getState().getNewTempFolder().resolve("out.zip");
+      build.writeToZip(out, OutputMode.ClassFile);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+    classpath.add(out);
+    builder.addProgramFiles(out);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index fa4c41c..109b42d 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -158,6 +158,18 @@
   }
 
   @Override
+  public ProguardTestBuilder noTreeShaking() {
+    addKeepRules("-dontshrink");
+    return self();
+  }
+
+  @Override
+  public ProguardTestBuilder noMinification() {
+    addKeepRules("-dontobfuscate");
+    return self();
+  }
+
+  @Override
   public ProguardTestBuilder addProgramClasses(Collection<Class<?>> classes) {
     // Adding a collection of classes will build a jar of exactly those classes so that no other
     // classes are made available via a too broad classpath directory.
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 4404bae..07eea98 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -55,6 +55,18 @@
     return new R8TestCompileResult(getState(), backend, app.get(), proguardMapBuilder.toString());
   }
 
+  @Override
+  public R8TestBuilder noTreeShaking() {
+    builder.setDisableTreeShaking(true);
+    return self();
+  }
+
+  @Override
+  public R8TestBuilder noMinification() {
+    builder.setDisableMinification(true);
+    return self();
+  }
+
   public R8TestBuilder addDataResources(List<DataEntryResource> resources) {
     resources.forEach(builder.getAppBuilder()::addDataResource);
     return self();
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index a1a4f9d..2fd4034 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -797,7 +797,15 @@
     }
   }
 
-  public String extractClassName(byte[] ccc) {
+  public static String extractClassName(byte[] ccc) {
+    return DescriptorUtils.descriptorToJavaType(extractClassDescriptor(ccc));
+  }
+
+  public static String extractClassDescriptor(byte[] ccc) {
+    return "L" + extractClassInternalType(ccc) + ";";
+  }
+
+  private static String extractClassInternalType(byte[] ccc) {
     class ClassNameExtractor extends ClassVisitor {
       private String className;
 
@@ -813,13 +821,10 @@
           String signature,
           String superName,
           String[] interfaces) {
-        className =
-            name.replace(
-                DescriptorUtils.DESCRIPTOR_PACKAGE_SEPARATOR,
-                DescriptorUtils.JAVA_PACKAGE_SEPARATOR);
+        className = name;
       }
 
-      String getClassName() {
+      String getClassInternalType() {
         return className;
       }
     }
@@ -828,7 +833,7 @@
     ClassNameExtractor extractor = new ClassNameExtractor();
     reader.accept(
         extractor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
-    return extractor.getClassName();
+    return extractor.getClassInternalType();
   }
 
   protected Path writeToJar(List<byte[]> classes) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 2704039..46ae905 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -21,6 +21,10 @@
     super(state, builder, backend);
   }
 
+  public abstract T noTreeShaking();
+
+  public abstract T noMinification();
+
   public abstract T addKeepRules(Collection<String> rules);
 
   public T addKeepRules(String... rules) {
@@ -53,4 +57,5 @@
             "  public static void main(java.lang.String[]);",
             "}"));
   }
+
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/InnerClassNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/InnerClassNameTest.java
new file mode 100644
index 0000000..1406a48
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/InnerClassNameTest.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.optimize.reflection;
+// NOTE: This file is just used to create the initial dump in InnerClassNameTestDump.
+class OuterClass {
+  static class InnerClass {}
+}
+
+public class InnerClassNameTest {
+
+  public static void main(String[] args) {
+    Class<?> test = OuterClass.InnerClass.class;
+    System.out.println("getName: " + test.getName());
+    System.out.println("getTypeName: " + test.getTypeName());
+    System.out.println("getCanonicalName: " + test.getCanonicalName());
+    System.out.println("getSimpleName: " + test.getSimpleName());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/InnerClassNameTestDump.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/InnerClassNameTestDump.java
new file mode 100644
index 0000000..cf1dd95
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/InnerClassNameTestDump.java
@@ -0,0 +1,276 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.reflection;
+
+import com.android.tools.r8.ir.optimize.reflection.InnerClassNameTestRunner.TestNamingConfig;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+public class InnerClassNameTestDump implements Opcodes {
+
+  private static void writeInnerClass(TestNamingConfig config, ClassWriter classWriter) {
+    classWriter.visitInnerClass(
+        config.getInnerInternalType(),
+        config.getOuterInternalType(),
+        config.getInnerClassName(),
+        ACC_STATIC);
+  }
+
+  public static Collection<byte[]> dump(TestNamingConfig config) {
+    return ImmutableList.of(
+        dumpMainClass(config),
+        dumpOuterClass(config),
+        dumpInnerClass(config));
+  }
+
+  public static byte[] dumpOuterClass(TestNamingConfig config) {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(
+        V1_8, ACC_SUPER, config.getOuterInternalType(), null, "java/lang/Object", null);
+
+    classWriter.visitSource("InnerClassNameTest.java", null);
+
+    writeInnerClass(config, classWriter);
+
+    {
+      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(6, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      methodVisitor.visitInsn(RETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable(
+          "this", config.getOuterDescriptor(), null, label0, label1, 0);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+
+  public static byte[] dumpInnerClass(TestNamingConfig config) {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(
+        V1_8, ACC_SUPER, config.getInnerInternalType(), null, "java/lang/Object", null);
+
+    classWriter.visitSource("InnerClassNameTest.java", null);
+
+    writeInnerClass(config, classWriter);
+
+    {
+      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(7, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      methodVisitor.visitInsn(RETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable(
+          "this", config.getInnerDescriptor(), null, label0, label1, 0);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+
+  public static byte[] dumpMainClass(TestNamingConfig config) {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(
+        V1_8,
+        ACC_PUBLIC | ACC_SUPER,
+        "com/android/tools/r8/ir/optimize/reflection/InnerClassNameTest",
+        null,
+        "java/lang/Object",
+        null);
+
+    classWriter.visitSource("InnerClassNameTest.java", null);
+
+    writeInnerClass(config, classWriter);
+
+    {
+      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(11, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      methodVisitor.visitInsn(RETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable(
+          "this",
+          "Lcom/android/tools/r8/ir/optimize/reflection/InnerClassNameTest;",
+          null,
+          label0,
+          label1,
+          0);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(14, label0);
+      methodVisitor.visitLdcInsn(Type.getType(config.getInnerDescriptor()));
+      methodVisitor.visitVarInsn(ASTORE, 1);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(15, label1);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitMethodInsn(
+          INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
+      methodVisitor.visitLdcInsn("getName: ");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+          false);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;", false);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+          false);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      if (config.isGetTypeNameSupported()) {
+        Label label2 = new Label();
+        methodVisitor.visitLabel(label2);
+        methodVisitor.visitLineNumber(16, label2);
+        methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+        methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
+        methodVisitor.visitInsn(DUP);
+        methodVisitor.visitMethodInsn(
+            INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
+        methodVisitor.visitLdcInsn("getTypeName: ");
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "java/lang/StringBuilder",
+            "append",
+            "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+            false);
+        methodVisitor.visitVarInsn(ALOAD, 1);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/lang/Class", "getTypeName", "()Ljava/lang/String;", false);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL,
+            "java/lang/StringBuilder",
+            "append",
+            "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+            false);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
+        methodVisitor.visitMethodInsn(
+            INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      }
+      Label label3 = new Label();
+      methodVisitor.visitLabel(label3);
+      methodVisitor.visitLineNumber(17, label3);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitMethodInsn(
+          INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
+      methodVisitor.visitLdcInsn("getCanonicalName: ");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+          false);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Class", "getCanonicalName", "()Ljava/lang/String;", false);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+          false);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      Label label4 = new Label();
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitLineNumber(18, label4);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitMethodInsn(
+          INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
+      methodVisitor.visitLdcInsn("getSimpleName: ");
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+          false);
+      methodVisitor.visitVarInsn(ALOAD, 1);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/Class", "getSimpleName", "()Ljava/lang/String;", false);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL,
+          "java/lang/StringBuilder",
+          "append",
+          "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+          false);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      Label label5 = new Label();
+      methodVisitor.visitLabel(label5);
+      methodVisitor.visitLineNumber(19, label5);
+      methodVisitor.visitInsn(RETURN);
+      Label label6 = new Label();
+      methodVisitor.visitLabel(label6);
+      methodVisitor.visitLocalVariable("args", "[Ljava/lang/String;", null, label0, label6, 0);
+      methodVisitor.visitLocalVariable(
+          "test", "Ljava/lang/Class;", "Ljava/lang/Class<*>;", label1, label6, 1);
+      methodVisitor.visitMaxs(3, 2);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/InnerClassNameTestRunner.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/InnerClassNameTestRunner.java
new file mode 100644
index 0000000..8063a7f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/InnerClassNameTestRunner.java
@@ -0,0 +1,240 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.reflection;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.JvmTestRunResult;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InnerClassNameTestRunner extends TestBase {
+
+  private static final Class<?> MAIN_CLASS = InnerClassNameTest.class;
+  public static final String PACKAGE = "com/android/tools/r8/ir/optimize/reflection/";
+
+  public enum TestNamingConfig {
+    DEFAULT,
+    DOLLAR2_SEPARATOR,
+    EMTPY_SEPARATOR,
+    UNDERBAR_SEPARATOR,
+    NON_NESTED_INNER,
+    OUTER_ENDS_WITH_DOLLAR,
+    $_$_$;
+
+    public String getOuterTypeRaw() {
+      switch (this) {
+        case DEFAULT:
+        case DOLLAR2_SEPARATOR:
+        case EMTPY_SEPARATOR:
+        case UNDERBAR_SEPARATOR:
+        case NON_NESTED_INNER:
+          return "OuterClass";
+        case OUTER_ENDS_WITH_DOLLAR:
+          return "OuterClass$";
+        case $_$_$:
+          return "$";
+      }
+      throw new Unreachable();
+    }
+
+    public String getSeparator() {
+      switch (this) {
+        case DEFAULT:
+        case OUTER_ENDS_WITH_DOLLAR:
+        case $_$_$:
+          return "$";
+        case DOLLAR2_SEPARATOR:
+          return "$$";
+        case UNDERBAR_SEPARATOR:
+          return "_";
+        case NON_NESTED_INNER:
+        case EMTPY_SEPARATOR:
+          return "";
+      }
+      throw new Unreachable();
+    }
+
+    public String getInnerClassName() {
+      return this == $_$_$ ? "$" : "InnerClass";
+    }
+
+    public String getInnerTypeRaw() {
+      switch (this) {
+        case DEFAULT:
+        case OUTER_ENDS_WITH_DOLLAR:
+        case DOLLAR2_SEPARATOR:
+        case EMTPY_SEPARATOR:
+        case UNDERBAR_SEPARATOR:
+        case $_$_$:
+          return getOuterTypeRaw() + getSeparator() + getInnerClassName();
+        case NON_NESTED_INNER:
+          return getInnerClassName();
+      }
+      throw new Unreachable();
+    }
+
+    public String getInnerInternalType() {
+      return PACKAGE + getInnerTypeRaw();
+    }
+
+    public String getOuterInternalType() {
+      return PACKAGE + getOuterTypeRaw();
+    }
+
+    public String getInnerDescriptor() {
+      return "L" + getInnerInternalType() + ";";
+    }
+
+    public String getOuterDescriptor() {
+      return "L" + getOuterInternalType() + ";";
+    }
+
+    public static boolean isGetTypeNameSupported() {
+      return ToolHelper.getDexVm().isNewerThan(DexVm.ART_7_0_0_HOST);
+    }
+  }
+
+  @Parameters(name = "{0} minify:{1} {2}")
+  public static Collection<Object[]> parameters() {
+    return buildParameters(Backend.values(), BooleanUtils.values(), TestNamingConfig.values());
+  }
+
+  private final Backend backend;
+  private final boolean minify;
+  private final TestNamingConfig config;
+
+  public InnerClassNameTestRunner(Backend backend, boolean minify, TestNamingConfig config) {
+    this.backend = backend;
+    this.minify = minify;
+    this.config = config;
+  }
+
+  @Test
+  public void test() throws IOException, CompilationFailedException, ExecutionException {
+    assumeTrue(
+        "b/120185045 The DEX backend does not minify inner-class names",
+        backend != Backend.DEX || !minify);
+
+    JvmTestRunResult runResult =
+        testForJvm().addProgramClassFileData(InnerClassNameTestDump.dump(config)).run(MAIN_CLASS);
+
+    R8TestBuilder r8TestBuilder =
+        testForR8(backend)
+            .addKeepMainRule(MAIN_CLASS)
+            .addKeepRules("-keep,allowobfuscation class * { *; }")
+            .addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
+            .addProgramClassFileData(InnerClassNameTestDump.dump(config));
+
+    if (!minify) {
+      r8TestBuilder.noMinification();
+    }
+
+    R8TestCompileResult r8CompileResult = r8TestBuilder.compile();
+    CodeInspector inspector = r8CompileResult.inspector();
+    R8TestRunResult r8RunResult = r8CompileResult.run(MAIN_CLASS);
+    switch (config) {
+      case DEFAULT:
+      case OUTER_ENDS_WITH_DOLLAR:
+      case $_$_$:
+        if (backend == Backend.CF) {
+          runResult.assertSuccessWithOutput(getExpectedNonMinified(config.getInnerClassName()));
+        }
+        if (backend == Backend.CF && minify) {
+          // TODO(b/120639028) R8 does not keep the structure of inner classes.
+          r8RunResult.assertFailureWithErrorThatMatches(containsString("Malformed class name"));
+        } else {
+          r8RunResult.assertSuccessWithOutput(getExpectedMinified(inspector));
+        }
+        break;
+      case DOLLAR2_SEPARATOR:
+        if (backend == Backend.CF && minify) {
+          // TODO(b/120639028) R8 does not keep the structure of inner classes.
+          r8RunResult.assertFailureWithErrorThatMatches(containsString("Malformed class name"));
+        } else if (backend == Backend.CF) {
+          // $$ as separator and InnerClass as name, results in $InnerClass from getSimpleName...
+          String expectedWithDollarOnInnerName =
+              getExpectedNonMinified("$" + config.getInnerClassName());
+          runResult.assertSuccessWithOutput(expectedWithDollarOnInnerName);
+          // When minifying with R8 the classname $InnerName will map to, eg, 'a' and thus the
+          // dollar will not appear. This is coincidental and could be seen as an error, in which
+          // case R8 should map it to 'a' but with '$$' kept as the separator.
+          r8RunResult.assertSuccessWithOutput(
+              minify ? getExpectedMinified(inspector) : expectedWithDollarOnInnerName);
+        } else {
+          // $$ in DEX will not change the InnerName/getSimpleName.
+          r8RunResult.assertSuccessWithOutput(getExpectedMinified(inspector));
+        }
+        break;
+      case EMTPY_SEPARATOR:
+      case UNDERBAR_SEPARATOR:
+      case NON_NESTED_INNER:
+        if (backend == Backend.CF) {
+          // NOTE(b/120597515): These cases should fail, but if they succeed, we have recovered via
+          // minification, likely by not using the same separator from output in input.
+          // Any non-$ separator results in a runtime exception in getCanonicalName.
+          r8RunResult.assertFailureWithErrorThatMatches(containsString("Malformed class name"));
+        } else {
+          assert backend == Backend.DEX;
+          r8RunResult.assertSuccessWithOutput(getExpectedMinified(inspector));
+        }
+        break;
+      default:
+        throw new Unreachable("Unexpected test configuration: " + config);
+    }
+  }
+
+  private static String getExpected(String typeName, String canonicalName, String simpleName) {
+    if (TestNamingConfig.isGetTypeNameSupported()) {
+      return StringUtils.lines(
+          "getName: " + typeName,
+          "getTypeName: " + typeName,
+          "getCanonicalName: " + canonicalName,
+          "getSimpleName: " + simpleName);
+    } else {
+      return StringUtils.lines(
+          "getName: " + typeName,
+          "getCanonicalName: " + canonicalName,
+          "getSimpleName: " + simpleName);
+    }
+  }
+
+  private String getExpectedNonMinified(String innerName) {
+    String outerClassType = DescriptorUtils.descriptorToJavaType(config.getOuterDescriptor());
+    String innerClassType = DescriptorUtils.descriptorToJavaType(config.getInnerDescriptor());
+    return getExpected(innerClassType, outerClassType + "." + innerName, innerName);
+  }
+
+  private String getExpectedMinified(CodeInspector inspector) {
+    String outerClassType = DescriptorUtils.descriptorToJavaType(config.getOuterDescriptor());
+    String innerClassType = DescriptorUtils.descriptorToJavaType(config.getInnerDescriptor());
+    String outerClassTypeFinal = inspector.clazz(outerClassType).getFinalName();
+    String innerClassTypeFinal = inspector.clazz(innerClassType).getFinalName();
+    String innerNameFinal =
+        !minify
+            ? config.getInnerClassName()
+            : innerClassTypeFinal.substring(innerClassTypeFinal.length() - 1);
+    return getExpected(
+        innerClassTypeFinal, outerClassTypeFinal + "." + innerNameFinal, innerNameFinal);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveAndDebugLocalReads.java b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveAndDebugLocalReads.java
new file mode 100644
index 0000000..ff125be
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/reachabilitysensitive/ReachabilitySensitiveAndDebugLocalReads.java
@@ -0,0 +1,180 @@
+// 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.reachabilitysensitive;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+public class ReachabilitySensitiveAndDebugLocalReads extends TestBase {
+
+  @Test
+  public void test() throws Exception {
+    byte[] classdata = Dump.dump();
+    String mainClass = "test.Test";
+    String expected = StringUtils.lines("5");
+    testForJvm()
+        .addProgramClassFileData(classdata)
+        .run(mainClass)
+        .assertSuccessWithOutput(expected);
+
+    testForD8()
+        .release()
+        .addProgramClassFileData(classdata)
+        .run(mainClass)
+        .assertSuccessWithOutput(expected);
+  }
+}
+
+/* ASM Dump of ReachabilitySensitive/TestClassWithAnnotatedMethod + main method:
+<pre>
+ package test;
+ public class Test {
+
+  @ReachabilitySensitive
+  public void unrelatedAnnotatedMethod() {}
+
+  public void method() {
+    int i = 2;
+    int j = i + 1;
+    int k = j + 2;
+    System.out.println(k);
+  }
+
+  public static void main(String[] args) {
+    new Test().method();
+  }
+}
+</pre>
+ */
+class Dump implements Opcodes {
+
+  public static byte[] dump() {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+    AnnotationVisitor annotationVisitor0;
+
+    classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "test/Test", null, "java/lang/Object", null);
+
+    classWriter.visitSource("Test.java", null);
+
+    {
+      methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(53, label0);
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      methodVisitor.visitInsn(RETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable("this", "Ltest/Test;", null, label0, label1, 0);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+
+    {
+      methodVisitor =
+          classWriter.visitMethod(ACC_PUBLIC, "unrelatedAnnotatedMethod", "()V", null, null);
+      {
+        annotationVisitor0 =
+            methodVisitor.visitAnnotation(
+                "Ldalvik/annotation/optimization/ReachabilitySensitive;", true);
+        annotationVisitor0.visitEnd();
+      }
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(56, label0);
+      methodVisitor.visitInsn(RETURN);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLocalVariable("this", "Ltest/Test;", null, label0, label1, 0);
+      methodVisitor.visitMaxs(0, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "method", "()V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(59, label0);
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitVarInsn(ISTORE, 1);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(60, label1);
+      methodVisitor.visitVarInsn(ILOAD, 1);
+      methodVisitor.visitInsn(ICONST_1);
+      methodVisitor.visitInsn(IADD);
+      methodVisitor.visitVarInsn(ISTORE, 2);
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLineNumber(61, label2);
+      methodVisitor.visitVarInsn(ILOAD, 2);
+      methodVisitor.visitInsn(ICONST_2);
+      methodVisitor.visitInsn(IADD);
+      methodVisitor.visitVarInsn(ISTORE, 3);
+      Label label3 = new Label();
+      methodVisitor.visitLabel(label3);
+      methodVisitor.visitLineNumber(62, label3);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitVarInsn(ILOAD, 3);
+      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false);
+      Label label4 = new Label();
+      methodVisitor.visitLabel(label4);
+      methodVisitor.visitLineNumber(63, label4);
+      // Insert some unneeded code which we know will be removed.
+      methodVisitor.visitLdcInsn(1);
+      methodVisitor.visitLdcInsn(2);
+      methodVisitor.visitInsn(IADD);
+      // Insert an label that will end some local variables so removing will create local reads.
+      Label labelForEndingLocals = new Label();
+      methodVisitor.visitLabel(labelForEndingLocals);
+      methodVisitor.visitInsn(POP);
+      // Pop the unneeded value and continue as usual.
+      methodVisitor.visitInsn(RETURN);
+      Label label5 = new Label();
+      methodVisitor.visitLabel(label5);
+      methodVisitor.visitLocalVariable("this", "Ltest/Test;", null, label0, label5, 0);
+      methodVisitor.visitLocalVariable("i", "I", null, label1, labelForEndingLocals, 1);
+      methodVisitor.visitLocalVariable("j", "I", null, label2, labelForEndingLocals, 2);
+      methodVisitor.visitLocalVariable("k", "I", null, label3, labelForEndingLocals, 3);
+      methodVisitor.visitMaxs(2, 4);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+      methodVisitor.visitCode();
+      Label label0 = new Label();
+      methodVisitor.visitLabel(label0);
+      methodVisitor.visitLineNumber(66, label0);
+      methodVisitor.visitTypeInsn(NEW, "test/Test");
+      methodVisitor.visitInsn(DUP);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "test/Test", "<init>", "()V", false);
+      methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "test/Test", "method", "()V", false);
+      Label label1 = new Label();
+      methodVisitor.visitLabel(label1);
+      methodVisitor.visitLineNumber(67, label1);
+      methodVisitor.visitInsn(RETURN);
+      Label label2 = new Label();
+      methodVisitor.visitLabel(label2);
+      methodVisitor.visitLocalVariable("args", "[Ljava/lang/String;", null, label0, label2, 0);
+      methodVisitor.visitMaxs(2, 1);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/tools/archive.py b/tools/archive.py
index 302b0ff..78a8189 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -5,6 +5,7 @@
 
 import create_maven_release
 import gradle
+import optparse
 import os
 import shutil
 import subprocess
@@ -16,6 +17,13 @@
 
 ARCHIVE_BUCKET = 'r8-releases'
 
+def ParseOptions():
+  result = optparse.OptionParser()
+  result.add_option('--dry-run', '--dry_run',
+      help='Build only, no upload.',
+      default=False, action='store_true')
+  return result.parse_args()
+
 def GetToolVersion(jar_path):
   output = subprocess.check_output(['java', '-jar', jar_path, '--version'])
   return output.splitlines()[0].strip()
@@ -79,12 +87,13 @@
   return GetVersionDestination('http://storage.googleapis.com/', '', is_master)
 
 def Main():
-  if not utils.is_bot():
+  (options, args) = ParseOptions()
+  if not utils.is_bot() and not options.dry_run:
     raise Exception('You are not a bot, don\'t archive builds')
 
   # Generate an r8-ed build without dependencies.
   # Note: build_r8lib does a gradle-clean, this must be the first command.
-  build_r8lib('r8', True, True, utils.R8LIB_KEEP_RULES,
+  build_r8lib('r8nomanifest', True, True, utils.R8LIB_KEEP_RULES,
     utils.R8LIB_EXCLUDE_DEPS_JAR)
 
   # Create maven release which uses a build that exclude dependencies.
@@ -128,13 +137,17 @@
       utils.D8_JAR,
       utils.R8_JAR,
       utils.R8LIB_JAR,
+      utils.R8LIB_JAR + '.map',
       utils.R8_SRC_JAR,
       utils.R8_FULL_EXCLUDE_DEPS_JAR,
       utils.R8LIB_EXCLUDE_DEPS_JAR,
+      utils.R8LIB_EXCLUDE_DEPS_JAR + '.map',
       utils.COMPATDX_JAR,
       utils.COMPATDXLIB_JAR,
+      utils.COMPATDXLIB_JAR + '.map',
       utils.COMPATPROGUARD_JAR,
       utils.COMPATPROGUARDLIB_JAR,
+      utils.COMPATPROGUARDLIB_JAR + '.map',
       utils.MAVEN_ZIP,
       utils.GENERATED_LICENSE,
     ]:
@@ -146,14 +159,20 @@
           zip.write(version_file, os.path.basename(version_file))
       destination = GetUploadDestination(version, file_name, is_master)
       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))
+      if options.dry_run:
+        print('Dry run, not actually uploading')
+      else:
+        utils.upload_file_to_cloud_storage(tagged_jar, destination)
+        print('File available at: %s' % GetUrl(version, file_name, 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 options.dry_run:
+          print('Dry run, not actually creating maven repo')
+        else:
+          utils.upload_file_to_cloud_storage(tagged_jar, maven_dst)
+          print('Maven repo root available at: %s' % GetMavenUrl(is_master))
 
 
 if __name__ == '__main__':
diff --git a/tools/archive_logs.py b/tools/archive_logs.py
deleted file mode 100755
index bdae32e..0000000
--- a/tools/archive_logs.py
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/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.
-
-# Script for achiving gradle test logs.
-
-import utils
-
-if __name__ == '__main__':
-  utils.archive_failures()
diff --git a/tools/build_r8lib.py b/tools/build_r8lib.py
index 5af6d4d..5368f6f 100755
--- a/tools/build_r8lib.py
+++ b/tools/build_r8lib.py
@@ -56,7 +56,7 @@
   # Produce R8 for compiling lib
   if output_path is None:
     output_path = target + 'lib.jar'
-  output_map_path = os.path.splitext(output_path)[0] + '.map'
+  output_map_path = output_path + '.map'
   toolhelper.run(
       'r8',
       ('--release',
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index ae64322..e5ab66c 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -22,7 +22,8 @@
 
 TYPES = ['dex', 'deploy', 'proguarded']
 APPS = ['gmscore', 'nest', 'youtube', 'gmail', 'chrome']
-COMPILERS = ['d8', 'r8']
+COMPILERS = ['d8', 'r8', 'r8lib-r8', 'r8lib-d8']
+R8_COMPILERS = ['r8', 'r8lib-r8']
 
 def ParseOptions(argv):
   result = optparse.OptionParser()
@@ -136,14 +137,18 @@
     for version in data.VERSIONS:
       for type in data.VERSIONS[version]:
         if (app, version, type) not in DISABLED_PERMUTATIONS:
-          yield app, version, type
+          for use_r8lib in [False, True]:
+            yield app, version, type, use_r8lib
 
 def run_all(options, args):
   # Args will be destroyed
   assert len(args) == 0
-  for name, version, type in get_permutations():
-    print('Execution %s %s %s' % (name, version, type))
+  for name, version, type, use_r8lib in get_permutations():
     compiler = 'r8' if type == 'deploy' else 'd8'
+    if use_r8lib:
+      compiler = 'r8lib-' + compiler
+    print('Executing %s with %s %s %s' % (compiler, name, version, type))
+
     fixed_options = copy.copy(options)
     fixed_options.app = name
     fixed_options.version = version
@@ -201,7 +206,7 @@
   version = data.VERSIONS[options.version]
 
   if not options.type:
-    options.type = 'deploy' if options.compiler == 'r8' \
+    options.type = 'deploy' if options.compiler in R8_COMPILERS \
         else 'proguarded'
 
   if options.type not in version:
@@ -213,7 +218,7 @@
   # For R8 'deploy' the JAR is located using the Proguard configuration
   # -injars option. For chrome and nest we don't have the injars in the
   # proguard files.
-  if 'inputs' in values and (options.compiler != 'r8'
+  if 'inputs' in values and (options.compiler not in R8_COMPILERS
                              or options.type != 'deploy'
                              or options.app == 'chrome'
                              or options.app == 'nest'):
@@ -226,7 +231,7 @@
   if 'main-dex-list' in values:
     args.extend(['--main-dex-list', values['main-dex-list']])
 
-  if options.compiler == 'r8':
+  if options.compiler in R8_COMPILERS:
     if 'pgconf' in values and not options.k:
       for pgconf in values['pgconf']:
         args.extend(['--pg-conf', pgconf])
@@ -248,7 +253,7 @@
   # Additional flags for the compiler from the configuration file.
   if 'flags' in values:
     args.extend(values['flags'].split(' '))
-  if options.compiler == 'r8':
+  if options.compiler in R8_COMPILERS:
     if 'r8-flags' in values:
       args.extend(values['r8-flags'].split(' '))
 
@@ -270,7 +275,7 @@
       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 == 'r8' and app_provided_pg_conf:
+      if options.compiler in R8_COMPILERS 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'):
diff --git a/tools/test.py b/tools/test.py
index e451699..0e4625f 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -15,6 +15,7 @@
 import thread
 import time
 import utils
+import uuid
 import notify
 
 ALL_ART_VMS = [
@@ -35,6 +36,8 @@
 # is not a problem, no harm done except some logging in the stdout.
 TIMEOUT_HANDLER_PERIOD = 60 * 18
 
+BUCKET = 'r8-test-results'
+
 def ParseOptions():
   result = optparse.OptionParser()
   result.add_option('--no-internal', '--no_internal',
@@ -103,6 +106,15 @@
 
   return result.parse_args()
 
+def archive_failures():
+  upload_dir = os.path.join(utils.REPO_ROOT, 'build', 'reports', 'tests')
+  u_dir = uuid.uuid4()
+  destination = 'gs://%s/%s' % (BUCKET, u_dir)
+  utils.upload_dir_to_cloud_storage(upload_dir, destination, is_html=True)
+  url = 'http://storage.googleapis.com/%s/%s/test/index.html' % (BUCKET, u_dir)
+  print 'Test results available at: %s' % url
+  print '@@@STEP_LINK@Test failures@%s@@@' % url
+
 def Main():
   (options, args) = ParseOptions()
   if utils.is_bot():
@@ -204,7 +216,7 @@
 
     if return_code != 0:
       if options.archive_failures and os.name != 'nt':
-        utils.archive_failures()
+        archive_failures()
       return return_code
 
   return 0
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index 56cd5a7..d9c96f1 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -13,7 +13,7 @@
   if build is None:
     build, args = extract_build_from_args(args)
   if build:
-    gradle.RunGradle(['r8'])
+    gradle.RunGradle(['r8lib' if tool.startswith('r8lib') else 'r8'])
   cmd = []
   if track_memory_file:
     cmd.extend(['tools/track_memory.sh', track_memory_file])
@@ -24,7 +24,13 @@
     cmd.append('-ea')
   if profile:
     cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
-  cmd.extend(['-jar', utils.R8_JAR, tool])
+  if tool in ['r8', 'd8']:
+    cmd.extend(['-jar', utils.R8_JAR, tool])
+  elif tool == 'r8lib-r8':
+    cmd.extend(['-cp', utils.R8LIB_JAR, 'com.android.tools.r8.R8'])
+  else:
+    assert(tool == 'r8lib-d8')
+    cmd.extend(['-cp', utils.R8LIB_JAR, 'com.android.tools.r8.D8'])
   lib, args = extract_lib_from_args(args)
   if lib:
     cmd.extend(["--lib", lib])
diff --git a/tools/utils.py b/tools/utils.py
index 83a6478..9278459 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -12,7 +12,6 @@
 import sys
 import tarfile
 import tempfile
-import uuid
 
 ANDROID_JAR = 'third_party/android_jar/lib-v{api}/android.jar'
 TOOLS_DIR = os.path.abspath(os.path.normpath(os.path.join(__file__, '..')))
@@ -52,8 +51,6 @@
 RT_JAR = os.path.join(REPO_ROOT, 'third_party/openjdk/openjdk-rt-1.8/rt.jar')
 R8LIB_KEEP_RULES = os.path.join(REPO_ROOT, 'src/main/keep.txt')
 
-TEST_RESULT_BUCKET = 'r8-test-results'
-
 def PrintCmd(s):
   if type(s) is list:
     s = ' '.join(s)
@@ -190,15 +187,6 @@
  def __exit__(self, *_):
    shutil.rmtree(self._temp_dir, ignore_errors=True)
 
-def archive_failures():
-  upload_dir = os.path.join(REPO_ROOT, 'build', 'reports', 'tests')
-  u_dir = uuid.uuid4()
-  destination = 'gs://%s/%s' % (TEST_RESULT_BUCKET, u_dir)
-  upload_dir_to_cloud_storage(upload_dir, destination, is_html=True)
-  url = 'http://storage.googleapis.com/%s/%s/test/index.html' % (TEST_RESULT_BUCKET, u_dir)
-  print 'Test results available at: %s' % url
-  print '@@@STEP_LINK@Test failures@%s@@@' % url
-
 class ChangedWorkingDirectory(object):
  def __init__(self, working_directory):
    self._working_directory = working_directory