Merge "Add cache for getting the clinit method on a DexClass"
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 88c1b0a..6a6cd08 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
@@ -916,7 +916,7 @@
 
     previous = printMethod(code, "IR after disable assertions (SSA)", previous);
 
-    if (options.enableNonNullTracking && nonNullTracker != null) {
+    if (nonNullTracker != null) {
       nonNullTracker.addNonNull(code);
       assert code.isConsistentSSA();
     }
@@ -975,7 +975,7 @@
       invertConditionalsForTesting(code);
     }
 
-    if (options.enableNonNullTracking && nonNullTracker != null) {
+    if (nonNullTracker != null) {
       // Computation of non-null parameters on normal exits rely on the existence of non-null IRs.
       nonNullTracker.computeNonNullParamOnNormalExits(feedback, code);
       nonNullTracker.cleanupNonNull(code);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index ec338ca..489891a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -120,14 +120,28 @@
             knownToBeNonNullValues.add(knownToBeNonNullValue);
           }
         }
-        if (current.isInvokeMethod()
-            && !current.isInvokePolymorphic()
-            && appView.appInfo().hasLiveness()) {
-          DexEncodedMethod singleTarget =
-              current
-                  .asInvokeMethod()
-                  .lookupSingleTarget(
-                      appView.appInfo().withLiveness(), code.method.method.holder);
+        if (current.isInvokeMethod() && !current.isInvokePolymorphic()) {
+          DexEncodedMethod singleTarget = null;
+          if (appView.enableWholeProgramOptimizations()) {
+            assert appView.appInfo().hasLiveness();
+            singleTarget =
+                current
+                    .asInvokeMethod()
+                    .lookupSingleTarget(
+                        appView.appInfo().withLiveness(), code.method.method.holder);
+          } else {
+            // Even in D8, invoke-{direct|static} can be resolved without liveness.
+            // Due to the incremental compilation, though, it is allowed only if the holder of the
+            // invoked method is same as that of the method we are processing now.
+            DexMethod invokedMethod = current.asInvokeMethod().getInvokedMethod();
+            if (invokedMethod.holder == code.method.method.holder) {
+              if (current.isInvokeDirect()) {
+                singleTarget = appView.appInfo().lookupDirectTarget(invokedMethod);
+              } else if (current.isInvokeStatic()) {
+                singleTarget = appView.appInfo().lookupStaticTarget(invokedMethod);
+              }
+            }
+          }
           if (singleTarget != null
               && singleTarget.getOptimizationInfo().getNonNullParamOnNormalExits() != null) {
             BitSet facts = singleTarget.getOptimizationInfo().getNonNullParamOnNormalExits();
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 2bc4483..64624d3 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -414,6 +414,10 @@
         continue;
       }
       DexDefinition dependentDefinition = appView.definitionFor(dependentItem);
+      if (dependentDefinition == null) {
+        assert false;
+        continue;
+      }
       if (!dependentDefinition.isStaticMember()) {
         enqueueRootItem(holder, entry.getValue());
         // Enough to enqueue the known holder once.
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 5a71add..52952a5 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -1848,9 +1848,54 @@
     }
   }
 
+  private static class VmErrors {
+    private final Set<TestRuntime> failedVms = new HashSet<>();
+    private StringBuilder message;
+
+    private void addShouldHaveFailedError(CompilerUnderTest compilerUnderTest, TestRuntime vm) {
+      addFailure(vm);
+      message.append(
+          "FAILURE: Test should have failed on "
+              + vm
+              + " after compiling with "
+              + compilerUnderTest
+              + ".\n");
+    }
+
+    private void addFailedOnRunError(
+        CompilerUnderTest compilerUnderTest, TestRuntime vm, AssertionError error) {
+      addFailure(vm);
+      message.append(
+          "FAILURE: Test failed on "
+              + vm
+              + " after compiling with "
+              + compilerUnderTest
+              + ", error:\n"
+              + error.getMessage()
+              + "\n");
+    }
+
+    private void addFailure(TestRuntime vm) {
+      if (message == null) {
+        message = new StringBuilder();
+      }
+      failedVms.add(vm);
+    }
+  }
+
   protected void runJctfTest(
       CompilerUnderTest compilerUnderTest, String classFilePath, String fullClassName)
       throws IOException, CompilationFailedException {
+    VmErrors vmErrors = runJctfTestCore(compilerUnderTest, classFilePath, fullClassName);
+    if (vmErrors.message != null) {
+      throw new RuntimeException(vmErrors.message.toString());
+    }
+  }
+
+  private VmErrors runJctfTestCore(
+      CompilerUnderTest compilerUnderTest, String classFilePath, String fullClassName)
+      throws IOException, CompilationFailedException {
+    VmErrors vmErrors = new VmErrors();
     List<TestRuntime> vms = new ArrayList<>();
     if (compilerUnderTest == CompilerUnderTest.R8CF) {
       for (CfVm vm : TestParametersBuilder.getAvailableCfVms()) {
@@ -1890,7 +1935,7 @@
     }
 
     if (vmSpecs.isEmpty()) {
-      return;
+      return vmErrors;
     }
 
     File classFile = new File(JCTF_TESTS_PREFIX + "/" + classFilePath);
@@ -1959,7 +2004,7 @@
         runJctfTestDoRunOnJava(
             fileNames, vmSpec.spec, fullClassName, compilationMode, vmSpec.vm.asCf().getVm());
       }
-      return;
+      return vmErrors;
     }
 
     CompilationOptions compilationOptions = null;
@@ -1981,17 +2026,31 @@
       Files.copy(
           compiledDir.toPath().resolve("classes.dex"),
           vmSpec.spec.directory.toPath().resolve("classes.dex"));
-      runJctfTestDoRunOnArt(fileNames, vmSpec.spec, fullClassName, vmSpec.vm.asDex().getVm());
+
+      AssertionError vmError = null;
+      try {
+        runJctfTestDoRunOnArt(fileNames, vmSpec.spec, fullClassName, vmSpec.vm.asDex().getVm());
+      } catch (AssertionError e) {
+        vmError = e;
+      }
+      if (vmSpec.spec.failsOnRun && vmError == null) {
+        vmErrors.addShouldHaveFailedError(firstCompilerUnderTest, vmSpec.vm);
+      } else if (!vmSpec.spec.failsOnRun && vmError != null) {
+        vmErrors.addFailedOnRunError(firstCompilerUnderTest, vmSpec.vm, vmError);
+      }
     }
 
     if (compilerUnderTest != CompilerUnderTest.R8_AFTER_D8) {
-      return;
+      return vmErrors;
     }
 
     // Second pass (R8), if R8_AFTER_D8.
     CompilationOptions r8CompilationOptions = null;
     File r8CompiledDir = temp.newFolder();
     for (VmSpec vmSpec : vmSpecs) {
+      if (vmSpec.spec.failsOnRun || vmErrors.failedVms.contains(vmSpec.vm)) {
+        continue;
+      }
       File r8ResultDir = temp.newFolder("r8-output-" + vmSpec.vm.toString());
       TestSpecification specification =
           JctfTestSpecifications.getExpectedOutcome(
@@ -2019,8 +2078,13 @@
       Files.copy(
           r8CompiledDir.toPath().resolve("classes.dex"),
           specification.directory.toPath().resolve("classes.dex"));
-      runJctfTestDoRunOnArt(fileNames, specification, fullClassName, vmSpec.vm.asDex().getVm());
+      try {
+        runJctfTestDoRunOnArt(fileNames, specification, fullClassName, vmSpec.vm.asDex().getVm());
+      } catch (AssertionError e) {
+        vmErrors.addFailedOnRunError(CompilerUnderTest.R8, vmSpec.vm, e);
+      }
     }
+    return vmErrors;
   }
 
   private void runJctfTestDoRunOnArt(
@@ -2061,10 +2125,6 @@
     builder.setMainClass(JUNIT_TEST_RUNNER);
     builder.appendProgramArgument(fullClassName);
 
-    if (specification.failsOnRun) {
-      expectException(AssertionError.class);
-    }
-
     try {
       ToolHelper.runArt(builder);
     } catch (AssertionError e) {
@@ -2072,9 +2132,6 @@
           specification.resolveFile("classes.dex"), e);
       throw e;
     }
-    if (specification.failsOnRun) {
-      System.err.println("Should have failed run with art.");
-    }
   }
 
   private void runJctfTestDoRunOnJava(
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
index fc00afb..d96d999 100644
--- a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
@@ -13,6 +13,8 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.ExternalR8TestCompileResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.FileUtils;
@@ -25,17 +27,22 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 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;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 /**
  * This test relies on a freshly built build/libs/r8lib_with_deps.jar. If this test fails remove
  * build directory and rebuild r8lib_with_deps by calling test.py or gradle r8libWithdeps.
  */
+@RunWith(Parameterized.class)
 public class BootstrapCurrentEqualityTest extends TestBase {
 
   private static final Path MAIN_KEEP = Paths.get("src/main/keep.txt");
@@ -54,8 +61,19 @@
 
   @BeforeClass
   public static void beforeAll() throws Exception {
-    r8R8Debug = compileR8(CompilationMode.DEBUG);
-    r8R8Release = compileR8(CompilationMode.RELEASE);
+    if (data().stream().count() > 0) {
+      r8R8Debug = compileR8(CompilationMode.DEBUG);
+      r8R8Release = compileR8(CompilationMode.RELEASE);
+    }
+  }
+
+  @Parameters
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  public BootstrapCurrentEqualityTest(TestParameters parameters) {
+    // TODO: use parameters to run on the right java.
   }
 
   private static Pair<Path, Path> compileR8(CompilationMode mode) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapTest.java b/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
index fc24ed3..883c2c6 100644
--- a/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
@@ -13,6 +13,8 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.FileUtils;
@@ -23,7 +25,11 @@
 import java.util.Collections;
 import java.util.List;
 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 BootstrapTest extends TestBase {
 
   private static final Path R8_STABLE_JAR = Paths.get("third_party/r8/r8.jar");
@@ -56,6 +62,15 @@
     }
   }
 
+  @Parameters
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  public BootstrapTest(TestParameters parameters) {
+    // TODO: use parameters to fork the right Java.
+  }
+
   @Test
   public void test() throws Exception {
     // Run hello.jar to ensure it exists and is valid.
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 5d4f802..ef53f73 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -20,6 +20,8 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Constants;
@@ -79,6 +81,7 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -95,7 +98,11 @@
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
+@RunWith(Parameterized.class)
 public class MainDexListTests extends TestBase {
 
   private static final int MAX_METHOD_COUNT = Constants.U16BIT_MAX;
@@ -106,6 +113,15 @@
   private static final int MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS = 10;
   private static List<String> MANY_CLASSES;
 
+  @Parameters
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  public MainDexListTests(TestParameters parameters) {
+    // We ignore the paramters, but only run once instead of running on every vm
+  }
+
   interface Runner {
     void run(DiagnosticsHandler handler) throws Throwable;
   }
@@ -117,6 +133,9 @@
   // Generate the test applications in a @BeforeClass method, as they are used by several tests.
   @BeforeClass
   public static void generateTestApplications() throws Throwable {
+    if (data().stream().count() == 0) {
+      return;
+    }
     ImmutableList.Builder<String> builder = ImmutableList.builder();
     for (int i = 0; i < MANY_CLASSES_COUNT; ++i) {
       String pkg = i % 2 == 0 ? "a" : "b";
diff --git a/tools/archive.py b/tools/archive.py
index 25e6a4f..9213cc2 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -138,7 +138,7 @@
     version = GetGitHash()
 
   destination = GetVersionDestination('gs://', version, is_master)
-  if utils.cloud_storage_exists(destination):
+  if utils.cloud_storage_exists(destination) and not options.dry_run:
     raise Exception('Target archive directory %s already exists' % destination)
   with utils.TempDir() as temp:
     version_file = os.path.join(temp, 'r8-version.properties')
diff --git a/tools/as_utils.py b/tools/as_utils.py
index a5bfffe..83d0d15 100644
--- a/tools/as_utils.py
+++ b/tools/as_utils.py
@@ -171,12 +171,13 @@
 def MoveProfileReportTo(dest_dir, build_stdout, quiet=False):
   html_file = None
   profile_message = 'See the profiling report at: '
+  # We are not interested in the profiling report for buildSrc.
   for line in build_stdout:
-    if profile_message in line:
+    if (profile_message in line) and ('buildSrc' not in line):
+      assert not html_file, "Only one report should be created"
       html_file = line[len(profile_message):]
       if html_file.startswith('file://'):
         html_file = html_file[len('file://'):]
-      break
 
   if not html_file:
     return
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index bb6ac32..5596cd5 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -250,7 +250,6 @@
               'module': '',
               'flavor': 'play',
               'main_dex_rules': 'multidex-config.pro',
-              'releaseTarget': 'assemblePlayRelease',
               'signed_apk_name': 'Signal-play-release-4.32.7.apk'
           })
       ]
@@ -679,8 +678,7 @@
     app, repo, options, checkout_dir, temp_dir, shrinker, proguard_config_file):
   recompilation_results = []
 
-  # Build app with gradle using -D...keepRuleSynthesisForRecompilation=
-  # true.
+  # Build app with gradle using -D...keepRuleSynthesisForRecompilation=true.
   out_dir = os.path.join(checkout_dir, 'out', shrinker + '-1')
   (apk_dest, profile_dest_dir, ext_proguard_config_file) = \
       BuildAppWithShrinker(
@@ -1071,10 +1069,10 @@
     else:
       # Make a copy of r8.jar and r8lib.jar such that they stay the same for
       # the entire execution of this script.
-      if 'r8-nolib' in options.shrinker:
+      if 'r8-nolib' in options.shrinker or 'r8-nolib-full' in options.shrinker:
         assert os.path.isfile(utils.R8_JAR), 'Cannot build without r8.jar'
         shutil.copyfile(utils.R8_JAR, os.path.join(temp_dir, 'r8.jar'))
-      if 'r8' in options.shrinker:
+      if 'r8' in options.shrinker or 'r8-full' in options.shrinker:
         assert os.path.isfile(utils.R8LIB_JAR), 'Cannot build without r8lib.jar'
         shutil.copyfile(utils.R8LIB_JAR, os.path.join(temp_dir, 'r8lib.jar'))