Merge "Avoid direct use of ValueType fields during Value creations."
diff --git a/build.gradle b/build.gradle
index a542ea4..e6432e4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1243,6 +1243,8 @@
     }
 
     dependsOn getJarsFromSupportLibs
+    // R8.jar is required for running bootstrap tests.
+    dependsOn R8
     testLogging.exceptionFormat = 'full'
     if (project.hasProperty('print_test_stdout')) {
         testLogging.showStandardStreams = true
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
index 6fd04b6..fd85c25 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -26,6 +26,7 @@
 import java.util.Collection;
 import java.util.concurrent.ExecutionException;
 
+@Keep
 public class ExtractMarker {
   public static class VdexOrigin extends Origin {
 
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 43989ff..6fe8ad0 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -28,6 +28,7 @@
 import java.util.concurrent.ExecutorService;
 import java.util.stream.Collectors;
 
+@Keep
 public class GenerateMainDexList {
   private final Timing timing = new Timing("maindex");
   private final InternalOptions options;
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 0d4e79d..aeaac60 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.4.0-dev";
+  public static final String LABEL = "1.4.1-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java b/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
index b5db1ae..7cbf816 100644
--- a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
+++ b/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
@@ -73,6 +73,22 @@
       }
       throw new AssertionError("Unknown: " + this);
     }
+
+    public static MultidexStrategy parse(String value) {
+      switch (value) {
+        case "off":
+          return OFF;
+        case "given_shard":
+          return GIVEN_SHARD;
+        case "minimal":
+          return MINIMAL;
+        case "best_effort":
+          return BEST_EFFORT;
+        default:
+          throw new RuntimeException(
+              "Multidex argument must be either 'off', 'given_shard', 'minimal' or 'best_effort'.");
+      }
+    }
   }
 
   private static class Options {
@@ -129,7 +145,7 @@
       }
       string = OptionsParsing.tryParseSingle(context, "--multidex", null);
       if (string != null) {
-        options.multidexMode = MultidexStrategy.valueOf(string.toUpperCase());
+        options.multidexMode = MultidexStrategy.parse(string);
         continue;
       }
       string = OptionsParsing.tryParseSingle(context, "--main-dex-list", null);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 262f670..7e5759a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -643,6 +643,26 @@
     }
   }
 
+  public void optimizeMethodOnSynthesizedClass(DexProgramClass clazz, DexEncodedMethod method) {
+    if (!method.isProcessed()) {
+      try {
+        enterCachedClass(clazz);
+        // Process the generated method, but don't apply any outlining.
+        optimizeSynthesizedMethod(method);
+      } finally {
+        leaveCachedClass(clazz);
+      }
+    }
+  }
+
+  public void optimizeSynthesizedMethod(DexEncodedMethod method) {
+    if (!method.isProcessed()) {
+      // Process the generated method, but don't apply any outlining.
+      processMethod(method, ignoreOptimizationFeedback, x -> false, CallSiteInformation.empty(),
+          Outliner::noProcessing);
+    }
+  }
+
   private void enterCachedClass(DexProgramClass clazz) {
     DexProgramClass previous = cachedClasses.put(clazz.type, clazz);
     assert previous == null;
@@ -653,12 +673,6 @@
     assert existing == clazz;
   }
 
-  public void optimizeSynthesizedMethod(DexEncodedMethod method) {
-    // Process the generated method, but don't apply any outlining.
-    processMethod(method, ignoreOptimizationFeedback, x -> false, CallSiteInformation.empty(),
-        Outliner::noProcessing);
-  }
-
   private String logCode(InternalOptions options, DexEncodedMethod method) {
     return options.useSmaliSyntax ? method.toSmaliString(null) : method.codeToString();
   }
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 03a3818..01555da 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
@@ -128,6 +128,8 @@
   }
 
   private DexProgramClass synthesizeLambdaClass() {
+    DexMethod mainMethod = rewriter.factory
+        .createMethod(type, descriptor.erasedProto, descriptor.name);
     DexProgramClass clazz =
         new DexProgramClass(
             type,
@@ -146,8 +148,11 @@
             synthesizeStaticFields(),
             synthesizeInstanceFields(),
             synthesizeDirectMethods(),
-            synthesizeVirtualMethods(),
+            synthesizeVirtualMethods(mainMethod),
             rewriter.factory.getSkipNameValidationForTesting());
+    // Optimize main method.
+    rewriter.converter.optimizeMethodOnSynthesizedClass(
+        clazz, clazz.lookupVirtualMethod(mainMethod));
     // The method addSynthesizedFrom() may be called concurrently. To avoid a Concurrent-
     // ModificationException we must use synchronization.
     synchronized (synthesizedFrom) {
@@ -177,13 +182,11 @@
   }
 
   // Synthesize virtual methods.
-  private DexEncodedMethod[] synthesizeVirtualMethods() {
+  private DexEncodedMethod[] synthesizeVirtualMethods(DexMethod mainMethod) {
     DexEncodedMethod[] methods = new DexEncodedMethod[1 + descriptor.bridges.size()];
     int index = 0;
 
     // Synthesize main method.
-    DexMethod mainMethod = rewriter.factory
-        .createMethod(type, descriptor.erasedProto, descriptor.name);
     methods[index++] =
         new DexEncodedMethod(
             mainMethod,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 1ac301a..30244bf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1261,9 +1261,7 @@
                   Value argument = invoke.arguments().get(argumentIndex);
                   assert invoke.outType().verifyCompatible(argument.outType());
                   invoke.outValue().replaceUsers(argument);
-                  if (!options.isGeneratingClassFiles()) {
-                    invoke.setOutValue(null);
-                  }
+                  invoke.setOutValue(null);
                 }
               }
             }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 6fcade8..98de5be 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -252,7 +252,7 @@
         if (target != null) {
           // Remove writes to dead (i.e. never read) fields.
           if (!isFieldRead(target, true)) {
-            iterator.remove();
+            iterator.removeOrReplaceByDebugLocalRead();
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 37e6de9..051b81f 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -42,6 +42,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -188,8 +189,11 @@
     }
   }
 
-  private void parseError(DexDefinition item, Origin origin, GenericSignatureFormatError e) {
-    StringBuilder message = new StringBuilder("Invalid signature for ");
+  private void parseError(
+      DexDefinition item, Origin origin, String signature, GenericSignatureFormatError e) {
+    StringBuilder message = new StringBuilder("Invalid signature '");
+    message.append(signature);
+    message.append("' for ");
     if (item.isDexClass()) {
       message.append("class ");
       message.append((item.asDexClass()).getType().toSourceString());
@@ -202,43 +206,54 @@
       message.append(item.toSourceString());
     }
     message.append(".\n");
+    message.append("Signature is ignored and will not be present in the output.\n");
+    message.append("Parser error: ");
     message.append(e.getMessage());
     reporter.warning(new StringDiagnostic(message.toString(), origin));
   }
 
   private void renameTypesInGenericSignatures() {
     for (DexClass clazz : appInfo.classes()) {
-      clazz.annotations = rewriteGenericSignatures(clazz.annotations,
-          genericSignatureParser::parseClassSignature,
-          e -> parseError(clazz, clazz.getOrigin(), e));
-      clazz.forEachField(field ->
-          field.annotations = rewriteGenericSignatures(
-              field.annotations, genericSignatureParser::parseFieldSignature,
-              e -> parseError(field, clazz.getOrigin(), e)));
-      clazz.forEachMethod(method ->
-        method.annotations = rewriteGenericSignatures(
-            method.annotations, genericSignatureParser::parseMethodSignature,
-            e -> parseError(method, clazz.getOrigin(), e)));
+      clazz.annotations =
+          rewriteGenericSignatures(
+              clazz.annotations,
+              genericSignatureParser::parseClassSignature,
+              (signature, e) -> parseError(clazz, clazz.getOrigin(), signature, e));
+      clazz.forEachField(
+          field ->
+              field.annotations =
+                  rewriteGenericSignatures(
+                      field.annotations,
+                      genericSignatureParser::parseFieldSignature,
+                      (signature, e) -> parseError(field, clazz.getOrigin(), signature, e)));
+      clazz.forEachMethod(
+          method ->
+              method.annotations =
+                  rewriteGenericSignatures(
+                      method.annotations,
+                      genericSignatureParser::parseMethodSignature,
+                      (signature, e) -> parseError(method, clazz.getOrigin(), signature, e)));
     }
   }
 
   private DexAnnotationSet rewriteGenericSignatures(
       DexAnnotationSet annotations,
       Consumer<String> parser,
-      Consumer<GenericSignatureFormatError> parseError) {
+      BiConsumer<String, GenericSignatureFormatError> parseError) {
     // There can be no more than one signature annotation in an annotation set.
     final int VALID = -1;
     int invalid = VALID;
     for (int i = 0; i < annotations.annotations.length && invalid == VALID; i++) {
       DexAnnotation annotation = annotations.annotations[i];
       if (DexAnnotation.isSignatureAnnotation(annotation, appInfo.dexItemFactory)) {
+        String signature = DexAnnotation.getSignature(annotation);
         try {
-          parser.accept(DexAnnotation.getSignature(annotation));
+          parser.accept(signature);
           annotations.annotations[i] = DexAnnotation.createSignatureAnnotation(
               genericSignatureRewriter.getRenamedSignature(),
               appInfo.dexItemFactory);
         } catch (GenericSignatureFormatError e) {
-          parseError.accept(e);
+          parseError.accept(signature, e);
           invalid = i;
         }
       }
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index fe002ec..68d634e 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -1354,8 +1354,12 @@
               "lang.Character.isWhitespaceI.Character_isWhitespace_A01",
               match(runtimes(Runtime.ART_V4_0_4)))
           .put("lang.Character.getDirectionalityI.Character_getDirectionality_A01", any())
-          .put("lang.Character.UnicodeBlock.ofC.UnicodeBlock_of_A01", any())
-          .put("lang.Character.UnicodeBlock.ofI.UnicodeBlock_of_A01", any())
+          .put(
+              "lang.Character.UnicodeBlock.ofC.UnicodeBlock_of_A01",
+              match(artRuntimesFromAndJava(Runtime.ART_V4_4_4)))
+          .put(
+              "lang.Character.UnicodeBlock.ofI.UnicodeBlock_of_A01",
+              match(artRuntimesFromAndJava(Runtime.ART_V4_4_4)))
           .put("lang.Character.isLowerCaseI.Character_isLowerCase_A01", anyDexVm())
           .put("lang.Process.waitFor.Process_waitFor_A01", anyDexVm())
           .put("lang.System.getProperties.System_getProperties_A01", anyDexVm())
@@ -1527,7 +1531,7 @@
               match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
           .put(
               "lang.reflect.AccessibleObject.setAccessibleZ.AccessibleObject_setAccessible_A04",
-              match(runtimes(Runtime.ART_V4_4_4, Runtime.JAVA)))
+              match(artRuntimesUpToAndJava(Runtime.ART_V4_4_4)))
           .put(
               "lang.reflect.AccessibleObject.setAccessible_Ljava_lang_reflect_AccessibleObjectZ.AccessibleObject_setAccessible_A04",
               match(artRuntimesUpToAndJava(Runtime.ART_V6_0_1)))
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index bf80acc..403ee70 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -706,6 +706,11 @@
     }
   }
 
+  protected ProcessResult runOnVMRaw(AndroidApp app, Class<?> mainClass, Backend backend)
+      throws IOException {
+    return runOnVMRaw(app, mainClass.getCanonicalName(), backend);
+  }
+
   protected ProcessResult runOnVMRaw(AndroidApp app, String mainClass, Backend backend)
       throws IOException {
     switch (backend) {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index e34282d..f5eaa25 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -93,7 +93,7 @@
   public static final String SMALI_BUILD_DIR = TESTS_BUILD_DIR + "smali/";
 
   public static final String LINE_SEPARATOR = StringUtils.LINE_SEPARATOR;
-  public final static String PATH_SEPARATOR = File.pathSeparator;
+  public static final String PATH_SEPARATOR = File.pathSeparator;
   public static final String DEFAULT_DEX_FILENAME = "classes.dex";
   public static final String DEFAULT_PROGUARD_MAP_FILE = "proguard.map";
 
@@ -106,6 +106,8 @@
   private static final String PROGUARD6_0_1 = "third_party/proguard/proguard6.0.1/bin/proguard";
   private static final String PROGUARD = PROGUARD5_2_1;
 
+  public static final Path R8_JAR = Paths.get(LIBS_DIR, "r8.jar");
+
   public enum DexVm {
     ART_4_0_4_TARGET(Version.V4_0_4, Kind.TARGET),
     ART_4_0_4_HOST(Version.V4_0_4, Kind.HOST),
@@ -997,7 +999,7 @@
 
   public static ProcessResult forkR8Jar(Path dir, String... args)
       throws IOException, InterruptedException {
-    String r8Jar = Paths.get(LIBS_DIR,  "r8.jar").toAbsolutePath().toString();
+    String r8Jar = R8_JAR.toAbsolutePath().toString();
     return forkJavaWithJar(dir, r8Jar, Arrays.asList(args));
   }
 
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
new file mode 100644
index 0000000..02df3d1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
@@ -0,0 +1,205 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.cf;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static com.google.common.io.ByteStreams.toByteArray;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassHierarchyVerifier;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.base.Charsets;
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+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.List;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class BootstrapCurrentEqualityTest extends TestBase {
+
+  private static final String R8_NAME = "com.android.tools.r8.R8";
+  private static final Path MAIN_KEEP = Paths.get("src/main/keep.txt");
+
+  private static final String HELLO_NAME = "hello.Hello";
+  private static final String[] KEEP_HELLO = {
+    "-keep class " + HELLO_NAME + " {", "  public static void main(...);", "}",
+  };
+
+  private class R8Result {
+
+    final ProcessResult processResult;
+    final Path outputJar;
+    final String pgMap;
+
+    R8Result(ProcessResult processResult, Path outputJar, String pgMap) {
+      this.processResult = processResult;
+      this.outputJar = outputJar;
+      this.pgMap = pgMap;
+    }
+
+    @Override
+    public String toString() {
+      return processResult.toString() + "\n\n" + pgMap;
+    }
+  }
+
+  private static Path r8R8Debug;
+  private static Path r8R8Release;
+
+  @ClassRule public static TemporaryFolder testFolder = new TemporaryFolder();
+
+  @BeforeClass
+  public static void beforeAll() throws Exception {
+    r8R8Debug = compileR8(CompilationMode.DEBUG);
+    r8R8Release = compileR8(CompilationMode.RELEASE);
+  }
+
+  private static Path compileR8(CompilationMode mode) throws Exception {
+    // Run R8 on r8.jar.
+    Path output = runR8(ToolHelper.R8_JAR, testFolder.newFolder().toPath(), mode);
+    // Check that all non-abstract classes in the R8'd R8 implement all abstract/interface methods
+    // from their supertypes. This is a sanity check for the tree shaking and minification.
+    AndroidApp app = AndroidApp.builder().addProgramFile(output).build();
+    new ClassHierarchyVerifier(new CodeInspector(app)).run();
+    return output;
+  }
+
+  @Test
+  public void test() throws Exception {
+    Path helloJar = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION);
+    ProcessResult runResult = ToolHelper.runJava(helloJar, "hello.Hello");
+    assertEquals(0, runResult.exitCode);
+    compareR8(helloJar, runResult, KEEP_HELLO, "hello.Hello");
+  }
+
+  private void compareR8(Path program, ProcessResult runResult, String[] keep, String... args)
+      throws Exception {
+    R8Result runR8Debug =
+        runExternalR8(ToolHelper.R8_JAR, program, temp.newFolder().toPath(), keep, "--debug");
+    assertEquals(runResult.toString(), ToolHelper.runJava(runR8Debug.outputJar, args).toString());
+    R8Result runR8Release =
+        runExternalR8(ToolHelper.R8_JAR, program, temp.newFolder().toPath(), keep, "--release");
+    assertEquals(runResult.toString(), ToolHelper.runJava(runR8Release.outputJar, args).toString());
+    RunR8AndCheck(r8R8Debug, program, runR8Debug, keep, "--debug");
+    RunR8AndCheck(r8R8Debug, program, runR8Release, keep, "--release");
+    RunR8AndCheck(r8R8Release, program, runR8Debug, keep, "--debug");
+    RunR8AndCheck(r8R8Release, program, runR8Release, keep, "--release");
+  }
+
+  private void RunR8AndCheck(Path r8, Path program, R8Result result, String[] keep, String mode)
+      throws Exception {
+    R8Result runR8R8 = runExternalR8(r8, program, temp.newFolder().toPath(), keep, mode);
+    // Check that the process outputs (exit code, stdout, stderr) are the same.
+    assertEquals(result.toString(), runR8R8.toString());
+    // Check that the output jars are the same.
+    assertProgramsEqual(result.outputJar, runR8R8.outputJar);
+  }
+
+  private static Path runR8(Path inputJar, Path outputPath, CompilationMode mode) throws Exception {
+    Path outputJar = outputPath.resolve("output.jar");
+    ToolHelper.runR8(
+        R8Command.builder()
+            .setMode(mode)
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+            .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outputJar, true))
+            .addProgramFiles(inputJar)
+            .addProguardConfigurationFiles(MAIN_KEEP)
+            .build());
+    return outputJar;
+  }
+
+  private R8Result runExternalR8(
+      Path r8Jar, Path inputJar, Path output, String[] keepRules, String mode) throws Exception {
+    Path pgConfigFile = output.resolve("keep.rules");
+    Path outputJar = output.resolve("output.jar");
+    Path pgMapFile = output.resolve("map.txt");
+    FileUtils.writeTextFile(pgConfigFile, keepRules);
+    ProcessResult processResult =
+        ToolHelper.runJava(
+            r8Jar,
+            R8_NAME,
+            "--lib",
+            ToolHelper.JAVA_8_RUNTIME,
+            "--classfile",
+            inputJar.toString(),
+            "--output",
+            outputJar.toString(),
+            "--pg-conf",
+            pgConfigFile.toString(),
+            mode,
+            "--pg-map-output",
+            pgMapFile.toString());
+    assertEquals(0, processResult.exitCode);
+    String pgMap = FileUtils.readTextFile(pgMapFile, Charsets.UTF_8);
+    return new R8Result(processResult, outputJar, pgMap);
+  }
+
+  private static void assertProgramsEqual(Path expectedJar, Path actualJar) throws Exception {
+    if (filesAreEqual(expectedJar, actualJar)) {
+      return;
+    }
+    ArchiveClassFileProvider expected = new ArchiveClassFileProvider(expectedJar);
+    ArchiveClassFileProvider actual = new ArchiveClassFileProvider(actualJar);
+    assertEquals(getSortedDescriptorList(expected), getSortedDescriptorList(actual));
+    for (String descriptor : expected.getClassDescriptors()) {
+      assertArrayEquals(
+          "Class " + descriptor + " differs",
+          getClassAsBytes(expected, descriptor),
+          getClassAsBytes(actual, descriptor));
+    }
+  }
+
+  private static boolean filesAreEqual(Path file1, Path file2) throws IOException {
+    long size = Files.size(file1);
+    long sizeOther = Files.size(file2);
+    if (size != sizeOther) {
+      return false;
+    }
+    if (size < 4096) {
+      return Arrays.equals(Files.readAllBytes(file1), Files.readAllBytes(file2));
+    }
+    int byteRead1 = 0;
+    int byteRead2 = 0;
+    try (FileInputStream fs1 = new FileInputStream(file1.toString());
+        FileInputStream fs2 = new FileInputStream(file2.toString())) {
+      BufferedInputStream bs1 = new BufferedInputStream(fs1);
+      BufferedInputStream bs2 = new BufferedInputStream(fs2);
+      while (byteRead1 == byteRead2 && byteRead1 != -1) {
+        byteRead1 = bs1.read();
+        byteRead2 = bs2.read();
+      }
+    }
+    return byteRead1 == byteRead2;
+  }
+
+  private static List<String> getSortedDescriptorList(ArchiveClassFileProvider inputJar) {
+    ArrayList<String> descriptorList = new ArrayList<>(inputJar.getClassDescriptors());
+    Collections.sort(descriptorList);
+    return descriptorList;
+  }
+
+  private static byte[] getClassAsBytes(ArchiveClassFileProvider inputJar, String descriptor)
+      throws Exception {
+    return toByteArray(inputJar.getProgramResource(descriptor).getByteStream());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynthesizedLambdaClass.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynthesizedLambdaClass.java
new file mode 100644
index 0000000..dc1c40b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynthesizedLambdaClass.java
@@ -0,0 +1,58 @@
+// 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.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+
+public class InlineSynthesizedLambdaClass extends TestBase {
+
+  @Test
+  public void test() throws Exception {
+    AndroidApp input = readClasses(Lambda.class, Lambda.Consumer.class);
+    AndroidApp output =
+        compileWithR8(
+            input,
+            String.join(
+                System.lineSeparator(),
+                keepMainProguardConfiguration(Lambda.class),
+                "-allowaccessmodification"),
+            options -> options.enableMinification = false);
+
+    // Check that everything has been inlined into main.
+    CodeInspector inspector = new CodeInspector(output);
+    assertEquals(1, inspector.allClasses().size());
+
+    ClassSubject classSubject = inspector.clazz(Lambda.class);
+    assertThat(classSubject, isPresent());
+    assertEquals(1, classSubject.allMethods().size());
+
+    // Check that the program gives the expected result.
+    assertEquals(runOnJava(Lambda.class), runOnArt(output, Lambda.class));
+  }
+}
+
+class Lambda {
+
+  interface Consumer<T> {
+    void accept(T value);
+  }
+
+  public static void main(String... args) {
+    load(s -> System.out.println(s));
+    load(s -> System.out.println(s));
+  }
+
+  public static void load(Consumer<String> c) {
+    c.accept("Hello!");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinification.java b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
new file mode 100644
index 0000000..a8708eb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
+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 EnumMinification extends TestBase {
+
+  private Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Collection<Backend> data() {
+    return Arrays.asList(Backend.values());
+  }
+
+  public EnumMinification(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void test() throws Exception {
+    AndroidApp output =
+        ToolHelper.runR8(
+            R8Command.builder()
+                .addClassProgramData(ToolHelper.getClassAsBytes(Main.class), Origin.unknown())
+                .addClassProgramData(ToolHelper.getClassAsBytes(Enum.class), Origin.unknown())
+                .addProguardConfiguration(
+                    ImmutableList.of(keepMainProguardConfiguration(Main.class)), Origin.unknown())
+                .setProgramConsumer(emptyConsumer(backend))
+                .build());
+
+    // TODO(117299356): valueOf on enum fails for minified enums.
+    ProcessResult result = runOnVMRaw(output, Main.class, backend);
+    assertEquals(1, result.exitCode);
+    assertThat(
+        result.stderr,
+        containsString(
+            backend == Backend.DEX
+                ? ToolHelper.getDexVm().isNewerThan(DexVm.ART_4_4_4_HOST)
+                    ? "java.lang.NoSuchMethodException"
+                    : "java.lang.NullPointerException"
+                : "java.lang.IllegalArgumentException"));
+  }
+}
+
+class Main {
+
+  public static void main(String[] args) {
+    Enum e = Enum.valueOf("VALUE1");
+    System.out.println(e);
+  }
+}
+
+enum Enum {
+  VALUE1,
+  VALUE2
+}
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
index 3240365..8d62033 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
@@ -498,7 +498,7 @@
     testSingleClass("Outer", "X", diagnostics -> {
       assertEquals(1, diagnostics.warnings.size());
       DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
-          "Invalid signature for class Outer", "Expected L at position 1");
+          "Invalid signature 'X' for class Outer", "Expected L at position 1");
     }, inspector -> noSignatureAttribute(inspector.clazz("Outer")));
   }
 
@@ -507,7 +507,7 @@
     testSingleClass("Outer", "<L", diagnostics -> {
       assertEquals(1, diagnostics.warnings.size());
       DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
-          "Invalid signature for class Outer", "Unexpected end of signature at position 3");
+          "Invalid signature '<L' for class Outer", "Unexpected end of signature at position 3");
     }, inspector -> noSignatureAttribute(inspector.clazz("Outer")));
   }
 
@@ -516,7 +516,7 @@
     testSingleClass("Outer$ExtendsInner", "X", diagnostics -> {
       assertEquals(1, diagnostics.warnings.size());
       DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
-          "Invalid signature for class Outer$ExtendsInner", "Expected L at position 1");
+          "Invalid signature 'X' for class Outer$ExtendsInner", "Expected L at position 1");
     }, inspector -> noSignatureAttribute(inspector.clazz("Outer$ExtendsInner")));
   }
 
@@ -525,7 +525,7 @@
     testSingleClass("Outer$Inner$ExtendsInnerInner", "X", diagnostics -> {
       assertEquals(1, diagnostics.warnings.size());
       DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
-          "Invalid signature for class Outer$Inner$ExtendsInnerInner",
+          "Invalid signature 'X' for class Outer$Inner$ExtendsInnerInner",
           "Expected L at position 1");
     }, inspector -> noSignatureAttribute(inspector.clazz("Outer$Inner$ExtendsInnerInner")));
   }
@@ -548,8 +548,11 @@
     String signature = "LOuter<TT;>.com/example/Inner;";
     testSingleClass("Outer$ExtendsInner", signature, diagnostics -> {
       assertEquals(1, diagnostics.warnings.size());
-      DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
-          "Invalid signature for class Outer$ExtendsInner", "Expected ; at position 16");
+      DiagnosticsChecker.checkDiagnostic(
+          diagnostics.warnings.get(0),
+          this::isOriginUnknown,
+          "Invalid signature '" + signature + "' for class Outer$ExtendsInner",
+          "Expected ; at position 16");
     }, inspector -> {
       noSignatureAttribute(inspector.clazz("Outer$ExtendsInner"));
     });
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
index 4c81632..f73b8d2 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
@@ -278,7 +278,7 @@
       assertEquals(1, diagnostics.warnings.size());
       // TODO(sgjesse): The position 2 reported here is one off.
       DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
-          "Invalid signature for field",
+          "Invalid signature 'X' for field",
           "java.lang.String Fields.anX",
           "Expected L, [ or T at position 2");
     }, inspector -> noSignatureAttribute(lookupAnX(inspector)));
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
index a1ab483..d9d4774 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
@@ -299,7 +299,7 @@
     testSingleMethod("generic", "X", diagnostics -> {
       assertEquals(1, diagnostics.warnings.size());
       DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
-          "Invalid signature for method",
+          "Invalid signature 'X' for method",
           "java.lang.Throwable Methods.generic(java.lang.Throwable, Methods$Inner)",
           "Expected ( at position 1");
     }, inspector -> noSignatureAttribute(lookupGeneric(inspector)));
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 6331f5d..90013b0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -17,6 +17,12 @@
 
   public abstract void forAllMethods(Consumer<FoundMethodSubject> inspection);
 
+  public final List<FoundMethodSubject> allMethods() {
+    ImmutableList.Builder<FoundMethodSubject> builder = ImmutableList.builder();
+    forAllMethods(builder::add);
+    return builder.build();
+  }
+
   public MethodSubject method(Method method) {
     List<String> parameters = new ArrayList<>();
     for (Class<?> parameterType : method.getParameterTypes()) {
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index dbf393b..1e6ebfd 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -3,6 +3,7 @@
 # BSD-style license that can be found in the LICENSE file.
 
 import gradle
+import os
 import subprocess
 import utils
 
@@ -23,6 +24,9 @@
   if profile:
     cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
   cmd.extend(['-jar', utils.R8_JAR, tool])
+  lib, args = extract_lib_from_args(args)
+  if lib:
+    cmd.extend(["--lib", lib])
   cmd.extend(args)
   utils.PrintCmd(cmd)
   return subprocess.call(cmd)
@@ -36,3 +40,15 @@
     else:
       args.append(arg)
   return build, args
+
+def extract_lib_from_args(input_args):
+  lib = None
+  args = []
+  for arg in input_args:
+    if arg == '--lib-android':
+      lib = utils.get_android_jar(26)
+    elif arg == '--lib-java':
+      lib = utils.RT_JAR
+    else:
+      args.append(arg)
+  return lib, args