Merge "Add indexOf and lastIndexOf to compile-time string computation"
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/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 010a851..95bfbc0 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.5.4-dev";
+  public static final String LABEL = "1.5.5-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 9cdede4..936f967 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
@@ -166,6 +167,13 @@
   }
 
   public boolean isStrictSubtypeOf(DexType other, AppInfo appInfo) {
+    // For all erroneous cases, saying `no`---not a strict subtype---is conservative.
+    return isStrictSubtypeOf(other, appInfo, false);
+  }
+
+  // Depending on optimizations, conservative answer of subtype relation may vary.
+  // Pass different `orElse` in that case.
+  public boolean isStrictSubtypeOf(DexType other, AppInfo appInfo, boolean orElse) {
     if (this == other) {
       return false;
     }
@@ -184,7 +192,7 @@
       return other.directSubtypes.stream().anyMatch(subtype -> this.isSubtypeOf(subtype,
           appInfo));
     }
-    return isSubtypeOfClass(other, appInfo);
+    return isSubtypeOfClass(other, appInfo, orElse);
   }
 
   private boolean isInterfaceSubtypeOf(DexType candidate, DexType other, AppInfo appInfo) {
@@ -204,15 +212,22 @@
     return false;
   }
 
-  private boolean isSubtypeOfClass(DexType other, AppInfo appInfo) {
+  private boolean isSubtypeOfClass(DexType other, AppInfo appInfo, boolean orElse) {
     DexType self = this;
     if (other.hierarchyLevel == UNKNOWN_LEVEL) {
-      // We have no definition for this class, hence it is not part of the
-      // hierarchy.
-      return false;
+      // We have no definition for this class, hence it is not part of the hierarchy.
+      return orElse;
     }
     while (other.hierarchyLevel < self.hierarchyLevel) {
       DexClass holder = appInfo.definitionFor(self);
+      // TODO(b/113374256): even synthesized class should be available ATM.
+      if (holder == null) {
+        assert self.isD8R8SynthesizedClassType();
+        if (Log.ENABLED) {
+          Log.debug(getClass(), "%s is not in AppInfo yet.", self.toSourceString());
+        }
+        return orElse;
+      }
       assert holder != null && !holder.isInterface();
       self = holder.superType;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index bac74f6..5fd473f 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -724,6 +724,10 @@
       if (originalMethods.contains(targetMethod)) {
         return true;
       }
+      // Stop traversing upwards if we reach the Object.
+      if (holder == dexItemFactory.objectType) {
+        continue;
+      }
       DexClass clazz = originalApplication.definitionFor(holder);
       if (clazz != null) {
         worklist.add(clazz.superType);
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/ir/code/AlwaysMaterializingDefinition.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
index 21bc624..1d42746 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingDefinition.java
@@ -9,8 +9,10 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 
 public class AlwaysMaterializingDefinition extends ConstInstruction {
 
@@ -19,7 +21,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, IRCode code) {
     // This instruction may never be considered dead as it must remain.
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
index 197afaa..37bbd07 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingNop.java
@@ -7,11 +7,13 @@
 import com.android.tools.r8.cf.code.CfNop;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 
 public class AlwaysMaterializingNop extends Instruction {
 
@@ -20,7 +22,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, IRCode code) {
     return false;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
index b5cd950..ecb7a81 100644
--- a/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
+++ b/src/main/java/com/android/tools/r8/ir/code/AlwaysMaterializingUser.java
@@ -6,11 +6,13 @@
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 
 public class AlwaysMaterializingUser extends Instruction {
 
@@ -19,7 +21,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, IRCode code) {
     // This instruction may never be considered dead as it must remain.
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index 1bed0cc..4ae0c3e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -8,12 +8,14 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 
 /**
  * Argument pseudo instruction used to introduce values for all arguments for SSA conversion.
@@ -26,7 +28,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appview, AppInfo appInfo, IRCode code) {
     // Never remove argument instructions. That would change the signature of the method.
     // TODO(b/65810338): If we can tell that a method never uses an argument we might be able to
     // rewrite the signature and call-sites.
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 7263948..34a0041 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -23,6 +24,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.Arrays;
 
 public class ArrayPut extends Instruction implements ImpreciseMemberTypeInstruction {
@@ -128,7 +130,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, IRCode code) {
     // ArrayPut has side-effects on input values.
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index 7610f9c..b7a1f70 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.cf.code.CfConstClass;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
@@ -17,6 +18,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 
 public class ConstClass extends ConstInstruction {
 
@@ -87,7 +89,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, IRCode code) {
     // A const-class instruction can be dead code only if the resulting program is known to contain
     // the class mentioned.
     DexType baseType = clazz.toBaseType(appInfo.dexItemFactory);
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 692dba2..48cc0ba 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
@@ -15,6 +16,7 @@
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.io.UTFDataFormatException;
 
 public class ConstString extends ConstInstruction {
@@ -115,7 +117,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, IRCode code) {
     // No side-effect, such as throwing an exception, in CF.
     return code.options.isGeneratingClassFiles() || !instructionInstanceCanThrow();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
index 8a778b9..1c372ed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
@@ -6,11 +6,13 @@
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 
 public class DebugLocalRead extends Instruction {
   private static final String ERROR_MESSAGE = "Unexpected attempt to emit debug-local read.";
@@ -61,7 +63,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, IRCode code) {
     // Reads are never dead code.
     // They should also have a non-empty set of debug values (see RegAlloc::computeDebugInfo)
     return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
index 9b8a00e..a2aa761 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
@@ -6,12 +6,14 @@
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.StringUtils;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
@@ -73,7 +75,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, IRCode code) {
     return false;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index ea4aebc..6990e18 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -7,11 +7,13 @@
 import com.android.tools.r8.cf.code.CfNop;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 
 public class DebugPosition extends Instruction {
 
@@ -57,7 +59,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, IRCode code) {
     return false;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index c3b8f80..0f00350 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.cf.code.CfDexItemBasedConstString;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
@@ -16,6 +17,7 @@
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 
 public class DexItemBasedConstString extends ConstInstruction {
 
@@ -110,7 +112,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, IRCode code) {
     // No side-effect, such as throwing an exception, in CF.
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 40756ae..5f3c869 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.code;
 
+import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isVisibleFromOriginalContext;
+
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
@@ -19,6 +21,7 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
@@ -31,6 +34,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import org.objectweb.asm.Opcodes;
 
 public class InstanceGet extends FieldInstruction {
@@ -98,6 +102,29 @@
   }
 
   @Override
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, IRCode code) {
+    // Not applicable for D8.
+    if (appView == null || !appView.enableWholeProgramOptimizations()) {
+      return false;
+    }
+    // instance-get can be dead code as long as it cannot have any of the following:
+    // * NoSuchFieldError (resolution failure)
+    // * IllegalAccessError (not visible from the access context)
+    // * NullPointerException (null receiver).
+    // TODO(b/123857022): Should be possible to use definitionFor().
+    DexEncodedField resolvedField = appInfo.resolveFieldOn(getField().getHolder(), getField());
+    if (resolvedField == null) {
+      return false;
+    }
+    if (code == null
+        || !isVisibleFromOriginalContext(appInfo, code.method.method.getHolder(), resolvedField)) {
+      return false;
+    }
+    return object().getTypeLattice().nullability().isDefinitelyNotNull();
+  }
+
+  @Override
   public int maxInValueRegister() {
     return Constants.U4BIT_MAX;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index d8500e6..afe55d1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
@@ -510,7 +511,8 @@
   }
 
   /** Returns true is this instruction can be treated as dead code if its outputs are not used. */
-  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, IRCode code) {
     return !instructionInstanceCanThrow();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
index 7fa8c80..8e7c646 100644
--- a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
@@ -4,9 +4,11 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.List;
 
 public abstract class JumpInstruction extends Instruction {
@@ -32,7 +34,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, IRCode code) {
     return false;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 325016e..e89778e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
@@ -14,6 +15,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 
 public class MoveException extends Instruction {
@@ -70,7 +72,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, IRCode code) {
     return !(code.options.debug || code.method.getOptimizationInfo().isReachabilitySensitive())
         && code.options.isGeneratingDex();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index e235682..5ce2193 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.code.NewArray;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
@@ -16,6 +17,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 
 public class NewArrayEmpty extends Instruction {
 
@@ -72,7 +74,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, IRCode code) {
     if (instructionInstanceCanThrow()) {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index f534fb9..f107c90 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -9,11 +9,13 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.Arrays;
 
 public class NewArrayFilledData extends Instruction {
@@ -74,7 +76,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, IRCode code) {
     if (!src().getTypeLattice().isNullable() && src().numberOfAllUsers() == 1) {
       // The NewArrayFilledData instruction is only inserted by an R8 optimization following
       // a NewArrayEmpty when there are more than one entry.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index 273e57e..41d17b0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -7,11 +7,13 @@
 import com.android.tools.r8.cf.code.CfStackInstruction;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 
 public class Pop extends Instruction {
 
@@ -81,7 +83,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, IRCode code) {
     // Pop cannot be dead code as it modifies the stack height.
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index b79d076..f27ebd6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isVisibleFromOriginalContext;
+
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
@@ -18,6 +20,7 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
@@ -29,6 +32,7 @@
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import org.objectweb.asm.Opcodes;
 
 public class StaticGet extends FieldInstruction {
@@ -91,6 +95,33 @@
   }
 
   @Override
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, IRCode code) {
+    // Not applicable for D8.
+    if (appView == null || !appView.enableWholeProgramOptimizations()) {
+      return false;
+    }
+    // static-get can be dead as long as it cannot have any of the following:
+    // * NoSuchFieldError (resolution failure)
+    // * IllegalAccessError (not visible from the access context)
+    // * side-effects in <clinit>
+    // TODO(b/123857022): Should be possible to use definitionFor().
+    DexEncodedField resolvedField = appInfo.resolveFieldOn(getField().getHolder(), getField());
+    if (resolvedField == null) {
+      return false;
+    }
+    if (code == null
+        || !isVisibleFromOriginalContext(appInfo, code.method.method.getHolder(), resolvedField)) {
+      return false;
+    }
+    DexType context = code.method.method.holder;
+    return !getField().clazz.classInitializationMayHaveSideEffects(
+        appInfo,
+        // Types that are a super type of `context` are guaranteed to be initialized already.
+        type -> context.isSubtypeOf(type, appInfo));
+  }
+
+  @Override
   public int maxInValueRegister() {
     return Constants.U8BIT_MAX;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index 202bac6..07cb184 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -10,12 +10,14 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 
 public class Store extends Instruction {
 
@@ -89,7 +91,8 @@
   }
 
   @Override
-  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+  public boolean canBeDeadCode(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, IRCode code) {
     return !(outValue instanceof FixedLocalValue);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 374acd5..55bc508 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -6,12 +6,14 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.LongInterval;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
@@ -859,12 +861,13 @@
     }
   }
 
-  public boolean isDead(AppInfo appInfo) {
+  public boolean isDead(AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo) {
     // Totally unused values are trivially dead.
-    return !isUsed() || isDead(appInfo, new HashSet<>());
+    return !isUsed() || isDead(appView, appInfo, new HashSet<>());
   }
 
-  protected boolean isDead(AppInfo appInfo, Set<Value> active) {
+  protected boolean isDead(
+      AppView<? extends AppInfoWithLiveness> appView, AppInfo appInfo, Set<Value> active) {
     // If the value has debug users we cannot eliminate it since it represents a value in a local
     // variable that should be visible in the debugger.
     if (numberOfDebugUsers() != 0) {
@@ -874,19 +877,19 @@
     // currently active values.
     active.add(this);
     for (Instruction instruction : uniqueUsers()) {
-      if (!instruction.canBeDeadCode(appInfo, null)) {
+      if (!instruction.canBeDeadCode(appView, appInfo, null)) {
         return false;
       }
       Value outValue = instruction.outValue();
       // Instructions with no out value cannot be dead code by the current definition
       // (unused out value). They typically side-effect input values or deals with control-flow.
       assert outValue != null;
-      if (!active.contains(outValue) && !outValue.isDead(appInfo, active)) {
+      if (!active.contains(outValue) && !outValue.isDead(appView, appInfo, active)) {
         return false;
       }
     }
     for (Phi phi : uniquePhiUsers()) {
-      if (!active.contains(phi) && !phi.isDead(appInfo, active)) {
+      if (!active.contains(phi) && !phi.isDead(appView, appInfo, active)) {
         return false;
       }
     }
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 8571409..cb7a5d5 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
@@ -196,7 +196,8 @@
             ? new CovariantReturnTypeAnnotationTransformer(this, appInfo.dexItemFactory)
             : null;
     this.stringOptimizer = new StringOptimizer(appInfo, options.getInternalOutputMode());
-    this.enableWholeProgramOptimizations = appView != null;
+    this.enableWholeProgramOptimizations =
+        appView != null && appView.enableWholeProgramOptimizations();
     if (enableWholeProgramOptimizations) {
       assert appInfo.hasLiveness();
       AppInfoWithLiveness appInfoWithLiveness = appInfo.withLiveness();
@@ -241,8 +242,7 @@
     this.classStaticizer = options.enableClassStaticizer && appInfo.hasLiveness()
         ? new ClassStaticizer(appInfo.withLiveness(), this) : null;
     this.deadCodeRemover =
-        new DeadCodeRemover(
-            appInfo, codeRewriter, graphLense(), options, enableWholeProgramOptimizations);
+        new DeadCodeRemover(appView, appInfo, codeRewriter, graphLense(), options);
     this.idempotentFunctionCallCanonicalizer =
         new IdempotentFunctionCallCanonicalizer(appInfo.dexItemFactory);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index fa259c2..aadb088 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.optimize;
 
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
@@ -26,6 +27,7 @@
 
 public class DeadCodeRemover {
 
+  private final AppView<? extends AppInfoWithLiveness> appView;
   private final AppInfo appInfo;
   private final CodeRewriter codeRewriter;
   private final GraphLense graphLense;
@@ -33,16 +35,18 @@
   private final boolean enableWholeProgramOptimizations;
 
   public DeadCodeRemover(
+      AppView<? extends AppInfoWithLiveness> appView,
       AppInfo appInfo,
       CodeRewriter codeRewriter,
       GraphLense graphLense,
-      InternalOptions options,
-      boolean enableWholeProgramOptimizations) {
+      InternalOptions options) {
+    this.appView = appView;
     this.appInfo = appInfo;
     this.codeRewriter = codeRewriter;
     this.graphLense = graphLense;
     this.options = options;
-    this.enableWholeProgramOptimizations = enableWholeProgramOptimizations;
+    this.enableWholeProgramOptimizations =
+        appView != null && appView.enableWholeProgramOptimizations();
   }
 
   public void run(IRCode code) {
@@ -54,8 +58,8 @@
     do {
       worklist.addAll(code.blocks);
       for (BasicBlock block = worklist.poll(); block != null; block = worklist.poll()) {
-        removeDeadInstructions(worklist, code, block, appInfo);
-        removeDeadPhis(worklist, block, appInfo);
+        removeDeadInstructions(worklist, code, block, appView, appInfo);
+        removeDeadPhis(worklist, block, appView, appInfo);
       }
     } while (removeUnneededCatchHandlers(code));
     assert code.isConsistentSSA();
@@ -86,11 +90,14 @@
   }
 
   private static void removeDeadPhis(
-      Queue<BasicBlock> worklist, BasicBlock block, AppInfo appInfo) {
+      Queue<BasicBlock> worklist,
+      BasicBlock block,
+      AppView<? extends AppInfoWithLiveness> appView,
+      AppInfo appInfo) {
     Iterator<Phi> phiIt = block.getPhis().iterator();
     while (phiIt.hasNext()) {
       Phi phi = phiIt.next();
-      if (phi.isDead(appInfo)) {
+      if (phi.isDead(appView, appInfo)) {
         phiIt.remove();
         for (Value operand : phi.getOperands()) {
           operand.removePhiUser(phi);
@@ -101,7 +108,11 @@
   }
 
   private static void removeDeadInstructions(
-      Queue<BasicBlock> worklist, IRCode code, BasicBlock block, AppInfo appInfo) {
+      Queue<BasicBlock> worklist,
+      IRCode code,
+      BasicBlock block,
+      AppView<? extends AppInfoWithLiveness> appView,
+      AppInfo appInfo) {
     InstructionListIterator iterator = block.listIterator(block.getInstructions().size());
     while (iterator.hasPrevious()) {
       Instruction current = iterator.previous();
@@ -111,11 +122,11 @@
           && !current.outValue().isUsed()) {
         current.setOutValue(null);
       }
-      if (!current.canBeDeadCode(appInfo, code)) {
+      if (!current.canBeDeadCode(appView, appInfo, code)) {
         continue;
       }
       Value outValue = current.outValue();
-      if (outValue != null && !outValue.isDead(appInfo)) {
+      if (outValue != null && !outValue.isDead(appView, appInfo)) {
         continue;
       }
       updateWorklist(worklist, current);
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 3ff88b9..a2bed1d 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.optimize;
 
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
@@ -290,14 +291,15 @@
       Set<DexEncodedMethod> contexts = fieldsWithContexts.get(field);
       if (target != null && target.field != field
           && contexts.stream().allMatch(context ->
-              isVisibleFromOriginalContext(context.method.getHolder(), target))) {
+              isVisibleFromOriginalContext(appInfo, context.method.getHolder(), target))) {
         builder.map(field,
             lense.lookupField(validTargetFor(target.field, field, lookupTargetOnClass)));
       }
     }
   }
 
-  private boolean isVisibleFromOriginalContext(DexType context, DexEncodedField field) {
+  public static boolean isVisibleFromOriginalContext(
+      AppInfo appInfo, DexType context, DexEncodedField field) {
     DexType holderType = field.field.getHolder();
     DexClass holder = appInfo.definitionFor(holderType);
     if (holder == null) {
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/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 5efc26d..6d34f44 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -508,7 +508,6 @@
             "156-register-dex-file-multi-loader",
             "412-new-array",
             "530-checker-lse2",
-            "550-new-instance-clinit",
             "580-checker-round",
             "594-invoke-super",
             "625-checker-licm-regressions",
@@ -670,6 +669,12 @@
           // lib64 libarttest.so: wrong ELF class ELFCLASS64.
           .put("543-env-long-ref",
               TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
+          // Leaving two static-get triggers LSE bug on 6.0.1 (b/25735083).
+          // R8, with subtyping, knows the first sget is dead, and removing it avoids the bug.
+          // Due to the lack of subtype hierarchy, D8 can't guarantee <clinit> side effects.
+          .put("550-new-instance-clinit",
+              TestCondition.match(
+                  TestCondition.D8_COMPILER, TestCondition.runtimes(DexVm.Version.V6_0_1)))
           // Regression test for an issue that is not fixed on version 5.1.1. Throws an Exception
           // instance instead of the expected NullPointerException. This bug is only tickled when
           // running the R8 generated code when starting from jar or from dex code generated with
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/jasmin/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
index 33f28fc..ea8ed0b 100644
--- a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
@@ -462,7 +462,7 @@
     main.addMainMethod(
         ".limit stack 2",
         ".limit locals 1",
-        "  getstatic Empty/aMethod I",
+        "  getstatic Empty/aField I",
         "  return");
 
     ensureRuntimeException(builder, NoSuchFieldError.class);
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithReprocessingTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithReprocessingTest.java
index 31db483..343dfdf 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithReprocessingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingWithReprocessingTest.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.function.Consumer;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class KotlinLambdaMergingWithReprocessingTest extends AbstractR8KotlinTestBase {
@@ -18,7 +17,6 @@
       o.enableLambdaMerging = true;
     };
 
-  @Ignore("b/123737770")
   @Test
   public void testMergingKStyleLambdasAndReprocessing() throws Exception {
     final String mainClassName = "reprocess_merged_lambdas_kstyle.MainKt";
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
new file mode 100644
index 0000000..f306e04
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/FieldReadsJasminTest.java
@@ -0,0 +1,286 @@
+// 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 static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.jasmin.JasminTestBase;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.Iterator;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class FieldReadsJasminTest extends JasminTestBase {
+  private static final String CLS = "Empty";
+  private static final String MAIN = "Main";
+  private final Backend backend;
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Object[] data() {
+    return Backend.values();
+  }
+
+  public FieldReadsJasminTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void testInstanceGet_nonNullReceiver() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+
+    ClassBuilder empty = builder.addClass(CLS);
+    empty.addField("protected", "aField", "I", null);
+
+    ClassBuilder main = builder.addClass(MAIN);
+    main.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  new Empty",
+        "  dup",
+        "  invokespecial Empty/<init>()V",
+        "  getfield Empty/aField I",
+        "  return");
+
+    ensureNoFields(builder, empty);
+  }
+
+  @Test
+  public void testStaticGet_noSideEffect() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+
+    ClassBuilder empty = builder.addClass(CLS);
+    empty.addStaticField("sField", "I");
+
+    ClassBuilder main = builder.addClass(MAIN);
+    main.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic Empty/sField I",
+        "  return");
+
+    ensureNoFields(builder, empty);
+  }
+
+  private void ensureNoFields(JasminBuilder app, ClassBuilder clazz) throws Exception {
+    testForR8(backend)
+        .addProgramClassFileData(app.buildClasses())
+        .addKeepRules("-keep class * { <methods>; }")
+        .compile()
+        .inspect(inspector -> {
+          ClassSubject classSubject = inspector.clazz(clazz.name);
+          assertThat(classSubject, isPresent());
+          classSubject.forAllFields(foundFieldSubject -> {
+            fail("Expect not to see any fields.");
+          });
+        });
+  }
+
+  @Test
+  public void testStaticGet_nonTrivialClinit_yetSameHolder() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+
+    ClassBuilder main = builder.addClass(MAIN);
+    // static int sField = System.currentTimeMillis() >=0 ? 42 : 0;
+    main.addStaticField("sField", "I", null);
+    main.addClassInitializer(
+        ".limit stack 4",
+        ".limit locals 0",
+        "  invokestatic java/lang/System/currentTimeMillis()J",
+        "  lconst_0",
+        "  lcmp",
+        "  iflt l",
+        "  bipush 42",
+        "  goto p",
+        "l:",
+        "  iconst_0",
+        "p:",
+        "  putstatic Main/sField I",
+        "  return");
+    MethodSignature mainMethod = main.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic Main/sField I",
+        "  return");
+
+    ensureFieldExistsButNoRead(builder, main, mainMethod, main, "sField");
+  }
+
+  private void ensureFieldExistsButNoRead(
+      JasminBuilder app,
+      ClassBuilder clazz,
+      MethodSignature method,
+      ClassBuilder fieldHolder,
+      String fieldName)
+      throws Exception {
+    testForR8(backend)
+        .addProgramClassFileData(app.buildClasses())
+        .addKeepRules("-keep class * { <methods>; }")
+        .compile()
+        .inspect(inspector -> {
+          FieldSubject fld = inspector.clazz(fieldHolder.name).uniqueFieldWithName(fieldName);
+          assertThat(fld, isRenamed());
+
+          ClassSubject classSubject = inspector.clazz(clazz.name);
+          assertThat(classSubject, isPresent());
+          MethodSubject methodSubject = classSubject.uniqueMethodWithName(method.name);
+          assertThat(methodSubject, isPresent());
+          Iterator<InstructionSubject> it =
+              methodSubject.iterateInstructions(InstructionSubject::isFieldAccess);
+          assertFalse(it.hasNext());
+        });
+  }
+
+  @Test
+  public void testInstanceGet_nullableReceiver() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+
+    ClassBuilder empty = builder.addClass(CLS);
+    empty.addDefaultConstructor();
+    empty.addField("protected", "aField", "I", null);
+    MethodSignature foo = empty.addStaticMethod("foo", ImmutableList.of("L" + CLS + ";"), "V",
+        ".limit stack 2",
+        ".limit locals 1",
+        "  aload 0",
+        "  getfield Empty/aField I",
+        "  return");
+
+    ClassBuilder main = builder.addClass(MAIN);
+    main.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  aconst_null",
+        "  invokestatic Empty/foo(L" + CLS + ";)V",
+        "  return");
+
+    ensureFieldExistsAndReadOnlyOnce(builder, empty, foo, empty, "aField");
+  }
+
+  @Test
+  public void testStaticGet_nonTrivialClinit() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+
+    ClassBuilder empty = builder.addClass(CLS);
+    empty.addDefaultConstructor();
+    empty.addStaticField("sField", "I");
+    empty.addClassInitializer(
+        ".limit stack 3",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  ldc \"hello\"",
+        "  invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+        "  return");
+
+    ClassBuilder main = builder.addClass(MAIN);
+    MethodSignature mainMethod = main.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic Empty/sField I",
+        "  return");
+
+    ensureFieldExistsAndReadOnlyOnce(builder, main, mainMethod, empty, "sField");
+  }
+
+  @Test
+  public void testStaticGet_allocation() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+
+    ClassBuilder empty = builder.addClass(CLS);
+    empty.addDefaultConstructor();
+    empty.addStaticField("sField", "Ljava/lang/String;", "\"8\"");
+
+    ClassBuilder main = builder.addClass(MAIN);
+    MethodSignature mainMethod = main.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic Empty/sField Ljava/lang/String;",
+        "  return");
+
+    ensureFieldExistsAndReadOnlyOnce(builder, main, mainMethod, empty, "sField");
+  }
+
+  @Test
+  public void b124039115() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+
+    ClassBuilder empty = builder.addClass(CLS);
+    empty.addDefaultConstructor();
+    empty.addClassInitializer(
+        ".limit stack 2",
+        ".limit locals 0",
+        "  getstatic Main/sField I",
+        "  iconst_1",
+        "  iadd",
+        "  putstatic Main/sField I",
+        "  return");
+
+    ClassBuilder main = builder.addClass(MAIN);
+    main.addDefaultConstructor();
+    main.addStaticField("sField", "I", null);
+    main.addClassInitializer(
+        ".limit stack 2",
+        ".limit locals 0",
+        "  bipush 1",
+        "  putstatic Main/sField I",
+        "  return");
+    MethodSignature mainMethod = main.addMainMethod(
+        ".limit stack 3",
+        ".limit locals 2",
+        "  getstatic Main/sField I",
+        "  new Empty",
+        "  dup",
+        "  invokespecial Empty/<init>()V",
+        "  getstatic Main/sField I",
+        "  bipush 2",
+        "  if_icmpeq r",
+        "  aconst_null",
+        "  athrow",
+        "r:",
+        "  return");
+
+    ensureFieldExistsAndReadOnlyOnce(builder, main, mainMethod, main, "sField");
+  }
+
+  private void ensureFieldExistsAndReadOnlyOnce(
+      JasminBuilder app,
+      ClassBuilder clazz,
+      MethodSignature method,
+      ClassBuilder fieldHolder,
+      String fieldName)
+      throws Exception {
+    testForR8(backend)
+        .addProgramClassFileData(app.buildClasses())
+        .addKeepRules("-keep class * { <methods>; }")
+        .compile()
+        .inspect(inspector -> {
+          FieldSubject fld = inspector.clazz(fieldHolder.name).uniqueFieldWithName(fieldName);
+          assertThat(fld, isRenamed());
+
+          ClassSubject classSubject = inspector.clazz(clazz.name);
+          assertThat(classSubject, isPresent());
+          MethodSubject methodSubject = classSubject.uniqueMethodWithName(method.name);
+          assertThat(methodSubject, isPresent());
+          Iterator<InstructionSubject> it =
+              methodSubject.iterateInstructions(InstructionSubject::isFieldAccess);
+          assertTrue(it.hasNext());
+          assertEquals(fld.getFinalName(), it.next().getField().name.toString());
+          assertFalse(it.hasNext());
+        });
+  }
+
+}
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')