Merge "Extend Instruction#canBeDaedCode to receive AppView."
diff --git a/.gitignore b/.gitignore
index c04d9dd..b8248ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -85,6 +85,12 @@
 third_party/ddmlib.tar.gz
 third_party/core-lambda-stubs
 third_party/core-lambda-stubs.tar.gz
+third_party/openjdk/openjdk-9.0.4/linux
+third_party/openjdk/openjdk-9.0.4/linux.tar.gz
+third_party/openjdk/openjdk-9.0.4/osx
+third_party/openjdk/openjdk-9.0.4/osx.tar.gz
+third_party/openjdk/openjdk-9.0.4/windows
+third_party/openjdk/openjdk-9.0.4/windows.tar.gz
 third_party/openjdk/openjdk-rt-1.8
 third_party/openjdk/openjdk-rt-1.8.tar.gz
 third_party/r8
@@ -98,6 +104,7 @@
 *.iml
 r8.ipr
 r8.iws
+local.properties
 #*#
 *~
 .#*
diff --git a/build.gradle b/build.gradle
index 4ce2e18..053bbfb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,12 +1,33 @@
 // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
+
 import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
-import net.ltgt.gradle.errorprone.ErrorProneToolChain
+import net.ltgt.gradle.errorprone.CheckSeverity
 import org.gradle.internal.os.OperatingSystem
 import tasks.GetJarsFromConfiguration
 import utils.Utils
 
+buildscript {
+    repositories {
+        mavenCentral()
+        jcenter()
+        maven {
+            url "https://plugins.gradle.org/m2/"
+        }
+    }
+    dependencies {
+        classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.4'
+    }
+}
+
+plugins {
+  id "net.ltgt.errorprone" version "0.7"
+}
+
+apply plugin: 'java'
+apply plugin: 'idea'
+
 ext {
     androidSupportVersion = '25.4.0'
     asmVersion = '6.2.1'
@@ -20,32 +41,9 @@
     kotlinVersion = '1.3.11'
     kotlinExtMetadataJVMVersion = '0.0.4'
     smaliVersion = '2.2b4'
+    errorproneVersion = '2.3.2'
 }
 
-def errorProneConfiguration = [
-    '-XepDisableAllChecks',
-    // D8 want to use reference equality, thus disable the checker explicitly
-    '-Xep:ReferenceEquality:OFF',
-    '-Xep:ClassCanBeStatic:ERROR',
-    '-Xep:OperatorPrecedence:ERROR',
-    '-Xep:RemoveUnusedImports:ERROR',
-    '-Xep:MissingOverride:ERROR',
-    '-Xep:IntLongMath:ERROR',
-    '-Xep:EqualsHashCode:ERROR',
-    '-Xep:InconsistentOverloads:ERROR',
-    '-Xep:ArrayHashCode:ERROR',
-    '-Xep:EqualsIncompatibleType:ERROR',
-    '-Xep:NonOverridingEquals:ERROR',
-    '-Xep:FallThrough:ERROR',
-    '-Xep:MissingCasesInEnumSwitch:ERROR',
-    '-Xep:MissingDefault:ERROR',
-    '-Xep:MultipleTopLevelClasses:ERROR',
-    '-Xep:NarrowingCompoundAssignment:ERROR',
-    '-Xep:BoxedPrimitiveConstructor:ERROR',
-    '-Xep:LogicalAssignment:ERROR',
-    '-Xep:FloatCast:ERROR',
-    '-Xep:ReturnValueIgnored:ERROR']
-
 apply from: 'copyAdditionalJctfCommonFiles.gradle'
 
 repositories {
@@ -54,32 +52,6 @@
     mavenCentral()
 }
 
-buildscript {
-    repositories {
-        mavenCentral()
-        jcenter()
-        maven {
-            url "https://plugins.gradle.org/m2/"
-        }
-    }
-    dependencies {
-        classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.4'
-        classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
-        classpath "com.gradle:build-scan-plugin:1.14"
-    }
-}
-
-apply plugin: "com.gradle.build-scan"
-
-buildScan {
-    licenseAgreementUrl = 'https://gradle.com/terms-of-service'
-    licenseAgree = 'yes'
-}
-
-apply plugin: 'java'
-apply plugin: 'idea'
-apply plugin: 'net.ltgt.errorprone-base'
-
 if (project.hasProperty('with_code_coverage')) {
     apply plugin: 'jacoco'
 }
@@ -206,10 +178,10 @@
         module {
             if (sources.name == "main") {
                 sourceDirs += sources.java.srcDirs
-                outputDir sources.output.classesDir
+                outputDir sources.output.classesDirs[0]
             } else {
                 testSourceDirs += sources.java.srcDirs
-                testOutputDir sources.output.classesDir
+                testOutputDir sources.output.classesDirs[0]
             }
         }
     }
@@ -262,6 +234,7 @@
     debugTestResourcesKotlinCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
     examplesKotlinCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
     kotlinR8TestResourcesCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
+    errorprone("com.google.errorprone:error_prone_core:$errorproneVersion")
 }
 
 def r8LibPath = "$buildDir/libs/r8lib.jar"
@@ -320,9 +293,41 @@
         ]
 ]
 
+def cloudSystemDependencies = [
+        linux: [
+                "third_party": ["openjdk/openjdk-9.0.4/linux"],
+        ],
+        osx: [
+                "third_party": ["openjdk/openjdk-9.0.4/osx"],
+        ],
+        windows: [
+                "third_party": ["openjdk/openjdk-9.0.4/windows"],
+        ],
+]
+
+if (OperatingSystem.current().isWindows()) {
+    cloudSystemDependencies.windows.each { entry ->
+        cloudDependencies.get(entry.key).addAll(entry.value)
+    }
+} else if (OperatingSystem.current().isLinux()) {
+    cloudSystemDependencies.linux.each { entry ->
+        cloudDependencies.get(entry.key).addAll(entry.value)
+    }
+} else if (OperatingSystem.current().isMacOsX()) {
+    cloudSystemDependencies.osx.each { entry ->
+        cloudDependencies.get(entry.key).addAll(entry.value)
+    }
+} else {
+    println "WARNING: Unsupported system: " + OperatingSystem.current()
+}
+
+def getDownloadDepsTaskName(entryKey, entryFile) {
+    return "download_deps_${entryKey}_${entryFile.replace('/', '_').replace('\\', '_')}"
+}
+
 cloudDependencies.each { entry ->
     entry.value.each { entryFile ->
-        task "download_deps_${entry.key}/${entryFile}"(type: Exec) {
+        task "${getDownloadDepsTaskName(entry.key, entryFile)}"(type: Exec) {
             def outputDir = "${entry.key}/${entryFile}"
             def gzFile = "${outputDir}.tar.gz"
             def sha1File = "${gzFile}.sha1"
@@ -372,7 +377,7 @@
 
 x20Dependencies.each { entry ->
     entry.value.each { entryFile ->
-        task "download_deps_${entry.key}/${entryFile}"(type: Exec) {
+        task "${getDownloadDepsTaskName(entry.key, entryFile)}"(type: Exec) {
             def outputDir = "${entry.key}/${entryFile}"
             def gzFile = "${outputDir}.tar.gz"
             def sha1File = "${gzFile}.sha1"
@@ -388,7 +393,7 @@
     cloudDependencies.each { entry ->
         entry.value.each { entryFile ->
             if (entryFile.contains("proguard")) {
-                dependsOn "download_deps_${entry.key}/${entryFile}"
+                dependsOn "${getDownloadDepsTaskName(entry.key, entryFile)}"
             }
         }
     }
@@ -398,7 +403,7 @@
    cloudDependencies.each { entry ->
         entry.value.each { entryFile ->
             if (entryFile.contains("openjdk-rt")) {
-                dependsOn "download_deps_${entry.key}/${entryFile}"
+                dependsOn "${getDownloadDepsTaskName(entry.key, entryFile)}"
             }
         }
     }
@@ -408,7 +413,7 @@
     cloudDependencies.each { entry ->
         entry.value.each { entryFile ->
             if (entryFile.endsWith("/dx")) {
-                dependsOn "download_deps_${entry.key}/${entryFile}"
+                dependsOn "${getDownloadDepsTaskName(entry.key, entryFile)}"
             }
         }
     }
@@ -418,7 +423,7 @@
     cloudDependencies.each { entry ->
         entry.value.each { entryFile ->
             if (entryFile.contains("android_cts_baseline")) {
-                dependsOn "download_deps_${entry.key}/${entryFile}"
+                dependsOn "${getDownloadDepsTaskName(entry.key, entryFile)}"
             }
         }
     }
@@ -427,13 +432,13 @@
 task downloadDeps {
     cloudDependencies.each { entry ->
         entry.value.each { entryFile ->
-            dependsOn "download_deps_${entry.key}/${entryFile}"
+            dependsOn "${getDownloadDepsTaskName(entry.key, entryFile)}"
         }
     }
     if (!project.hasProperty('no_internal')) {
         x20Dependencies.each { entry ->
             entry.value.each { entryFile ->
-                dependsOn "download_deps_${entry.key}/${entryFile}"
+                dependsOn "${getDownloadDepsTaskName(entry.key, entryFile)}"
             }
         }
     }
@@ -444,22 +449,63 @@
     targetCompatibility = JavaVersion.VERSION_1_8
 }
 
-// Javac often runs out of stack space when compiling the tests.
-// Increase the stack size for the javac process.
-tasks.withType(JavaCompile) {
-    options.fork = true
-    options.forkOptions.jvmArgs = ["-Xss4m"]
+// Check if running with the JDK location from tools/jdk.py.
+if (OperatingSystem.current().isWindows()) {
+    println "NOTE: Running with JDK: " + org.gradle.internal.jvm.Jvm.current().javaHome
+} else {
+    def javaHomeOut = new StringBuilder()
+    def javaHomeErr = new StringBuilder()
+    def javaHomeProc = './tools/jdk.py'.execute()
+    javaHomeProc.waitForProcessOutput(javaHomeOut, javaHomeErr)
+    def jdkHome = new File(javaHomeOut.toString().trim())
+    if (!jdkHome.exists()) {
+        println "WARNING: Failed to find the ./tools/jdk.py specified JDK: " + jdkHome
+    } else if (jdkHome != org.gradle.internal.jvm.Jvm.current().javaHome) {
+        println("WARNING: Gradle is running in a non-pinned Java"
+                + ". Gradle Java Home: " + org.gradle.internal.jvm.Jvm.current().javaHome
+                + ". Expected: " + jdkHome)
+    }
 }
 
-tasks.withType(JavaCompile) {
-    options.compilerArgs << '-Xlint:unchecked'
+sourceSets.configureEach { sourceSet ->
+    tasks.named(sourceSet.compileJavaTaskName).configure {
+        // Default disable errorprone (enabled and setup below).
+        options.errorprone.enabled = false
+        options.compilerArgs << '-Xlint:unchecked'
+        // Javac often runs out of stack space when compiling the tests.
+        // Increase the stack size for the javac process.
+        options.forkOptions.jvmArgs << "-Xss4m"
+        // Set the bootclass path so compilation is consistent with 1.8 target compatibility.
+        options.forkOptions.jvmArgs << "-Xbootclasspath/a:third_party/openjdk/openjdk-rt-1.8/rt.jar"
+    }
 }
 
-if (!project.hasProperty('without_error_prone')) {
+if (!project.hasProperty('without_error_prone') &&
+        // Don't enable error prone on Java 8 as the plugin setup does not support it.
+        !org.gradle.internal.jvm.Jvm.current().javaVersion.java8) {
     compileJava {
         // Enable error prone for D8/R8 sources.
-        toolChain ErrorProneToolChain.create(project)
-        options.compilerArgs += errorProneConfiguration
+        options.errorprone.enabled = true
+        options.errorprone.disableAllChecks = true
+        options.errorprone.check('ClassCanBeStatic', CheckSeverity.ERROR)
+        options.errorprone.check('OperatorPrecedence', CheckSeverity.ERROR)
+        options.errorprone.check('RemoveUnusedImports', CheckSeverity.ERROR)
+        options.errorprone.check('MissingOverride', CheckSeverity.ERROR)
+        options.errorprone.check('IntLongMath', CheckSeverity.ERROR)
+        options.errorprone.check('EqualsHashCode', CheckSeverity.ERROR)
+        options.errorprone.check('InconsistentOverloads', CheckSeverity.ERROR)
+        options.errorprone.check('ArrayHashCode', CheckSeverity.ERROR)
+        options.errorprone.check('EqualsIncompatibleType', CheckSeverity.ERROR)
+        options.errorprone.check('NonOverridingEquals', CheckSeverity.ERROR)
+        options.errorprone.check('FallThrough', CheckSeverity.ERROR)
+        options.errorprone.check('MissingCasesInEnumSwitch', CheckSeverity.ERROR)
+        options.errorprone.check('MissingDefault', CheckSeverity.ERROR)
+        options.errorprone.check('MultipleTopLevelClasses', CheckSeverity.ERROR)
+        options.errorprone.check('NarrowingCompoundAssignment', CheckSeverity.ERROR)
+        options.errorprone.check('BoxedPrimitiveConstructor', CheckSeverity.ERROR)
+        options.errorprone.check('LogicalAssignment', CheckSeverity.ERROR)
+        options.errorprone.check('FloatCast', CheckSeverity.ERROR)
+        options.errorprone.check('ReturnValueIgnored', CheckSeverity.ERROR)
     }
 }
 
@@ -788,8 +834,7 @@
 task createArtTests(type: Exec) {
     def outputDir = "build/generated/test/java/com/android/tools/r8/art"
     def createArtTestsScript = "tools/create_art_tests.py"
-    inputs.file "tests/2017-10-04/art.tar.gz"
-    inputs.file createArtTestsScript
+    inputs.files files("tests/2017-10-04/art.tar.gz", createArtTestsScript)
     outputs.dir outputDir
     dependsOn downloadDeps
     commandLine "python", createArtTestsScript
@@ -864,7 +909,7 @@
         args "--dex"
         args "--output=build/test/${hostDexJar}"
         args "build/test/${hostJar}"
-        inputs.file file("build/test/${hostJar}")
+        inputs.files files("build/test/${hostJar}")
         outputs.file file("build/test/${hostDexJar}")
     }
     dependsOn dex_debuginfo_examples
@@ -1017,8 +1062,9 @@
             def proguardJarPath = "${exampleOutputDir}/${jarName}"
             def proguardMapPath = "${exampleOutputDir}/${name}/${name}.map"
             task "jar_example_${name}"(type: Exec, dependsOn: "pre_proguard_example_${name}") {
-                inputs.files tasks.getByPath("pre_proguard_example_${name}")
-                inputs.file  proguardConfigPath
+                inputs.files files(
+                        tasks.getByPath("pre_proguard_example_${name}"),
+                        proguardConfigPath)
                 // Enable these to get stdout and stderr redirected to files...
                 // standardOutput = new FileOutputStream('proguard.stdout')
                 // errorOutput = new FileOutputStream('proguard.stderr')
@@ -1338,7 +1384,7 @@
         def smaliOutputDir = file("build/test/smali/" + relativeDir);
         smaliOutputDir.mkdirs()
         outputs.dir smaliOutputDir
-        def taskName = "smali_build_${relativeDir.toString().replace('/', '_')}"
+        def taskName = "smali_build_${relativeDir.toString().replace('/', '_').replace('\\', '_')}"
         def smaliFiles = fileTree(dir: dir, include: '*.smali')
         def javaFiles = fileTree(dir: dir, include: '*.java')
         def destDir = smaliOutputDir;
@@ -1410,7 +1456,7 @@
 task buildPreNJdwpTestsDex(type: Exec, dependsOn: "buildPreNJdwpTestsJar") {
     def inFile = buildPreNJdwpTestsJar.archivePath
     def outFile = new File(buildPreNJdwpTestsJar.destinationDir, buildPreNJdwpTestsJar.baseName + '-dex.jar')
-    inputs.file inFile
+    inputs.files files(inFile)
     outputs.file outFile
     if (OperatingSystem.current().isWindows()) {
         executable file("tools/windows/dx/bin/dx.bat")
@@ -1431,7 +1477,7 @@
 task AospJarTest(type: Exec) {
     dependsOn CompatDx, downloadDeps
     def script = "tools/test_aosp_jar.py"
-    inputs.file script
+    inputs.files files(script)
     commandLine "python", script, "--no-build"
     workingDir = projectDir
 }
@@ -1447,7 +1493,7 @@
 
 task configureTestForR8Lib(type: Copy) {
     dependsOn testJar
-    inputs.file "$buildDir/libs/r8tests.jar"
+    inputs.files files("$buildDir/libs/r8tests.jar")
     if (getR8LibTask() != null) {
         dependsOn getR8LibTask()
         delete r8LibTestPath
@@ -1489,6 +1535,9 @@
 }
 
 test {
+    // TODO(b/124091860): Increase the max heap size to avoid OOM when running tests.
+    maxHeapSize = "3g"
+
     if (project.hasProperty('generate_golden_files_to')) {
         systemProperty 'generate_golden_files_to', project.property('generate_golden_files_to')
         assert project.hasProperty('HEAD_sha1')
@@ -1591,7 +1640,7 @@
                 sourceSets.test.runtimeClasspath -
                 sourceSets.main.output -
                 files(['build/classes/test'])
-        testClassesDir = new File(r8LibTestPath)
+        testClassesDirs = files(r8LibTestPath)
     }
     if (OperatingSystem.current().isLinux()
             || OperatingSystem.current().isMacOsX()
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
index 0f172d6..74c5cfd 100644
--- a/buildSrc/build.gradle
+++ b/buildSrc/build.gradle
@@ -12,3 +12,6 @@
     compile group: 'com.google.guava', name: 'guava', version: '19.0'
     compile group: 'org.smali', name: 'smali', version: '2.2b4'
 }
+
+sourceCompatibility = JavaVersion.VERSION_1_8
+targetCompatibility = JavaVersion.VERSION_1_8
diff --git a/buildSrc/src/main/java/dx/DexMerger.java b/buildSrc/src/main/java/dx/DexMerger.java
index d3d8427..6303009 100644
--- a/buildSrc/src/main/java/dx/DexMerger.java
+++ b/buildSrc/src/main/java/dx/DexMerger.java
@@ -8,34 +8,38 @@
 import org.gradle.api.Action;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.UncheckedIOException;
+import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.OutputFile;
 import org.gradle.api.tasks.TaskAction;
 import org.gradle.process.ExecSpec;
 import utils.Utils;
 
 public class DexMerger extends DefaultTask {
 
-  private FileTree source;
+  private FileCollection source;
   private File destination;
   private File dexMergerExecutable;
   private boolean debug;
 
-  public FileTree getSource() {
+  @InputFiles
+  public FileCollection getSource() {
     return source;
   }
 
-  public void setSource(FileTree source) {
+  public void setSource(FileCollection source) {
     this.source = source;
-    getInputs().file(source);
   }
 
+  @OutputFile
   public File getDestination() {
     return destination;
   }
 
   public void setDestination(File destination) {
     this.destination = destination;
-    getOutputs().file(destination);
   }
 
   public File getDexMergerExecutable() {
diff --git a/buildSrc/src/main/java/dx/Dx.java b/buildSrc/src/main/java/dx/Dx.java
index 86d1bce..b03ad0f 100644
--- a/buildSrc/src/main/java/dx/Dx.java
+++ b/buildSrc/src/main/java/dx/Dx.java
@@ -8,37 +8,38 @@
 import org.gradle.api.Action;
 import org.gradle.api.DefaultTask;
 import org.gradle.api.UncheckedIOException;
+import org.gradle.api.file.FileCollection;
 import org.gradle.api.file.FileTree;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.OutputFile;
 import org.gradle.api.tasks.TaskAction;
 import org.gradle.process.ExecSpec;
 import utils.Utils;
 
 public class Dx extends DefaultTask {
 
-  private FileTree source;
+  private FileCollection source;
   private File destination;
   private File dxExecutable;
   private boolean debug;
 
-  public FileTree getSource() {
+  @InputFiles
+  public FileCollection getSource() {
     return source;
   }
 
-  public void setSource(FileTree source) {
+  public void setSource(FileCollection source) {
     this.source = source;
-    getInputs().file(source);
   }
 
+  @OutputDirectory
   public File getDestination() {
     return destination;
   }
 
   public void setDestination(File destination) {
     this.destination = destination;
-    File classesFile = destination.toPath().resolve("classes.dex").toFile();
-    // The output from running DX is classes.dex in the destination directory.
-    // TODO(sgjesse): Handle multidex?
-    getOutputs().file(classesFile);
   }
 
   public File getDxExecutable() {
diff --git a/buildSrc/src/main/java/smali/Smali.java b/buildSrc/src/main/java/smali/Smali.java
index bd217d7..7a5241e 100644
--- a/buildSrc/src/main/java/smali/Smali.java
+++ b/buildSrc/src/main/java/smali/Smali.java
@@ -10,6 +10,9 @@
 import org.gradle.api.DefaultTask;
 import org.gradle.api.UncheckedIOException;
 import org.gradle.api.file.FileTree;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.OutputFile;
 import org.gradle.api.tasks.TaskAction;
 
 public class Smali extends DefaultTask {
@@ -18,22 +21,22 @@
   private File destination;
   private File smaliScript;
 
+  @InputFiles
   public FileTree getSource() {
     return source;
   }
 
   public void setSource(FileTree source) {
     this.source = source;
-    getInputs().file(source);
   }
 
+  @OutputFile
   public File getDestination() {
     return destination;
   }
 
   public void setDestination(File destination) {
     this.destination = destination;
-    getOutputs().file(destination);
   }
 
   public File getSmaliScript() {
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..9924d92
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,6 @@
+// Copyright (c) 2019, 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.
+
+rootProject.name = 'r8'
+
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 3ce1467..26264a5 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -129,12 +129,8 @@
       GraphLense graphLense,
       InternalOptions options,
       Origin origin) {
-    assert getOwner() == encodedMethod;
-    triggerDelayedParsingIfNeccessary();
-    return options.debug || encodedMethod.getOptimizationInfo().isReachabilitySensitive()
-        ? internalBuildWithLocals(
-            encodedMethod, encodedMethod, appInfo, graphLense, options, null, null)
-        : internalBuild(encodedMethod, encodedMethod, appInfo, graphLense, options, null, null);
+    return internalBuildPossiblyWithLocals(
+        encodedMethod, encodedMethod, appInfo, graphLense, options, null, null);
   }
 
   @Override
@@ -147,14 +143,30 @@
       ValueNumberGenerator generator,
       Position callerPosition,
       Origin origin) {
-    assert getOwner() == encodedMethod;
     assert generator != null;
+    return internalBuildPossiblyWithLocals(
+        context, encodedMethod, appInfo, graphLense, options, generator, callerPosition);
+  }
+
+  private IRCode internalBuildPossiblyWithLocals(
+      DexEncodedMethod context,
+      DexEncodedMethod encodedMethod,
+      AppInfo appInfo,
+      GraphLense graphLense,
+      InternalOptions options,
+      ValueNumberGenerator generator,
+      Position callerPosition) {
+    assert getOwner() == encodedMethod;
     triggerDelayedParsingIfNeccessary();
-    return options.debug || encodedMethod.getOptimizationInfo().isReachabilitySensitive()
-        ? internalBuildWithLocals(
-            context, encodedMethod, appInfo, graphLense, options, generator, callerPosition)
-        : internalBuild(
-            context, encodedMethod, appInfo, graphLense, options, generator, callerPosition);
+    if (!keepLocals(encodedMethod, options)) {
+      // We strip locals here because we will not be able to recover from invalid info.
+      node.localVariables.clear();
+      return internalBuild(
+          context, encodedMethod, appInfo, graphLense, options, generator, callerPosition);
+    } else {
+      return internalBuildWithLocals(
+          context, encodedMethod, appInfo, graphLense, options, generator, callerPosition);
+    }
   }
 
   private IRCode internalBuildWithLocals(
@@ -180,14 +192,10 @@
     if (options.testing.noLocalsTableOnInput) {
       return false;
     }
-    if (options.debug) {
+    if (options.debug || encodedMethod.getOptimizationInfo().isReachabilitySensitive()) {
       return true;
     }
-    if (options.getProguardConfiguration() != null
-        && options.getProguardConfiguration().getKeepAttributes().localVariableTable) {
-      return true;
-    }
-    return encodedMethod.getOptimizationInfo().isReachabilitySensitive();
+    return false;
   }
 
   private IRCode internalBuild(
@@ -198,9 +206,7 @@
       InternalOptions options,
       ValueNumberGenerator generator,
       Position callerPosition) {
-    if (!keepLocals(encodedMethod, options)) {
-      node.localVariables.clear();
-    }
+    assert node.localVariables.isEmpty() || keepLocals(encodedMethod, options);
     JarSourceCode source =
         new JarSourceCode(
             method.getHolder(),
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 709d27a..e3a8273 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -1635,6 +1635,7 @@
           ProguardTypeMatcher.create(parseClassName(), ClassOrType.CLASS, dexItemFactory));
       skipWhitespace();
       while (acceptChar(',')) {
+        skipWhitespace();
         negated = acceptChar('!');
         builder.addClassName(negated,
             ProguardTypeMatcher.create(parseClassName(), ClassOrType.CLASS, dexItemFactory));
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index e4a809c..a12ba39 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -778,6 +778,10 @@
     return getPlatform().startsWith("Windows");
   }
 
+  public static boolean isJava8Runtime() {
+    return System.getProperty("java.specification.version").equals("8");
+  }
+
   public static boolean isJava9Runtime() {
     return System.getProperty("java.specification.version").equals("9");
   }
@@ -804,7 +808,7 @@
   }
 
   public static Path getClassPathForTests() {
-    return Paths.get(BUILD_DIR, "classes", "test");
+    return Paths.get(BUILD_DIR, "classes", "java", "test");
   }
 
   private static List<String> getNamePartsForTestPackage(Package pkg) {
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index fb61f45..1c0287e 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
@@ -50,6 +51,7 @@
 import org.apache.harmony.jpda.tests.framework.jdwp.EventPacket;
 import org.apache.harmony.jpda.tests.framework.jdwp.Frame.Variable;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.ObjectReferenceCommandSet;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.ReferenceTypeCommandSet;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.StackFrameCommandSet;
 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
@@ -153,6 +155,12 @@
   }
 
   protected final void runDebugTest(
+      DebugTestConfig config, Class<?> debuggeeClass, JUnit3Wrapper.Command... commands)
+      throws Throwable {
+    runInternal(config, debuggeeClass.getTypeName(), Arrays.asList(commands));
+  }
+
+  protected final void runDebugTest(
       DebugTestConfig config, String debuggeeClass, JUnit3Wrapper.Command... commands)
       throws Throwable {
     runInternal(config, debuggeeClass, Arrays.asList(commands));
@@ -324,6 +332,10 @@
     return new JUnit3Wrapper.Command.RunCommand();
   }
 
+  protected final JUnit3Wrapper.Command breakpoint(MethodReference method) {
+    return breakpoint(method.getHolderClass().getTypeName(), method.getMethodName());
+  }
+
   protected final JUnit3Wrapper.Command breakpoint(String className, String methodName) {
     return breakpoint(className, methodName, null);
   }
@@ -477,6 +489,10 @@
     });
   }
 
+  protected final JUnit3Wrapper.Command checkLine(int line) {
+    return inspect(t -> t.checkLine(null, line));
+  }
+
   protected final JUnit3Wrapper.Command checkLine(String sourceFile, int line) {
     return inspect(t -> t.checkLine(sourceFile, line));
   }
@@ -532,6 +548,16 @@
     });
   }
 
+  protected final JUnit3Wrapper.Command checkFieldOnThis(
+      String fieldName, String fieldSignature, Value expectedValue) {
+    return inspect(
+        t -> {
+          Value value = t.getFieldOnThis(fieldName, fieldSignature);
+          Assert.assertEquals(
+              "Incorrect value for field 'this." + fieldName + "'", expectedValue, value);
+        });
+  }
+
   protected final JUnit3Wrapper.Command inspect(Consumer<JUnit3Wrapper.DebuggeeState> inspector) {
     return t -> inspector.accept(t.debuggeeState);
   }
@@ -1298,6 +1324,7 @@
 
         @Override
         public void checkLine(String sourceFile, int line) {
+          sourceFile = sourceFile != null ? sourceFile : getSourceFile();
           if (!Objects.equals(sourceFile, getSourceFile()) || line != getLineNumber()) {
             String locationString = convertCurrentLocationToString();
             Assert.fail(
@@ -1501,7 +1528,15 @@
 
         // The class is available, lookup and read the field.
         long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
-        return getField(getMirror(), classId, fieldId);
+        return internalStaticField(getMirror(), classId, fieldId);
+      }
+
+      public Value getFieldOnThis(String fieldName, String fieldSignature) {
+        long thisObjectId = getMirror().getThisObject(getThreadId(), getFrameId());
+        long classId = getMirror().getReferenceType(thisObjectId);
+        // TODO(zerny): Search supers too. This will only get the field if directly on the class.
+        long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
+        return internalInstanceField(getMirror(), thisObjectId, fieldId);
       }
 
       private long findField(VmMirror mirror, long classId, String fieldName,
@@ -1547,10 +1582,10 @@
         return matchingFieldIds.getLong(0);
       }
 
-      private Value getField(VmMirror mirror, long classId, long fieldId) {
-
-        CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
-            ReferenceTypeCommandSet.GetValuesCommand);
+      private static Value internalStaticField(VmMirror mirror, long classId, long fieldId) {
+        CommandPacket commandPacket =
+            new CommandPacket(
+                ReferenceTypeCommandSet.CommandSetID, ReferenceTypeCommandSet.GetValuesCommand);
         commandPacket.setNextValueAsReferenceTypeID(classId);
         commandPacket.setNextValueAsInt(1);
         commandPacket.setNextValueAsFieldID(fieldId);
@@ -1565,6 +1600,23 @@
       }
     }
 
+    private static Value internalInstanceField(VmMirror mirror, long objectId, long fieldId) {
+      CommandPacket commandPacket =
+          new CommandPacket(
+              ObjectReferenceCommandSet.CommandSetID, ObjectReferenceCommandSet.GetValuesCommand);
+      commandPacket.setNextValueAsObjectID(objectId);
+      commandPacket.setNextValueAsInt(1);
+      commandPacket.setNextValueAsFieldID(fieldId);
+      ReplyPacket replyPacket = mirror.performCommand(commandPacket);
+      assert replyPacket.getErrorCode() == Error.NONE;
+
+      int fieldsCount = replyPacket.getNextValueAsInt();
+      assert fieldsCount == 1;
+      Value result = replyPacket.getNextValueAsValue();
+      Assert.assertTrue(replyPacket.isAllDataRead());
+      return result;
+    }
+
     public static Optional<Variable> getVariableAt(VmMirror mirror, Location location,
         String localName) {
       return getVariablesAt(mirror, location).stream()
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java
new file mode 100644
index 0000000..9991b57
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+public class LambdaOuterContextTest {
+
+  public interface Converter {
+    String convert(int value);
+  }
+
+  public int outer;
+
+  public LambdaOuterContextTest(int outer) {
+    this.outer = outer;
+  }
+
+  public void foo(Converter converter) {
+    System.out.println(converter.convert(outer));
+  }
+
+  public void bar(int arg) {
+    foo(value -> {
+      // Ensure lambda uses parts of the outer context, otherwise javac will optimize it out.
+      return Integer.toString(outer + value + arg);
+    });
+  }
+
+  public static void main(String[] args) {
+    new LambdaOuterContextTest(args.length + 42).bar(args.length);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
new file mode 100644
index 0000000..8af2a6e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.JvmTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.debug.LambdaOuterContextTest.Converter;
+import com.android.tools.r8.utils.StringUtils;
+import org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class LambdaOuterContextTestRunner extends DebugTestBase {
+
+  public static final Class<?> CLASS = LambdaOuterContextTest.class;
+  public static final String EXPECTED = StringUtils.lines("84");
+
+  @Test
+  public void testJvm() throws Throwable {
+    JvmTestBuilder jvmTestBuilder = testForJvm().addTestClasspath();
+    jvmTestBuilder.run(CLASS).assertSuccessWithOutput(EXPECTED);
+    runDebugger(jvmTestBuilder.debugConfig());
+  }
+
+  @Test
+  @Ignore("b/123068053")
+  public void testD8() throws Throwable {
+    D8TestCompileResult compileResult =
+        testForD8().addProgramClassesAndInnerClasses(CLASS).compile();
+    compileResult.run(CLASS).assertSuccessWithOutput(EXPECTED);
+    runDebugger(compileResult.debugConfig());
+  }
+
+  @Test
+  public void testR8Cf() throws Throwable {
+    R8TestCompileResult compileResult =
+        testForR8(Backend.CF)
+            .addProgramClassesAndInnerClasses(CLASS)
+            .debug()
+            .noMinification()
+            .noTreeShaking()
+            .compile();
+    compileResult.run(CLASS).assertSuccessWithOutput(EXPECTED);
+    runDebugger(compileResult.debugConfig());
+  }
+
+  private void runDebugger(DebugTestConfig config) throws Throwable {
+    runDebugTest(
+        config,
+        CLASS,
+        breakpoint(methodFromMethod(CLASS.getMethod("foo", Converter.class))),
+        run(),
+        checkLine(19),
+        checkLocals("this", "converter"),
+        checkFieldOnThis("outer", null, Value.createInt(42)),
+        stepInto(INTELLIJ_FILTER),
+        checkLine(25),
+        checkLocals("this", "value", "arg"),
+        checkNoLocal("outer"),
+        checkFieldOnThis("outer", null, Value.createInt(42)),
+        run());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaTest.java b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
index 1b413ba..f9e6c34 100644
--- a/src/test/java/com/android/tools/r8/debug/LambdaTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
@@ -13,7 +13,6 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
-// TODO(shertz) test local variables
 @RunWith(Parameterized.class)
 public class LambdaTest extends DebugTestBase {
 
@@ -47,8 +46,11 @@
         run(),
         checkMethod(debuggeeClass, initialMethodName),
         checkLine(SOURCE_FILE, 12),
+        checkLocals("i"),
+        checkNoLocal("j"),
         stepInto(INTELLIJ_FILTER),
         checkLine(SOURCE_FILE, 16),
+        checkLocals("i", "j"),
         run());
   }
 
@@ -63,8 +65,10 @@
         run(),
         checkMethod(debuggeeClass, initialMethodName),
         checkLine(SOURCE_FILE, 32),
+        checkLocals("i", "a", "b"),
         stepInto(INTELLIJ_FILTER),
         checkLine(SOURCE_FILE, 37),
+        checkLocals("a", "b"),
         run());
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
index e4ef08e..2a28a71 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
@@ -234,8 +234,8 @@
         .addOptionsModification(this::configure)
         .run(MAIN);
     if (enableMinification) {
-      if (backend == Backend.CF) {
-        // TODO(b/120639028): Incorrect inner-class structure fails on JVM.
+      if (backend == Backend.CF && ToolHelper.isJava8Runtime()) {
+        // TODO(b/120639028): Incorrect inner-class structure fails on JVM prior to JDK 9.
         result.assertFailureWithErrorThatMatches(containsString("Malformed class name"));
         return;
       } else {
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
index 0a71fe6..167d953 100644
--- 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
@@ -171,7 +171,7 @@
         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) {
+        } else if (backend == Backend.CF && ToolHelper.isJava8Runtime()) {
           // $$ as separator and InnerClass as name, results in $InnerClass from getSimpleName...
           String expectedWithDollarOnInnerName =
               getExpectedNonMinified("$" + config.getInnerClassName());
@@ -182,20 +182,21 @@
           r8RunResult.assertSuccessWithOutput(
               minify ? getExpectedMinified(inspector) : expectedWithDollarOnInnerName);
         } else {
-          // $$ in DEX will not change the InnerName/getSimpleName.
+          // $$ in DEX or JDK 9+ 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) {
+        if (backend == Backend.CF && ToolHelper.isJava8Runtime()) {
           // 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.
+          // NOTE: Behavior changed in JDK 9 so the class is no longer considered malformed.
           r8RunResult.assertFailureWithErrorThatMatches(containsString("Malformed class name"));
         } else {
-          assert backend == Backend.DEX;
+          assert backend == Backend.DEX || !ToolHelper.isJava8Runtime();
           r8RunResult.assertSuccessWithOutput(getExpectedMinified(inspector));
         }
         break;
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 26e5356..f3aba75 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -2399,4 +2399,22 @@
       assertThat(result.stderr, containsString(expectedMessage));
     }
   }
+
+  @Test
+  public void b124181032() throws Exception {
+    ProguardConfigurationParser parser;
+    parser = new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(
+        createConfigurationForTesting(
+            ImmutableList.of(
+                "-keepclassmembers class a.b.c.**, !**Client, !**Interceptor {",
+                "<fields>;",
+                "<init>();",
+                "}")));
+    List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
+    assertEquals(1, rules.size());
+    ProguardConfigurationRule rule = rules.get(0);
+    assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS.toString(), rule.typeString());
+    assertEquals("a.b.c.**,!**Client,!**Interceptor", rule.getClassNames().toString());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTest.java b/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTest.java
new file mode 100644
index 0000000..6a03a43
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTest.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import java.io.IOException;
+import org.junit.Ignore;
+import org.junit.Test;
+
+class UnusedTypeInThrowing {
+
+  public static void main(String[] args) throws UnusedTypeInThrowingThrowable {
+    System.out.print("42");
+  }
+}
+
+class UnusedTypeInThrowingThrowable extends Throwable {}
+
+public class UnusedTypeInThrowingTest extends TestBase {
+
+  static final Class THROWABLE_CLASS = UnusedTypeInThrowingThrowable.class;
+  static final Class MAIN_CLASS = UnusedTypeInThrowing.class;
+
+  @Test
+  @Ignore("b/124019003")
+  public void testTypeIsMarkedAsLive() throws IOException, CompilationFailedException {
+    testForR8(Backend.CF)
+        .addProgramClasses(MAIN_CLASS)
+        .addProgramClasses(THROWABLE_CLASS)
+        .addKeepMainRule(MAIN_CLASS)
+        .addKeepRules("-keepattributes Exceptions")
+        .run(MAIN_CLASS)
+        .assertSuccessWithOutput("42");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTestRunner.java b/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTestRunner.java
new file mode 100644
index 0000000..2bf42a1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/UnusedTypeInThrowingTestRunner.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.TestBase;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class UnusedTypeInThrowingTestRunner extends TestBase {
+
+  static final Class THROWABLE_CLASS = UnusedTypeInThrowingThrowable.class;
+  static final Class MAIN_CLASS = UnusedTypeInThrowingTest.class;
+
+  @Test
+  @Ignore("b/124019003")
+  public void testTypeIsMarkedAsLive() throws IOException, CompilationFailedException {
+    Path outDex = temp.newFile("out.zip").toPath();
+    testForR8(Backend.CF)
+        .addProgramClasses(MAIN_CLASS)
+        .addProgramClasses(THROWABLE_CLASS)
+        .addKeepMainRule(MAIN_CLASS)
+        .addKeepRules(ImmutableList.of("-keepattributes Exceptions"))
+        .setMode(CompilationMode.RELEASE)
+        .enableInliningAnnotations()
+        .minification(true)
+        .compile()
+        .run(MAIN_CLASS)
+        .assertSuccessWithOutput("42");
+  }
+}
diff --git a/third_party/gradle/gradle.tar.gz.sha1 b/third_party/gradle/gradle.tar.gz.sha1
index c9f900a..a219d61 100644
--- a/third_party/gradle/gradle.tar.gz.sha1
+++ b/third_party/gradle/gradle.tar.gz.sha1
@@ -1 +1 @@
-2d9bb7b50771ad8252cdcf83a1a9648af7eee837
\ No newline at end of file
+4a07bcbff312d890bc1b96c39aaffdcab882cd3a
\ No newline at end of file
diff --git a/third_party/openjdk/jdk9.tar.gz.sha1 b/third_party/openjdk/jdk9.tar.gz.sha1
new file mode 100644
index 0000000..fe944e4
--- /dev/null
+++ b/third_party/openjdk/jdk9.tar.gz.sha1
@@ -0,0 +1 @@
+b7fd72327317977e4221244115bdbbac4edd3444
\ No newline at end of file
diff --git a/third_party/openjdk/openjdk-9.0.4/linux.tar.gz.sha1 b/third_party/openjdk/openjdk-9.0.4/linux.tar.gz.sha1
new file mode 100644
index 0000000..4670484
--- /dev/null
+++ b/third_party/openjdk/openjdk-9.0.4/linux.tar.gz.sha1
@@ -0,0 +1 @@
+ed9e71246f8bba2d7ec0c4d56813c241d8315960
\ No newline at end of file
diff --git a/third_party/openjdk/openjdk-9.0.4/osx.tar.gz.sha1 b/third_party/openjdk/openjdk-9.0.4/osx.tar.gz.sha1
new file mode 100644
index 0000000..7db6817
--- /dev/null
+++ b/third_party/openjdk/openjdk-9.0.4/osx.tar.gz.sha1
@@ -0,0 +1 @@
+a68b718365f7ce214eec2f6bb89311885940b10e
\ No newline at end of file
diff --git a/third_party/openjdk/openjdk-9.0.4/windows.tar.gz.sha1 b/third_party/openjdk/openjdk-9.0.4/windows.tar.gz.sha1
new file mode 100644
index 0000000..dedab10
--- /dev/null
+++ b/third_party/openjdk/openjdk-9.0.4/windows.tar.gz.sha1
@@ -0,0 +1 @@
+d9d352dfa1484bc1b7eaff0e013f720a120ff963
\ No newline at end of file
diff --git a/tools/gradle.py b/tools/gradle.py
index ff90779..374f885 100755
--- a/tools/gradle.py
+++ b/tools/gradle.py
@@ -7,9 +7,12 @@
 # Will make sure we pulled down gradle before running, and will use the pulled
 # down version to have a consistent developer experience.
 
+import optparse
 import os
 import subprocess
 import sys
+
+import jdk
 import utils
 
 GRADLE_DIR = os.path.join(utils.REPO_ROOT, 'third_party', 'gradle')
@@ -21,6 +24,15 @@
 else:
   GRADLE = os.path.join(GRADLE_DIR, 'gradle', 'bin', 'gradle')
 
+def ParseOptions():
+  result = optparse.OptionParser()
+  result.add_option('--java-home', '--java_home',
+      help='Use a custom java version to run gradle.')
+  return result.parse_args()
+
+def GetJavaEnv(env):
+  return dict(env if env else os.environ, JAVA_HOME = jdk.GetJdkHome())
+
 def PrintCmd(s):
   if type(s) is list:
     s = ' '.join(s)
@@ -29,17 +41,12 @@
   sys.stdout.flush()
 
 def EnsureGradle():
-  if not os.path.exists(GRADLE) or os.path.getmtime(GRADLE_TGZ) < os.path.getmtime(GRADLE_SHA1):
-    # Bootstrap or update gradle, everything else is controlled using gradle.
-    utils.DownloadFromGoogleCloudStorage(GRADLE_SHA1)
-    # Update the mtime of the tar file to make sure we do not run again unless
-    # there is an update.
-    os.utime(GRADLE_TGZ, None)
-  else:
-    print 'gradle.py: Gradle binary present'
+  utils.EnsureDepFromGoogleCloudStorage(
+    GRADLE, GRADLE_TGZ, GRADLE_SHA1, 'Gradle binary')
 
 def EnsureDeps():
   EnsureGradle()
+  jdk.EnsureJdk()
 
 def RunGradleIn(gradleCmd, args, cwd, throw_on_failure=True, env=None):
   EnsureDeps()
@@ -47,7 +54,7 @@
   cmd.extend(args)
   utils.PrintCmd(cmd)
   with utils.ChangedWorkingDirectory(cwd):
-    return_value = subprocess.call(cmd, env=env)
+    return_value = subprocess.call(cmd, env=GetJavaEnv(env))
     if throw_on_failure and return_value != 0:
       raise Exception('Failed to execute gradle')
     return return_value
@@ -69,7 +76,7 @@
   cmd.extend(args)
   utils.PrintCmd(cmd)
   with utils.ChangedWorkingDirectory(cwd):
-    return subprocess.check_output(cmd, env=env)
+    return subprocess.check_output(cmd, env=GetJavaEnv(env))
 
 def RunGradleWrapperInGetOutput(args, cwd, env=None):
   return RunGradleInGetOutput('./gradlew', args, cwd, env=env)
@@ -78,7 +85,11 @@
   return RunGradleInGetOutput(GRADLE, args, utils.REPO_ROOT, env=env)
 
 def Main():
-  RunGradle(sys.argv[1:])
+  (options, args) = ParseOptions()
+  gradle_args = sys.argv[1:]
+  if options.java_home:
+    gradle_args.append('-Dorg.gradle.java.home=' + options.java_home)
+  return RunGradle(gradle_args)
 
 if __name__ == '__main__':
   sys.exit(Main())
diff --git a/tools/java.py b/tools/java.py
new file mode 100755
index 0000000..3abfcdd
--- /dev/null
+++ b/tools/java.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+# Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+import jdk
+import utils
+import subprocess
+import sys
+
+def run(args):
+  cmd = [jdk.GetJavaExecutable()] + args
+  utils.PrintCmd(cmd)
+  result = subprocess.check_output(cmd)
+  print result
+  return result
+
+def main():
+  try:
+    run(sys.argv[1:])
+  except subprocess.CalledProcessError as e:
+    # In case anything relevant was printed to stdout, normally this is already
+    # on stderr.
+    print e.output
+    return e.returncode
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/tools/javac.py b/tools/javac.py
new file mode 100755
index 0000000..0d8ac05
--- /dev/null
+++ b/tools/javac.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+# Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+import jdk
+import utils
+import subprocess
+import sys
+
+def run(args):
+  cmd = [jdk.GetJavacExecutable()] + args
+  utils.PrintCmd(cmd)
+  result = subprocess.check_output(cmd)
+  print result
+  return result
+
+def main():
+  try:
+    run(sys.argv[1:])
+  except subprocess.CalledProcessError as e:
+    # In case anything relevant was printed to stdout, normally this is already
+    # on stderr.
+    print e.output
+    return e.returncode
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/tools/jdk.py b/tools/jdk.py
new file mode 100755
index 0000000..5a20901
--- /dev/null
+++ b/tools/jdk.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+# Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+import os
+import sys
+import utils
+
+JDK_DIR = os.path.join(utils.REPO_ROOT, 'third_party', 'openjdk')
+
+def GetJdkHome():
+  root = os.path.join(JDK_DIR, 'openjdk-9.0.4')
+  if utils.IsLinux():
+    return os.path.join(root, 'linux')
+  elif utils.IsOsX():
+    return os.path.join(root, 'osx')
+  elif utils.IsWindows():
+    return os.path.join(root, 'windows')
+  else:
+    return os.environ['JAVA_HOME']
+  return jdkHome
+
+def GetJavaExecutable(jdkHome=None):
+  jdkHome = jdkHome if jdkHome else GetJdkHome()
+  executable = 'java.exe' if utils.IsWindows() else 'java'
+  return os.path.join(jdkHome, 'bin', executable) if jdkHome else executable
+
+def GetJavacExecutable(jdkHome=None):
+  jdkHome = jdkHome if jdkHome else GetJdkHome()
+  executable = 'javac.exe' if utils.IsWindows() else 'javac'
+  return os.path.join(jdkHome, 'bin', executable) if jdkHome else executable
+
+def EnsureJdk():
+  jdkHome = GetJdkHome()
+  jdkTgz = jdkHome + '.tar.gz'
+  jdkSha1 = jdkTgz + '.sha1'
+  utils.EnsureDepFromGoogleCloudStorage(jdkHome, jdkTgz, jdkSha1, 'JDK')
+
+def Main():
+  print GetJdkHome()
+
+if __name__ == '__main__':
+  sys.exit(Main())
diff --git a/tools/test.py b/tools/test.py
index bb6a98a..4ce6453 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -7,16 +7,17 @@
 # if an argument is given, run only tests with that pattern. This script will
 # force the tests to run, even if no input changed.
 
-import os
-import gradle
 import optparse
+import os
 import subprocess
 import sys
 import thread
 import time
-import utils
 import uuid
+
+import gradle
 import notify
+import utils
 
 ALL_ART_VMS = [
     "default",
@@ -96,6 +97,8 @@
           ' Note that the directory will not be cleared before the test.')
   result.add_option('--java-home', '--java_home',
       help='Use a custom java version to run tests.')
+  result.add_option('--java-max-memory-size', '--java_max_memory_size',
+      help='Use a custom max memory size for the gradle java instance, eg, 4g')
   result.add_option('--generate-golden-files-to', '--generate_golden_files_to',
       help='Store dex files produced by tests in the specified directory.'
            ' It is aimed to be read on platforms with no host runtime available'
@@ -121,8 +124,6 @@
 def Main():
   (options, args) = ParseOptions()
   if utils.is_bot():
-    print "Result of 'java -version':"
-    print subprocess.check_output(['java', '-version'])
     gradle.RunGradle(['clean'])
 
   gradle_args = ['--stacktrace']
@@ -166,6 +167,8 @@
       os.makedirs(options.test_dir)
   if options.java_home:
     gradle_args.append('-Dorg.gradle.java.home=' + options.java_home)
+  if options.java_max_memory_size:
+    gradle_args.append('-Dorg.gradle.jvmargs=-Xmx' + options.java_max_memory_size)
   if options.generate_golden_files_to:
     gradle_args.append('-Pgenerate_golden_files_to=' + options.generate_golden_files_to)
     if not os.path.exists(options.generate_golden_files_to):
diff --git a/tools/utils.py b/tools/utils.py
index 08faec6..20d7bc7 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -156,7 +156,22 @@
       return stdout
 
 def IsWindows():
-  return os.name == 'nt'
+  return sys.platform.startswith('win')
+
+def IsLinux():
+  return sys.platform.startswith('linux')
+
+def IsOsX():
+  return sys.platform.startswith('darwin')
+
+def EnsureDepFromGoogleCloudStorage(dep, tgz, sha1, msg):
+  if not os.path.exists(dep) or os.path.getmtime(tgz) < os.path.getmtime(sha1):
+    DownloadFromGoogleCloudStorage(sha1)
+    # Update the mtime of the tar file to make sure we do not run again unless
+    # there is an update.
+    os.utime(tgz, None)
+  else:
+    print 'Ensure cloud dependency:', msg, 'present'
 
 def DownloadFromX20(sha1_file):
   download_script = os.path.join(REPO_ROOT, 'tools', 'download_from_x20.py')