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')