Merge "Add a test for minified enum and use of valueOf"
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/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 4e7f421..67b6e2d 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
@@ -1259,9 +1259,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 6aa1853..1a23da1 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/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/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());
+  }
+}