Merge "Remove dead code from VirtualFile"
diff --git a/.gitignore b/.gitignore
index b66a429..f743d94 100644
--- a/.gitignore
+++ b/.gitignore
@@ -60,6 +60,8 @@
third_party/goyt.tar.gz
third_party/ddmlib
third_party/ddmlib.tar.gz
+third_party/core-lambda-stubs
+third_party/core-lambda-stubs.tar.gz
src/test/jack/ub-jack
gradle-app.setting
gradlew
diff --git a/build.gradle b/build.gradle
index c9c415e..9b8d59b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -25,6 +25,22 @@
}
}
+ext {
+ androidSupportVersion = '25.4.0'
+ apacheCommonsVersion = '1.12'
+ asmVersion = '6.0'
+ autoValueVersion = '1.5'
+ espressoVersion = '3.0.0'
+ fastutilVersion = '7.2.0'
+ guavaVersion = '23.0'
+ joptSimpleVersion = '4.6'
+ jsonSimpleVersion = '1.1'
+ junitVersion = '4.12'
+ kotlinVersion = '1.2.0'
+ protobufVersion = '3.0.0'
+ smaliVersion = '2.2b4'
+}
+
def errorProneConfiguration = [
'-XepDisableAllChecks',
// D8 want to use reference equality, thus disable the checker explicitly
@@ -192,48 +208,48 @@
}
dependencies {
- compile 'net.sf.jopt-simple:jopt-simple:4.6'
- compile 'com.googlecode.json-simple:json-simple:1.1'
+ compile "net.sf.jopt-simple:jopt-simple:$joptSimpleVersion"
+ compile "com.googlecode.json-simple:json-simple:$jsonSimpleVersion"
// Include all of guava when compiling the code, but exclude annotations that we don't
// need from the packaging.
- compileOnly('com.google.guava:guava:23.0')
- compile('com.google.guava:guava:23.0', {
+ compileOnly("com.google.guava:guava:$guavaVersion")
+ compile("com.google.guava:guava:$guavaVersion", {
exclude group: 'com.google.errorprone'
exclude group: 'com.google.code.findbugs'
exclude group: 'com.google.j2objc'
exclude group: 'org.codehaus.mojo'
})
- compile group: 'it.unimi.dsi', name: 'fastutil', version: '7.2.0'
- compile group: 'org.ow2.asm', name: 'asm', version: '6.0'
- compile group: 'org.ow2.asm', name: 'asm-commons', version: '6.0'
- compile group: 'org.ow2.asm', name: 'asm-tree', version: '6.0'
- compile group: 'org.ow2.asm', name: 'asm-analysis', version: '6.0'
- compile group: 'org.ow2.asm', name: 'asm-util', version: '6.0'
+ compile group: 'it.unimi.dsi', name: 'fastutil', version: fastutilVersion
+ compile group: 'org.ow2.asm', name: 'asm', version: asmVersion
+ compile group: 'org.ow2.asm', name: 'asm-commons', version: asmVersion
+ compile group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
+ compile group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
+ compile group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
testCompile sourceSets.examples.output
- testCompile 'junit:junit:4.12'
- testCompile group: 'org.smali', name: 'smali', version: '2.2b4'
+ testCompile "junit:junit:$junitVersion"
+ testCompile group: 'org.smali', name: 'smali', version: smaliVersion
testCompile files('third_party/jasmin/jasmin-2.4.jar')
testCompile files('third_party/jdwp-tests/apache-harmony-jdwp-tests-host.jar')
testCompile files('third_party/ddmlib/ddmlib.jar')
- bsPatchCompile group: 'org.apache.commons', name: 'commons-compress', version: '1.12'
- jctfCommonCompile 'junit:junit:4.12'
- jctfTestsCompile 'junit:junit:4.12'
+ bsPatchCompile "org.apache.commons:commons-compress:$apacheCommonsVersion"
+ jctfCommonCompile "junit:junit:$junitVersion"
+ jctfTestsCompile "junit:junit:$junitVersion"
jctfTestsCompile sourceSets.jctfCommon.output
- examplesAndroidOCompile group: 'org.ow2.asm', name: 'asm', version: '6.0'
- examplesAndroidPCompile group: 'org.ow2.asm', name: 'asm', version: '6.0'
+ examplesAndroidOCompile group: 'org.ow2.asm', name: 'asm', version: asmVersion
+ examplesAndroidPCompile group: 'org.ow2.asm', name: 'asm', version: asmVersion
// Import Guava for @Nullable annotation
- examplesCompile 'com.google.guava:guava:23.0'
- examplesCompile 'com.google.protobuf:protobuf-lite:3.0.0'
- examplesCompileOnly "com.google.auto.value:auto-value:1.5"
- examplesRuntime 'com.google.protobuf:protobuf-lite:3.0.0'
- supportLibs 'com.android.support:support-v4:25.4.0'
- supportLibs 'junit:junit:4.12'
- supportLibs 'com.android.support.test.espresso:espresso-core:3.0.0'
+ examplesCompile "com.google.guava:guava:$guavaVersion"
+ examplesCompile "com.google.protobuf:protobuf-lite:$protobufVersion"
+ examplesCompileOnly "com.google.auto.value:auto-value:$autoValueVersion"
+ examplesRuntime "com.google.protobuf:protobuf-lite:$protobufVersion"
+ supportLibs "com.android.support:support-v4:$androidSupportVersion"
+ supportLibs "junit:junit:$junitVersion"
+ supportLibs "com.android.support.test.espresso:espresso-core:$espressoVersion"
apiUsageSampleCompile sourceSets.main.output
- debugTestResourcesKotlinCompileOnly 'org.jetbrains.kotlin:kotlin-stdlib:1.2.0'
- examplesKotlinCompileOnly 'org.jetbrains.kotlin:kotlin-stdlib:1.2.0'
- kotlinR8TestResourcesCompileOnly 'org.jetbrains.kotlin:kotlin-stdlib:1.2.0'
- apt 'com.google.auto.value:auto-value:1.5'
+ debugTestResourcesKotlinCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
+ examplesKotlinCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
+ kotlinR8TestResourcesCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
+ apt "com.google.auto.value:auto-value:$autoValueVersion"
}
configurations.bsPatchCompile.extendsFrom configurations.compile
@@ -245,11 +261,11 @@
protobuf {
protoc {
// Download from repositories
- artifact = 'com.google.protobuf:protoc:3.0.0'
+ artifact = "com.google.protobuf:protoc:$protobufVersion"
}
plugins {
javalite {
- artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
+ artifact = "com.google.protobuf:protoc-gen-javalite:$protobufVersion"
}
}
generateProtoTasks {
@@ -292,6 +308,7 @@
"android_cts_baseline",
"shadow",
"ddmlib",
+ "core-lambda-stubs",
],
// All dex-vms have a fixed OS of Linux, as they are only supported on Linux, and will be run in a Docker
// container on other platforms where supported.
@@ -559,6 +576,20 @@
}
}
+task DexSplitter(type: Jar) {
+ from sourceSets.main.output
+ baseName 'dexsplitter'
+ manifest {
+ attributes 'Main-Class': 'com.android.tools.r8.dexsplitter.DexSplitter'
+ }
+ // In order to build without dependencies, pass the exclude_deps property using:
+ // gradle -Pexclude_deps CompatDx
+ if (!project.hasProperty('exclude_deps')) {
+ // Also include dependencies
+ from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
+ }
+}
+
task CompatProguard(type: Jar) {
from sourceSets.main.output
baseName 'compatproguard'
@@ -1152,13 +1183,17 @@
task buildKotlinR8TestResources {
def examplesDir = file("src/test/kotlinR8TestResources")
examplesDir.eachDir { dir ->
- def name = dir.getName()
- def taskName = "jar_kotlinR8TestResources_${name}"
- task "${taskName}"(type: kotlin.Kotlinc) {
- source = fileTree(dir: file("${examplesDir}/${name}"), include: '**/*.kt')
- destination = file("build/test/kotlinR8TestResources/${name}.jar")
+ kotlin.Kotlinc.KotlinTargetVersion.values().each { kotlinTargetVersion ->
+ def name = dir.getName()
+ def taskName = "jar_kotlinR8TestResources_${name}_${kotlinTargetVersion}"
+ def outputFile = "build/test/kotlinR8TestResources/${kotlinTargetVersion}/${name}.jar"
+ task "${taskName}"(type: kotlin.Kotlinc) {
+ source = fileTree(dir: file("${examplesDir}/${name}"), include: '**/*.kt')
+ destination = file(outputFile)
+ targetVersion = kotlinTargetVersion
+ }
+ dependsOn taskName
}
- dependsOn taskName
}
}
diff --git a/buildSrc/src/main/java/kotlin/Kotlinc.java b/buildSrc/src/main/java/kotlin/Kotlinc.java
index 261acc3..0c0a50a 100644
--- a/buildSrc/src/main/java/kotlin/Kotlinc.java
+++ b/buildSrc/src/main/java/kotlin/Kotlinc.java
@@ -19,16 +19,29 @@
import utils.Utils;
/**
- * Gradle task to compile Kotlin source files.
+ * Gradle task to compile Kotlin source files. By default the generated classes target Java 1.6.
*/
public class Kotlinc extends DefaultTask {
+ enum KotlinTargetVersion {
+ JAVA_6("1.6"),
+ JAVA_8("1.8");
+
+ private final String optionName;
+
+ KotlinTargetVersion(String optionName) {
+ this.optionName = optionName;
+ }
+ }
+
@InputFiles
private FileTree source;
@OutputFile
private File destination;
+ private KotlinTargetVersion targetVersion = KotlinTargetVersion.JAVA_6;
+
public FileTree getSource() {
return source;
}
@@ -45,6 +58,14 @@
this.destination = destination;
}
+ public KotlinTargetVersion getTargetVersion() {
+ return targetVersion;
+ }
+
+ public void setTargetVersion(KotlinTargetVersion targetVersion) {
+ this.targetVersion = targetVersion;
+ }
+
@TaskAction
public void compile() {
getProject().exec(new Action<ExecSpec>() {
@@ -57,6 +78,7 @@
execSpec.setExecutable(kotlincExecPath.toFile());
execSpec.args("-include-runtime");
execSpec.args("-nowarn");
+ execSpec.args("-jvm-target", targetVersion.optionName);
execSpec.args("-d", destination.getCanonicalPath());
execSpec.args(source.getFiles());
} catch (IOException e) {
diff --git a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
index decf916..b2560ff 100644
--- a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
+++ b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
@@ -5,12 +5,14 @@
package com.android.tools.r8;
import com.android.tools.r8.origin.EmbeddedOrigin;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.List;
public class CompatProguardCommandBuilder extends R8Command.Builder {
- private static final List<String> REFLECTIONS = ImmutableList.of(
+ @VisibleForTesting
+ public static final List<String> REFLECTIONS = ImmutableList.of(
"-identifiernamestring public class java.lang.Class {",
" public static java.lang.Class forName(java.lang.String);",
" public java.lang.reflect.Field getField(java.lang.String);",
@@ -37,6 +39,13 @@
this(true);
}
+ public CompatProguardCommandBuilder(
+ boolean forceProguardCompatibility, DiagnosticsHandler diagnosticsHandler) {
+ super(forceProguardCompatibility, diagnosticsHandler);
+ setIgnoreDexInArchive(true);
+ addProguardConfiguration(REFLECTIONS, EmbeddedOrigin.INSTANCE);
+ }
+
public CompatProguardCommandBuilder(boolean forceProguardCompatibility) {
super(forceProguardCompatibility);
setIgnoreDexInArchive(true);
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index a806138..a7bb291 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -148,8 +148,8 @@
throws IOException, CompilationException {
try {
// Disable global optimizations.
- options.skipMinification = true;
- options.inlineAccessors = false;
+ options.enableMinification = false;
+ options.enableInlining = false;
options.outline.enabled = false;
Timing timing = new Timing("DX timer");
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 0e39dae..2678c1f 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -329,22 +329,22 @@
internal.minApiLevel = getMinApiLevel();
internal.intermediate = intermediate;
// Assert and fixup defaults.
- assert !internal.skipMinification;
- internal.skipMinification = true;
- assert internal.useTreeShaking;
- internal.useTreeShaking = false;
+ assert internal.enableMinification;
+ internal.enableMinification = false;
+ assert internal.enableTreeShaking;
+ internal.enableTreeShaking = false;
assert !internal.passthroughDexCode;
internal.passthroughDexCode = true;
// Disable some of R8 optimizations.
- assert internal.inlineAccessors;
- internal.inlineAccessors = false;
- assert internal.removeSwitchMaps;
- internal.removeSwitchMaps = false;
+ assert internal.enableInlining;
+ internal.enableInlining = false;
+ assert internal.enableSwitchMapRemoval;
+ internal.enableSwitchMapRemoval = false;
assert internal.outline.enabled;
internal.outline.enabled = false;
- assert internal.propagateMemberValue;
- internal.propagateMemberValue = false;
+ assert internal.enableValuePropagation;
+ internal.enableValuePropagation = false;
internal.enableDesugaring = getEnableDesugaring();
return internal;
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index 57b3804..bcbddb1 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -56,8 +56,8 @@
options.enableDesugaring = false;
options.enableMainDexListCheck = false;
options.minimalMainDex = minimalMainDex;
- options.skipMinification = true;
- options.inlineAccessors = false;
+ options.enableMinification = false;
+ options.enableInlining = false;
options.outline.enabled = false;
ExecutorService executor = ThreadUtils.getExecutorService(ThreadUtils.NOT_SPECIFIED);
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
new file mode 100644
index 0000000..d28f93f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.dex.ApplicationWriter;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexApplication.Builder;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.FeatureClassMapping;
+import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class DexSplitterHelper {
+
+ public static void run(
+ D8Command command,
+ FeatureClassMapping featureClassMapping,
+ String outputArchive,
+ String proguardMap)
+ throws IOException, CompilationException, ExecutionException {
+ InternalOptions options = command.getInternalOptions();
+ options.enableDesugaring = false;
+ options.enableMainDexListCheck = false;
+ options.minimalMainDex = false;
+ options.enableMinification = false;
+ options.enableInlining = false;
+ options.outline.enabled = false;
+
+ ExecutorService executor = ThreadUtils.getExecutorService(ThreadUtils.NOT_SPECIFIED);
+ try {
+ try {
+ Timing timing = new Timing("DexSplitter");
+ DexApplication app =
+ new ApplicationReader(command.getInputApp(), options, timing).read(null, executor);
+
+
+ ClassNameMapper mapper = null;
+ if (proguardMap != null) {
+ mapper = ClassNameMapper.mapperFromFile(Paths.get(proguardMap));
+ }
+ Map<String, Builder> applications = getDistribution(app, featureClassMapping, mapper);
+ for (Entry<String, Builder> entry : applications.entrySet()) {
+ DexApplication featureApp = entry.getValue().build();
+ // We use the same factory, reset sorting.
+ featureApp.dexItemFactory.resetSortedIndices();
+ assert !options.hasMethodsFilter();
+
+ // Run d8 optimize to ensure jumbo strings are handled.
+ AppInfo appInfo = new AppInfo(featureApp);
+ featureApp = D8.optimize(featureApp, appInfo, options, timing, executor);
+ // We create a specific consumer for each split.
+ DexIndexedConsumer consumer =
+ new ArchiveConsumer(Paths.get(outputArchive + "." + entry.getKey() + ".zip"));
+ try {
+ new ApplicationWriter(
+ featureApp,
+ options,
+ D8.getMarker(options),
+ null,
+ NamingLens.getIdentityLens(),
+ null,
+ null,
+ consumer)
+ .write(executor);
+ options.printWarnings();
+ } finally {
+ consumer.finished(options.reporter);
+ }
+ }
+ } catch (ExecutionException e) {
+ R8.unwrapExecutionException(e);
+ throw new AssertionError(e); // unwrapping method should have thrown
+ } catch (FeatureMappingException e) {
+ options.reporter.error(e.getMessage());
+ } finally {
+ options.signalFinishedToProgramConsumer();
+ }
+ } finally {
+ executor.shutdown();
+ }
+ }
+
+ private static Map<String, Builder> getDistribution(
+ DexApplication app, FeatureClassMapping featureClassMapping, ClassNameMapper mapper)
+ throws FeatureMappingException {
+ Map<String, Builder> applications = new HashMap<>();
+ for (DexProgramClass clazz : app.classes()) {
+ String clazzName =
+ mapper != null ? mapper.deobfuscateClassName(clazz.toString()) : clazz.toString();
+ String feature = featureClassMapping.featureForClass(clazzName);
+ Builder featureApplication = applications.get(feature);
+ if (featureApplication == null) {
+ featureApplication = DexApplication.builder(app.dexItemFactory, app.timing);
+ applications.put(feature, featureApplication);
+ }
+ featureApplication.addProgramClass(clazz);
+ }
+ return applications;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
index b27c26a..f9e0ebf 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -27,6 +27,7 @@
private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
private final StringConsumer mainDexListConsumer;
private final DexItemFactory factory;
+ private final Reporter reporter;
public static class Builder extends BaseCommand.Builder<GenerateMainDexListCommand, Builder> {
@@ -34,6 +35,14 @@
private final List<ProguardConfigurationSource> mainDexRules = new ArrayList<>();
private StringConsumer mainDexListConsumer = null;
+ private Builder() {
+ }
+
+ private Builder(DiagnosticsHandler diagnosticsHandler) {
+ super(diagnosticsHandler);
+ }
+
+
@Override
GenerateMainDexListCommand.Builder self() {
return this;
@@ -106,7 +115,7 @@
}
return new GenerateMainDexListCommand(
- factory, getAppBuilder().build(), mainDexKeepRules, mainDexListConsumer);
+ factory, getAppBuilder().build(), mainDexKeepRules, mainDexListConsumer, getReporter());
}
}
@@ -127,6 +136,10 @@
return new GenerateMainDexListCommand.Builder();
}
+ public static GenerateMainDexListCommand.Builder builder(DiagnosticsHandler diagnosticsHandler) {
+ return new GenerateMainDexListCommand.Builder(diagnosticsHandler);
+ }
+
public static GenerateMainDexListCommand.Builder parse(String[] args) {
GenerateMainDexListCommand.Builder builder = builder();
parse(args, builder);
@@ -168,11 +181,13 @@
DexItemFactory factory,
AndroidApp inputApp,
ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
- StringConsumer mainDexListConsumer) {
+ StringConsumer mainDexListConsumer,
+ Reporter reporter) {
super(inputApp);
this.factory = factory;
this.mainDexKeepRules = mainDexKeepRules;
this.mainDexListConsumer = mainDexListConsumer;
+ this.reporter = reporter;
}
private GenerateMainDexListCommand(boolean printHelp, boolean printVersion) {
@@ -180,18 +195,17 @@
this.factory = new DexItemFactory();
this.mainDexKeepRules = ImmutableList.of();
this.mainDexListConsumer = null;
+ this.reporter = new Reporter(new DefaultDiagnosticsHandler());
}
@Override
InternalOptions getInternalOptions() {
- InternalOptions internal =
- new InternalOptions(factory, new Reporter(new DefaultDiagnosticsHandler()));
+ InternalOptions internal = new InternalOptions(factory, reporter);
internal.mainDexKeepRules = mainDexKeepRules;
internal.mainDexListConsumer = mainDexListConsumer;
internal.minimalMainDex = internal.debug;
- internal.removeSwitchMaps = false;
- internal.inlineAccessors = false;
- internal.allowLibraryClassesToExtendProgramClasses = true;
+ internal.enableSwitchMapRemoval = false;
+ internal.enableInlining = false;
return internal;
}
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 345a671..906c8d8 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -287,7 +287,7 @@
out.flush();
proguardSeedsData = bytes.toString();
}
- if (options.useTreeShaking) {
+ if (options.enableTreeShaking) {
TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
application = pruner.run();
// Recompute the subtyping information.
@@ -329,7 +329,7 @@
if (appInfo.hasLiveness()) {
graphLense = new MemberRebindingAnalysis(appInfo.withLiveness(), graphLense).run();
// Class merging requires inlining.
- if (!options.skipClassMerging && options.inlineAccessors) {
+ if (options.enableClassMerging && options.enableInlining) {
timing.begin("ClassMerger");
SimpleClassMerger classMerger = new SimpleClassMerger(application,
appInfo.withLiveness(), graphLense, timing);
@@ -400,12 +400,12 @@
appInfo = new AppInfoWithSubtyping(application);
- if (options.useTreeShaking || !options.skipMinification) {
+ if (options.enableTreeShaking || options.enableMinification) {
timing.begin("Post optimization code stripping");
try {
Enqueuer enqueuer = new Enqueuer(appInfo, options);
appInfo = enqueuer.traceApplication(rootSet, timing);
- if (options.useTreeShaking) {
+ if (options.enableTreeShaking) {
TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
application = pruner.run();
appInfo = appInfo.withLiveness()
@@ -420,7 +420,7 @@
}
// Only perform discard-checking if tree-shaking is turned on.
- if (options.useTreeShaking && !rootSet.checkDiscarded.isEmpty()) {
+ if (options.enableTreeShaking && !rootSet.checkDiscarded.isEmpty()) {
new DiscardedChecker(rootSet, application, options).run();
}
@@ -428,9 +428,9 @@
// If we did not have keep rules, everything will be marked as keep, so no minification
// will happen. Just avoid the overhead.
NamingLens namingLens =
- options.skipMinification
- ? NamingLens.getIdentityLens()
- : new Minifier(appInfo.withLiveness(), rootSet, options).run(timing);
+ options.enableMinification
+ ? new Minifier(appInfo.withLiveness(), rootSet, options).run(timing)
+ : NamingLens.getIdentityLens();
timing.end();
ProguardMapSupplier proguardMapSupplier;
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index d42df18..7004d5f 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -75,6 +75,12 @@
this.forceProguardCompatibility = forceProguardCompatibility;
}
+ Builder(boolean forceProguardCompatibility, DiagnosticsHandler diagnosticsHandler) {
+ super(diagnosticsHandler);
+ setMode(CompilationMode.RELEASE);
+ this.forceProguardCompatibility = forceProguardCompatibility;
+ }
+
private Builder(DiagnosticsHandler diagnosticsHandler) {
super(diagnosticsHandler);
setMode(CompilationMode.DEBUG);
@@ -630,26 +636,17 @@
internal.programConsumer = getProgramConsumer();
internal.minApiLevel = getMinApiLevel();
internal.enableDesugaring = getEnableDesugaring();
- // -dontoptimize disables optimizations by flipping related flags.
- if (!proguardConfiguration.isOptimizing()) {
- internal.skipClassMerging = true;
- internal.addNonNull = false;
- internal.inlineAccessors = false;
- internal.removeSwitchMaps = false;
- internal.outline.enabled = false;
- internal.propagateMemberValue = false;
- }
- assert !internal.skipMinification;
- internal.skipMinification = !getEnableMinification();
- assert internal.useTreeShaking;
- internal.useTreeShaking = getEnableTreeShaking();
+ assert internal.enableMinification;
+ internal.enableMinification = getEnableMinification();
+ assert internal.enableTreeShaking;
+ internal.enableTreeShaking = getEnableTreeShaking();
assert !internal.ignoreMissingClasses;
internal.ignoreMissingClasses = proguardConfiguration.isIgnoreWarnings()
// TODO(70706667): We probably only want this in Proguard compatibility mode.
|| (forceProguardCompatibility
&& !proguardConfiguration.isOptimizing()
- && internal.skipMinification
- && !internal.useTreeShaking);
+ && !internal.enableMinification
+ && !internal.enableTreeShaking);
assert !internal.verbose;
internal.mainDexKeepRules = mainDexKeepRules;
@@ -661,7 +658,7 @@
if (internal.debug) {
// TODO(zerny): Should we support inlining in debug mode? b/62937285
- internal.inlineAccessors = false;
+ internal.enableInlining = false;
// TODO(zerny): Should we support outlining in debug mode? b/62937285
internal.outline.enabled = false;
}
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index cbbc619..46758e9 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 = "v1.1.3-dev";
+ public static final String LABEL = "v1.1.6-dev";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index bdce57dc..b413bfe 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.dex;
import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.errors.DexOverflowException;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationDirectory;
@@ -46,6 +47,7 @@
public final String proguardSeedsData;
public final InternalOptions options;
public DexString markerString;
+ public DexIndexedConsumer programConsumer;
public final ProguardMapSupplier proguardMapSupplier;
private static class SortAnnotations extends MixedSectionCollection {
@@ -113,6 +115,26 @@
NamingLens namingLens,
String proguardSeedsData,
ProguardMapSupplier proguardMapSupplier) {
+ this(
+ application,
+ options,
+ marker,
+ deadCode,
+ namingLens,
+ proguardSeedsData,
+ proguardMapSupplier,
+ null);
+ }
+
+ public ApplicationWriter(
+ DexApplication application,
+ InternalOptions options,
+ Marker marker,
+ String deadCode,
+ NamingLens namingLens,
+ String proguardSeedsData,
+ ProguardMapSupplier proguardMapSupplier,
+ DexIndexedConsumer consumer) {
assert application != null;
this.application = application;
assert options != null;
@@ -124,6 +146,7 @@
this.namingLens = namingLens;
this.proguardSeedsData = proguardSeedsData;
this.proguardMapSupplier = proguardMapSupplier;
+ this.programConsumer = consumer;
}
private Iterable<VirtualFile> distribute()
@@ -186,7 +209,13 @@
executorService.submit(
() -> {
byte[] result = writeDexFile(mapping);
- if (virtualFile.getPrimaryClassDescriptor() != null) {
+ if (programConsumer != null) {
+ programConsumer.accept(
+ virtualFile.getId(),
+ result,
+ virtualFile.getClassDescriptors(),
+ options.reporter);
+ } else if (virtualFile.getPrimaryClassDescriptor() != null) {
options
.getDexFilePerClassFileConsumer()
.accept(
diff --git a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java b/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
index baafe22..78463bc 100644
--- a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
+++ b/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
@@ -12,6 +12,8 @@
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.OptionsParsing;
+import com.android.tools.r8.utils.OptionsParsing.ParseContext;
import com.android.tools.r8.utils.StringDiagnostic;
import java.io.File;
import java.io.FileOutputStream;
@@ -20,7 +22,6 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -79,87 +80,6 @@
String dexPrefix = DEX_PREFIX;
}
- private static class ParseContext {
- private final String[] args;
- private int nextIndex = 0;
-
- ParseContext(String[] args) {
- this.args = args;
- }
-
- String head() {
- return nextIndex < args.length ? args[nextIndex] : null;
- }
-
- String next() {
- if (nextIndex < args.length) {
- ++nextIndex;
- return head();
- } else {
- throw new RuntimeException("Iterating over the end of argument list.");
- }
- }
- }
-
- /**
- * Try parsing the switch {@code name} and zero or more non-switch args after it. Also supports
- * the <name>=arg syntax.
- */
- private static List<String> tryParseMulti(ParseContext context, String name) {
- List<String> result = null;
- String head = context.head();
- if (head.equals(name)) {
- context.next();
- result = new ArrayList<>();
- while (context.head() != null && !context.head().startsWith("-")) {
- result.add(context.head());
- context.next();
- }
- } else if (head.startsWith(name) && head.charAt(name.length()) == '=') {
- result = Collections.singletonList(head.substring(name.length() + 1));
- context.next();
- }
- return result;
- }
-
- /**
- * Try parsing the switch {@code name} and one arg after it. Also supports the <name>=arg syntax.
- */
- private static String tryParseSingle(ParseContext context, String name, String shortName) {
- String head = context.head();
- if (head.equals(name) || head.equals(shortName)) {
- String next = context.next();
- if (next == null) {
- throw new RuntimeException(String.format("Missing argument for '%s'.", head));
- }
- context.next();
- return next;
- }
-
- if (head.startsWith(name) && head.charAt(name.length()) == '=') {
- context.next();
- return head.substring(name.length() + 1);
- }
-
- return null;
- }
-
- /**
- * Try parsing the switch {@code name} as a boolean switch or its negation, with a 'no' between
- * the dashes and the word.
- */
- private static Boolean tryParseBoolean(ParseContext context, String name) {
- if (context.head().equals(name)) {
- context.next();
- return true;
- }
- assert name.startsWith("--");
- if (context.head().equals("--no" + name.substring(2))) {
- context.next();
- return false;
- }
- return null;
- }
private static Options parseArguments(String[] args) throws IOException {
// We may have a single argument which is a parameter file path, prefixed with '@'.
@@ -192,54 +112,54 @@
if (context.head().startsWith("@")) {
throw new RuntimeException("A params file must be the only argument: " + context.head());
}
- strings = tryParseMulti(context, "--input");
+ strings = OptionsParsing.tryParseMulti(context, "--input");
if (strings != null) {
options.inputArchives.addAll(strings);
continue;
}
- string = tryParseSingle(context, "--output", "-o");
+ string = OptionsParsing.tryParseSingle(context, "--output", "-o");
if (string != null) {
options.outputArchive = string;
continue;
}
- string = tryParseSingle(context, "--multidex", null);
+ string = OptionsParsing.tryParseSingle(context, "--multidex", null);
if (string != null) {
options.multidexMode = MultidexStrategy.valueOf(string.toUpperCase());
continue;
}
- string = tryParseSingle(context, "--main-dex-list", null);
+ string = OptionsParsing.tryParseSingle(context, "--main-dex-list", null);
if (string != null) {
options.mainDexListFile = string;
continue;
}
- b = tryParseBoolean(context, "--minimal-main-dex");
+ b = OptionsParsing.tryParseBoolean(context, "--minimal-main-dex");
if (b != null) {
options.minimalMainDex = b;
continue;
}
- b = tryParseBoolean(context, "--verbose");
+ b = OptionsParsing.tryParseBoolean(context, "--verbose");
if (b != null) {
options.verbose = b;
continue;
}
- string = tryParseSingle(context, "--max-bytes-wasted-per-file", null);
+ string = OptionsParsing.tryParseSingle(context, "--max-bytes-wasted-per-file", null);
if (string != null) {
System.err.println("Warning: '--max-bytes-wasted-per-file' is ignored.");
continue;
}
- string = tryParseSingle(context, "--set-max-idx-number", null);
+ string = OptionsParsing.tryParseSingle(context, "--set-max-idx-number", null);
if (string != null) {
System.err.println("Warning: The '--set-max-idx-number' option is ignored.");
continue;
}
- b = tryParseBoolean(context, "--forceJumbo");
+ b = OptionsParsing.tryParseBoolean(context, "--forceJumbo");
if (b != null) {
System.err.println(
"Warning: '--forceJumbo' can be safely omitted. Strings will only use "
+ "jumbo-string indexing if necessary.");
continue;
}
- string = tryParseSingle(context, "--dex_prefix", null);
+ string = OptionsParsing.tryParseSingle(context, "--dex_prefix", null);
if (string != null) {
options.dexPrefix = string;
continue;
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
new file mode 100644
index 0000000..20cf4ee
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
@@ -0,0 +1,132 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.dexsplitter;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DexSplitterHelper;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.utils.FeatureClassMapping;
+import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
+import com.android.tools.r8.utils.OptionsParsing;
+import com.android.tools.r8.utils.OptionsParsing.ParseContext;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+public class DexSplitter {
+
+ private static final String DEFAULT_OUTPUT_ARCHIVE_FILENAME = "split";
+
+ private static final boolean PRINT_ARGS = false;
+
+ private static class Options {
+ List<String> inputArchives = new ArrayList<>();
+ List<String> featureJars = new ArrayList<>();
+ String splitBaseName = DEFAULT_OUTPUT_ARCHIVE_FILENAME;
+ String featureSplitMapping;
+ String proguardMap;
+ }
+
+ private static Options parseArguments(String[] args) throws IOException {
+ Options options = new Options();
+ ParseContext context = new ParseContext(args);
+ while (context.head() != null) {
+ List<String> input = OptionsParsing.tryParseMulti(context, "--input");
+ if (input != null) {
+ options.inputArchives.addAll(input);
+ continue;
+ }
+ List<String> featureJars = OptionsParsing.tryParseMulti(context, "--feature-jar");
+ if (featureJars != null) {
+ options.featureJars.addAll(featureJars);
+ continue;
+ }
+ String output = OptionsParsing.tryParseSingle(context, "--output", "-o");
+ if (output != null) {
+ options.splitBaseName = output;
+ continue;
+ }
+ String proguardMap = OptionsParsing.tryParseSingle(context, "--proguard-map", null);
+ if (proguardMap != null) {
+ options.proguardMap = proguardMap;
+ continue;
+ }
+ String featureSplit = OptionsParsing.tryParseSingle(context, "--feature-splits", null);
+ if (featureSplit != null) {
+ options.featureSplitMapping = featureSplit;
+ continue;
+ }
+ throw new RuntimeException(String.format("Unknown options: '%s'.", context.head()));
+ }
+ return options;
+ }
+
+ private static FeatureClassMapping createFeatureClassMapping(Options options)
+ throws IOException, FeatureMappingException, ResourceException {
+ if (options.featureSplitMapping != null) {
+ return FeatureClassMapping.fromSpecification(Paths.get(options.featureSplitMapping));
+ }
+ assert !options.featureJars.isEmpty();
+ return FeatureClassMapping.fromJarFiles(options.featureJars);
+ }
+
+ public static void run(String[] args)
+ throws CompilationFailedException, IOException, CompilationException, ExecutionException,
+ ResourceException, FeatureMappingException {
+ Options options = parseArguments(args);
+ if (options.inputArchives.isEmpty()) {
+ throw new RuntimeException("Need at least one --input");
+ }
+ if (options.featureSplitMapping == null && options.featureJars.isEmpty()) {
+ throw new RuntimeException("You must supply a feature split mapping or feature jars");
+ }
+ if (options.featureSplitMapping != null && !options.featureJars.isEmpty()) {
+ throw new RuntimeException("You can't supply both a feature split mapping and feature jars");
+ }
+
+ D8Command.Builder builder = D8Command.builder();
+ for (String s : options.inputArchives) {
+ builder.addProgramFiles(Paths.get(s));
+ }
+ // We set the actual consumer on the ApplicationWriter when we have calculated the distribution
+ // since we don't yet know the distribution.
+ builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+
+ FeatureClassMapping featureClassMapping = createFeatureClassMapping(options);
+
+ DexSplitterHelper.run(
+ builder.build(), featureClassMapping, options.splitBaseName, options.proguardMap);
+ }
+
+ public static void main(String[] args) {
+ try {
+ if (PRINT_ARGS) {
+ printArgs(args);
+ }
+ run(args);
+ } catch (CompilationFailedException
+ | IOException
+ | CompilationException
+ | ExecutionException
+ | ResourceException
+ | FeatureMappingException e) {
+ System.err.println("Splitting failed: " + e.getMessage());
+ System.exit(1);
+ }
+ }
+
+ private static void printArgs(String[] args) {
+ System.err.printf("r8.DexSplitter");
+ for (String s : args) {
+ System.err.printf(" %s", s);
+ }
+ System.err.println("");
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 612d280..2ba0890 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -108,11 +108,17 @@
*/
public DexEncodedMethod lookupSuperTarget(DexMethod method,
DexType invocationContext) {
+ // Make sure we are not chasing NotFoundError.
+ ResolutionResult resolutionResult = resolveMethod(method.holder, method);
+ if (resolutionResult.asListOfTargets().isEmpty()) {
+ return null;
+ }
+ // Then, resume on the search, but this time, starting from the holder of the caller.
DexClass contextClass = definitionFor(invocationContext);
if (contextClass == null || contextClass.superType == null) {
return null;
}
- ResolutionResult resolutionResult = resolveMethod(contextClass.superType, method);
+ resolutionResult = resolveMethod(contextClass.superType, method);
DexEncodedMethod target = resolutionResult.asSingleTarget();
return target == null || !target.isStaticMethod() ? target : null;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index dba4842..35108bd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -213,8 +213,12 @@
@Override
public void collectIndexedItems(IndexedItemCollection collection) {
- name.collectIndexedItems(collection);
- type.collectIndexedItems(collection);
+ if (name != null) {
+ name.collectIndexedItems(collection);
+ }
+ if (type != null) {
+ type.collectIndexedItems(collection);
+ }
if (signature != null) {
signature.collectIndexedItems(collection);
}
@@ -234,8 +238,8 @@
public int hashCode() {
return Constants.DBG_START_LOCAL
+ registerNum * 7
- + name.hashCode() * 13
- + type.hashCode() * 17
+ + Objects.hashCode(name) * 13
+ + Objects.hashCode(type) * 17
+ Objects.hashCode(signature) * 19;
}
@@ -248,10 +252,10 @@
if (registerNum != o.registerNum) {
return false;
}
- if (!name.equals(o.name)) {
+ if (!Objects.equals(name, o.name)) {
return false;
}
- if (!type.equals(o.type)) {
+ if (!Objects.equals(type, o.type)) {
return false;
}
return Objects.equals(signature, o.signature);
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 8aa0a38..a1e8821 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -609,9 +609,8 @@
return new OptimizationInfo(this);
}
- private void markCheckNullReceiverBeforeAnySideEffect() {
- assert !checksNullReceiverBeforeAnySideEffect;
- checksNullReceiverBeforeAnySideEffect = true;
+ private void markCheckNullReceiverBeforeAnySideEffect(boolean mark) {
+ checksNullReceiverBeforeAnySideEffect = mark;
}
}
@@ -659,8 +658,8 @@
ensureMutableOI().markUseIdentifierNameString();
}
- synchronized public void markCheckNullReceiverBeforeAnySideEffect() {
- ensureMutableOI().markCheckNullReceiverBeforeAnySideEffect();
+ synchronized public void markCheckNullReceiverBeforeAnySideEffect(boolean mark) {
+ ensureMutableOI().markCheckNullReceiverBeforeAnySideEffect(mark);
}
public OptimizationInfo getOptimizationInfo() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index d674ccb..b1a5781 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -194,6 +194,7 @@
public final LongMethods longMethods = new LongMethods();
public final ThrowableMethods throwableMethods = new ThrowableMethods();
public final ClassMethods classMethods = new ClassMethods();
+ public final Kotlin kotlin = new Kotlin();
// Dex system annotations.
// See https://source.android.com/devices/tech/dalvik/dex-format.html#system-annotation
@@ -334,6 +335,24 @@
}
}
+ public class Kotlin {
+ private Kotlin() {
+ }
+
+ public final Intrinsics intrinsics = new Intrinsics();
+
+ // kotlin.jvm.internal.Intrinsics class
+ public class Intrinsics {
+ private Intrinsics() {
+ }
+
+ public final DexType type = createType(createString("Lkotlin/jvm/internal/Intrinsics;"));
+ public final DexMethod throwParameterIsNullException =
+ createMethod(type, createProto(voidType, stringType), "throwParameterIsNullException");
+ public final DexMethod throwNpe = createMethod(type, createProto(voidType), "throwNpe");
+ }
+ }
+
private static <T extends DexItem> T canonicalize(ConcurrentHashMap<T, T> map, T item) {
assert item != null;
assert !DexItemFactory.isInternalSentinel(item);
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 2fe0796..621878f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -6,6 +6,8 @@
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.naming.NamingLens;
+import com.google.common.collect.Maps;
+import java.util.Map;
public class DexMethod extends Descriptor<DexEncodedMethod, DexMethod>
implements PresortedComparable<DexMethod> {
@@ -15,7 +17,7 @@
public final DexString name;
// Caches used during processing.
- private DexEncodedMethod singleTargetCache;
+ private Map<DexType, DexEncodedMethod> singleTargetCache;
DexMethod(DexType holder, DexProto proto, DexString name) {
this.holder = holder;
@@ -151,16 +153,20 @@
return builder.toString();
}
- synchronized public void setSingleVirtualMethodCache(DexEncodedMethod method) {
- singleTargetCache = method == null ? DexEncodedMethod.SENTINEL : method;
+ synchronized public void setSingleVirtualMethodCache(
+ DexType receiverType, DexEncodedMethod method) {
+ if (singleTargetCache == null) {
+ singleTargetCache = Maps.newIdentityHashMap();
+ }
+ singleTargetCache.put(receiverType, method);
}
- synchronized public boolean isSingleVirtualMethodCached() {
- return singleTargetCache != null;
+ synchronized public boolean isSingleVirtualMethodCached(DexType receiverType) {
+ return singleTargetCache != null && singleTargetCache.containsKey(receiverType);
}
- synchronized public DexEncodedMethod getSingleVirtualMethodCache() {
- assert isSingleVirtualMethodCached();
- return singleTargetCache == DexEncodedMethod.SENTINEL ? null : singleTargetCache;
+ synchronized public DexEncodedMethod getSingleVirtualMethodCache(DexType receiverType) {
+ assert isSingleVirtualMethodCached(receiverType);
+ return singleTargetCache.get(receiverType);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
index bd459b5..2e30a5d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
@@ -57,17 +57,6 @@
}
@Override
- public TypeLatticeElement checkCast(AppInfo appInfo, DexType castType) {
- if (castType.getNumberOfLeadingSquareBrackets() == getNesting()) {
- DexType base = castType.toBaseType(appInfo.dexItemFactory);
- if (getArrayBaseType(appInfo.dexItemFactory).isSubtypeOf(base, appInfo)) {
- return this;
- }
- }
- return super.checkCast(appInfo, castType);
- }
-
- @Override
public String toString() {
return isNullableString() + arrayType.toString();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
index 5e19e83..f5b2f6e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
@@ -45,14 +45,6 @@
}
@Override
- public TypeLatticeElement checkCast(AppInfo appInfo, DexType castType) {
- if (classType.isSubtypeOf(castType, appInfo)) {
- return this;
- }
- return fromDexType(castType, isNullable());
- }
-
- @Override
public String toString() {
return isNullableString() + classType.toString();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/NullLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/NullLatticeElement.java
index 3c3f0e9..ca9ac77 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/NullLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/NullLatticeElement.java
@@ -11,6 +11,11 @@
}
@Override
+ public boolean mustBeNull() {
+ return true;
+ }
+
+ @Override
TypeLatticeElement asNullable() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
index dddd435..4138b8c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Value;
import com.google.common.annotations.VisibleForTesting;
@@ -34,10 +35,11 @@
public TypeAnalysis(AppInfo appInfo, DexEncodedMethod encodedMethod, IRCode code) {
this.appInfo = appInfo;
this.encodedMethod = encodedMethod;
- updateBlocks(code.topologicallySortedBlocks());
+ analyzeBlocks(code.topologicallySortedBlocks());
}
- public void updateBlocks(List<BasicBlock> blocks) {
+ @Override
+ public void analyzeBlocks(List<BasicBlock> blocks) {
assert worklist.isEmpty();
worklist.addAll(blocks);
while (!worklist.isEmpty()) {
@@ -83,6 +85,12 @@
}
}
}
+ for (Phi phi : block.getPhis()) {
+ TypeLatticeElement phiType = computePhiType(phi);
+ if (!getLatticeElement(phi).equals(phiType)) {
+ updateTypeOfValue(phi, phiType);
+ }
+ }
}
private void registerAsUserOfValue(Value value, BasicBlock block, Set<Value> seenPhis) {
@@ -124,15 +132,37 @@
}
@Override
- public DexType getObjectType(Value value) {
- DexType type = appInfo.dexItemFactory.objectType;
- TypeLatticeElement l = getLatticeElement(value);
- if (l.isClassTypeLatticeElement()) {
- type = l.asClassTypeLatticeElement().getClassType();
- } else if (l.isArrayTypeLatticeElement()) {
- type = l.asArrayTypeLatticeElement().getArrayType();
+ public DexType getRefinedReceiverType(InvokeMethodWithReceiver invoke) {
+ DexType receiverType = invoke.getInvokedMethod().getHolder();
+ TypeLatticeElement lattice = getLatticeElement(invoke.getReceiver());
+ if (lattice.isClassTypeLatticeElement()) {
+ DexType refinedType = lattice.asClassTypeLatticeElement().getClassType();
+ if (refinedType.isSubtypeOf(receiverType, appInfo)) {
+ return refinedType;
+ }
}
- return type;
+ return receiverType;
+ }
+
+ private static final TypeEnvironment DEFAULT_ENVIRONMENT = new TypeEnvironment() {
+ @Override
+ public TypeLatticeElement getLatticeElement(Value value) {
+ return Top.getInstance();
+ }
+
+ @Override
+ public DexType getRefinedReceiverType(InvokeMethodWithReceiver invoke) {
+ return invoke.getInvokedMethod().holder;
+ }
+
+ @Override
+ public void analyzeBlocks(List<BasicBlock> blocks) {
+ }
+ };
+
+ // TODO(b/72693244): By annotating type lattice to value, remove the default type env at all.
+ public static TypeEnvironment getDefaultTypeEnvironment() {
+ return DEFAULT_ENVIRONMENT;
}
@VisibleForTesting
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeEnvironment.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeEnvironment.java
index e996012..890af8a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeEnvironment.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeEnvironment.java
@@ -4,9 +4,13 @@
package com.android.tools.r8.ir.analysis.type;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
import com.android.tools.r8.ir.code.Value;
+import java.util.List;
public interface TypeEnvironment {
TypeLatticeElement getLatticeElement(Value value);
- DexType getObjectType(Value value);
+ DexType getRefinedReceiverType(InvokeMethodWithReceiver invoke);
+ void analyzeBlocks(List<BasicBlock> blocks);
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
index 2a26571..5523abb 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
@@ -25,6 +25,10 @@
return isNullable;
}
+ public boolean mustBeNull() {
+ return false;
+ }
+
/**
* Defines how to join with null or switch to nullable lattice element.
*
@@ -138,13 +142,43 @@
public static TypeLatticeElement join(AppInfo appInfo, Stream<TypeLatticeElement> types) {
BinaryOperator<TypeLatticeElement> joiner = joiner(appInfo);
- return types.reduce(Bottom.getInstance(), joiner::apply, joiner::apply);
+ return types.reduce(Bottom.getInstance(), joiner, joiner);
+ }
+
+ /**
+ * Determines the strict partial order of the given {@link TypeLatticeElement}s.
+ *
+ * @param appInfo {@link AppInfo} to compute the least upper bound of {@link TypeLatticeElement}
+ * @param l1 subject {@link TypeLatticeElement}
+ * @param l2 expected to be *strict* bigger than {@param l1}
+ * @return {@code true} if {@param l1} is strictly less than {@param l2}.
+ */
+ public static boolean strictlyLessThan(
+ AppInfo appInfo, TypeLatticeElement l1, TypeLatticeElement l2) {
+ if (l1.equals(l2)) {
+ return false;
+ }
+ TypeLatticeElement lub = join(appInfo, Stream.of(l1, l2));
+ return !l1.equals(lub) && l2.equals(lub);
+ }
+
+ /**
+ * Determines the partial order of the given {@link TypeLatticeElement}s.
+ *
+ * @param appInfo {@link AppInfo} to compute the least upper bound of {@link TypeLatticeElement}
+ * @param l1 subject {@link TypeLatticeElement}
+ * @param l2 expected to be bigger than or equal to {@param l1}
+ * @return {@code true} if {@param l1} is less than or equal to {@param l2}.
+ */
+ public static boolean lessThanOrEqual(
+ AppInfo appInfo, TypeLatticeElement l1, TypeLatticeElement l2) {
+ return l1.equals(l2) || strictlyLessThan(appInfo, l1, l2);
}
/**
* Represents a type that can be everything.
*
- * @return true if the corresponding {@link Value} could be any kinds.
+ * @return {@code true} if the corresponding {@link Value} could be any kinds.
*/
public boolean isTop() {
return false;
@@ -153,7 +187,7 @@
/**
* Represents an empty type.
*
- * @return true if the type of corresponding {@link Value} is not determined yet.
+ * @return {@code true} if the type of corresponding {@link Value} is not determined yet.
*/
boolean isBottom() {
return false;
@@ -212,7 +246,15 @@
}
public TypeLatticeElement checkCast(AppInfo appInfo, DexType castType) {
- return fromDexType(castType, isNullable());
+ TypeLatticeElement castTypeLattice = fromDexType(castType, isNullable());
+ // Special case: casting null.
+ if (mustBeNull()) {
+ return castTypeLattice;
+ }
+ if (lessThanOrEqual(appInfo, this, castTypeLattice)) {
+ return this;
+ }
+ return castTypeLattice;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 8fe89bb..8d5857a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -182,6 +182,14 @@
}
}
+ public void swapSuccessors(BasicBlock a, BasicBlock b) {
+ assert a != b;
+ int aIndex = successors.indexOf(a);
+ int bIndex = successors.indexOf(b);
+ assert aIndex >= 0 && bIndex >= 0;
+ swapSuccessorsByIndex(aIndex, bIndex);
+ }
+
public void swapSuccessorsByIndex(int index1, int index2) {
assert index1 != index2;
if (hasCatchHandlers()) {
@@ -392,6 +400,10 @@
return instructions;
}
+ public boolean isEmpty() {
+ return instructions.isEmpty();
+ }
+
public Instruction entry() {
return instructions.get(0);
}
@@ -1049,20 +1061,25 @@
return hare;
}
- public boolean isSimpleAlwaysThrowingPath() {
+ public boolean isSimpleAlwaysThrowingPath(boolean failOnMissingPosition) {
// See Floyd's cycle-finding algorithm for reference.
BasicBlock hare = this;
BasicBlock tortuous = this;
boolean advance = false;
while (true) {
List<BasicBlock> normalSuccessors = hare.getNormalSuccessors();
- if (normalSuccessors.size() == 0) {
- return hare.exit().isThrow();
- }
if (normalSuccessors.size() > 1) {
return false;
}
+ if (failOnMissingPosition && hasThrowingInstructionWithoutPosition(hare)) {
+ return false;
+ }
+
+ if (normalSuccessors.size() == 0) {
+ return hare.exit().isThrow();
+ }
+
hare = normalSuccessors.get(0);
tortuous = advance ? tortuous.getNormalSuccessors().get(0) : tortuous;
advance = !advance;
@@ -1072,6 +1089,15 @@
}
}
+ private boolean hasThrowingInstructionWithoutPosition(BasicBlock hare) {
+ for (Instruction instruction : hare.instructions) {
+ if (instruction.instructionTypeCanThrow() && instruction.getPosition().isNone()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public Position getPosition() {
BasicBlock block = endOfGotoChain();
return block != null ? block.entry().getPosition() : Position.none();
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index d3ccf16..7f24908 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -10,14 +10,12 @@
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.DexEncodedMethod;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.List;
import java.util.function.Function;
@@ -230,10 +228,6 @@
return "Invoke-" + getTypeString();
}
- // This method is used for inlining and/or other optimizations, such as value propagation.
- // It returns the target method iff this invoke has only one target.
- abstract public DexEncodedMethod computeSingleTarget(AppInfoWithLiveness appInfo);
-
@Override
public boolean isInvoke() {
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index 0622639..38d83ed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -5,7 +5,6 @@
import com.android.tools.r8.code.InvokeCustomRange;
import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
@@ -47,12 +46,6 @@
}
@Override
- public DexEncodedMethod computeSingleTarget(AppInfoWithLiveness appInfo) {
- // Target method can not be known at compile time.
- return null;
- }
-
- @Override
public void buildDex(DexBuilder builder) {
com.android.tools.r8.code.Instruction instruction;
int argumentRegisters = requiredArgumentRegisters();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index f4ab435..60aee95 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
@@ -34,8 +35,10 @@
}
@Override
- public DexEncodedMethod computeSingleTarget(AppInfoWithLiveness appInfo) {
- return appInfo.lookupSingleInterfaceTarget(getInvokedMethod());
+ public DexEncodedMethod computeSingleTarget(
+ AppInfoWithLiveness appInfo, TypeEnvironment typeEnvironment, DexType invocationContext) {
+ DexType refinedReceiverType = typeEnvironment.getRefinedReceiverType(this);
+ return appInfo.lookupSingleInterfaceTarget(getInvokedMethod(), refinedReceiverType);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index c9a63d0..eb58c4d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -10,6 +10,8 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -60,12 +62,28 @@
return this;
}
+ // TODO(jsjeon): merge lookupSingleTarget and computeSingleTarget.
public abstract DexEncodedMethod lookupSingleTarget(AppInfoWithLiveness appInfo,
DexType invocationContext);
public abstract Collection<DexEncodedMethod> lookupTargets(AppInfoWithSubtyping appInfo,
DexType invocationContext);
+ // This method is used for inlining and/or other optimizations, such as value propagation.
+ // It returns the target method iff this invoke has only one target.
+ public DexEncodedMethod computeSingleTarget(AppInfoWithLiveness appInfo) {
+ // TODO(jsjeon): revisit all usage of this method and pass proper invocation context.
+ return computeSingleTarget(appInfo, TypeAnalysis.getDefaultTypeEnvironment(), null);
+ }
+
+ // TODO(b/72693244): By annotating type lattice to value, avoid passing type env.
+ public DexEncodedMethod computeSingleTarget(
+ AppInfoWithLiveness appInfo, TypeEnvironment typeEnvironment, DexType invocationContext) {
+ // In subclasses, e.g., invoke-virtual or invoke-super, use a narrower receiver type by using
+ // receiver type and type environment or invocation context---where the current invoke is.
+ return lookupSingleTarget(appInfo, appInfo.dexItemFactory.objectType);
+ }
+
@Override
public abstract Constraint inliningConstraint(AppInfoWithLiveness info,
DexType invocationContext);
@@ -120,7 +138,7 @@
return result;
}
- public abstract InlineAction computeInlining(InliningOracle decider);
+ public abstract InlineAction computeInlining(InliningOracle decider, DexType invocationContext);
@Override
public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 3118308..95b3f45 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.InliningOracle;
import java.util.List;
@@ -29,8 +30,8 @@
}
@Override
- public final InlineAction computeInlining(InliningOracle decider) {
- return decider.computeForInvokeWithReceiver(this);
+ public final InlineAction computeInlining(InliningOracle decider, DexType invocationContext) {
+ return decider.computeForInvokeWithReceiver(this, invocationContext);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index 07c7a8f..f3d41e2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.cf.code.CfMultiANewArray;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -57,11 +56,6 @@
}
@Override
- public DexEncodedMethod computeSingleTarget(AppInfoWithLiveness appInfo) {
- return null;
- }
-
- @Override
public boolean identicalNonValueNonPositionParts(Instruction other) {
if (!other.isInvokeMultiNewArray()) {
return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index aa04347..41e4ccf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -9,7 +9,6 @@
import com.android.tools.r8.code.FilledNewArrayRange;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -53,11 +52,6 @@
}
@Override
- public DexEncodedMethod computeSingleTarget(AppInfoWithLiveness appInfo) {
- return null;
- }
-
- @Override
public void buildDex(DexBuilder builder) {
com.android.tools.r8.code.Instruction instruction;
int argumentRegisters = requiredArgumentRegisters();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index cc81752..c0e1a30 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -116,7 +116,7 @@
}
@Override
- public InlineAction computeInlining(InliningOracle decider) {
- return decider.computeForInvokePolymorpic(this);
+ public InlineAction computeInlining(InliningOracle decider, DexType invocationContext) {
+ return decider.computeForInvokePolymorpic(this, invocationContext);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 638a335..1867d14 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -103,8 +103,8 @@
}
@Override
- public InlineAction computeInlining(InliningOracle decider) {
- return decider.computeForInvokeStatic(this);
+ public InlineAction computeInlining(InliningOracle decider, DexType inocationContext) {
+ return decider.computeForInvokeStatic(this, inocationContext);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
index f6719b8..d3ef914 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
@@ -4,10 +4,12 @@
package com.android.tools.r8.ir.code;
import com.android.tools.r8.code.InvokeSuperRange;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -32,9 +34,17 @@
}
@Override
- public DexEncodedMethod computeSingleTarget(AppInfoWithLiveness appInfo) {
- // TODO(b/70707023) Use lookupSuperTarget here once fixed.
- return null;
+ public DexEncodedMethod computeSingleTarget(
+ AppInfoWithLiveness appInfo, TypeEnvironment typeEnvironment, DexType invocationContext) {
+ if (invocationContext == null) {
+ return null;
+ }
+ try {
+ return appInfo.lookupSuperTarget(getInvokedMethod(), invocationContext);
+ } catch (CompilationError ce) {
+ // In case of illegal invoke-super, ignore inlining/optimizing it by returning null here.
+ return null;
+ }
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index e3ffea8..8a41c4e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
@@ -34,8 +35,10 @@
}
@Override
- public DexEncodedMethod computeSingleTarget(AppInfoWithLiveness appInfo) {
- return appInfo.lookupSingleVirtualTarget(getInvokedMethod());
+ public DexEncodedMethod computeSingleTarget(
+ AppInfoWithLiveness appInfo, TypeEnvironment typeEnvironment, DexType invocationContext) {
+ DexType refinedReceiverType = typeEnvironment.getRefinedReceiverType(this);
+ return appInfo.lookupSingleVirtualTarget(getInvokedMethod(), refinedReceiverType);
}
@Override
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 a6fd739..c6fdfc2 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
@@ -27,6 +27,11 @@
this.type = type;
}
+ @Override
+ public String toString() {
+ return super.toString() + " " + type.toString();
+ }
+
public Value dest() {
return outValue;
}
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 3b989da..bb4b629 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
@@ -23,6 +23,7 @@
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
@@ -33,7 +34,7 @@
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.optimize.MemberValuePropagation;
-import com.android.tools.r8.ir.optimize.NonNullMarker;
+import com.android.tools.r8.ir.optimize.NonNullTracker;
import com.android.tools.r8.ir.optimize.Outliner;
import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
@@ -81,7 +82,7 @@
private final CodeRewriter codeRewriter;
private final MemberValuePropagation memberValuePropagation;
private final LensCodeRewriter lensCodeRewriter;
- private final NonNullMarker nonNullMarker;
+ private final NonNullTracker nonNullTracker;
private final Inliner inliner;
private final ProtoLitePruner protoLiteRewriter;
private final IdentifierNameStringMarker identifierNameStringMarker;
@@ -112,17 +113,18 @@
? new InterfaceMethodRewriter(this, options) : null;
if (enableWholeProgramOptimizations) {
assert appInfo.hasLiveness();
- this.nonNullMarker = new NonNullMarker();
+ this.nonNullTracker = new NonNullTracker();
this.inliner = new Inliner(appInfo.withLiveness(), graphLense, options);
this.outliner = new Outliner(appInfo.withLiveness(), options);
this.memberValuePropagation =
- options.propagateMemberValue ? new MemberValuePropagation(appInfo.withLiveness()) : null;
+ options.enableValuePropagation ?
+ new MemberValuePropagation(appInfo.withLiveness()) : null;
this.lensCodeRewriter = new LensCodeRewriter(graphLense, appInfo.withSubtyping());
if (appInfo.hasLiveness()) {
// When disabling the pruner here, also disable the ProtoLiteExtension in R8.java.
this.protoLiteRewriter =
options.forceProguardCompatibility ? null : new ProtoLitePruner(appInfo.withLiveness());
- if (!appInfo.withLiveness().identifierNameStrings.isEmpty() && !options.skipMinification) {
+ if (!appInfo.withLiveness().identifierNameStrings.isEmpty() && options.enableMinification) {
this.identifierNameStringMarker = new IdentifierNameStringMarker(appInfo.withLiveness());
} else {
this.identifierNameStringMarker = null;
@@ -132,7 +134,7 @@
this.identifierNameStringMarker = null;
}
} else {
- this.nonNullMarker = null;
+ this.nonNullTracker = null;
this.inliner = null;
this.outliner = null;
this.memberValuePropagation = null;
@@ -377,7 +379,9 @@
// Second inlining pass for dealing with double inline callers.
if (inliner != null) {
- inliner.processDoubleInlineCallers(this, ignoreOptimizationFeedback);
+ // Use direct feedback still, since methods after inlining may
+ // change their status or other properties.
+ inliner.processDoubleInlineCallers(this, directFeedback);
}
synthesizeLambdaClasses(builder);
@@ -554,25 +558,26 @@
if (memberValuePropagation != null) {
memberValuePropagation.rewriteWithConstantValues(code, method.method.holder);
}
- if (options.removeSwitchMaps && appInfo.hasLiveness()) {
+ if (options.enableSwitchMapRemoval && appInfo.hasLiveness()) {
codeRewriter.removeSwitchMaps(code);
}
if (options.disableAssertions) {
codeRewriter.disableAssertions(code);
}
- if (options.addNonNull && nonNullMarker != null) {
- nonNullMarker.addNonNull(code);
+ if (options.enableNonNullTracking && nonNullTracker != null) {
+ nonNullTracker.addNonNull(code);
assert code.isConsistentSSA();
}
- TypeAnalysis typeAnalysis = new TypeAnalysis(appInfo, method, code);
- if (options.inlineAccessors && inliner != null) {
+ TypeEnvironment typeEnvironment = TypeAnalysis.getDefaultTypeEnvironment();
+ if (options.enableInlining && inliner != null) {
+ typeEnvironment = new TypeAnalysis(appInfo, method, code);
// TODO(zerny): Should we support inlining in debug mode? b/62937285
assert !options.debug;
inliner.performInlining(
- method, code, typeAnalysis, isProcessedConcurrently, callSiteInformation);
+ method, code, typeEnvironment, isProcessedConcurrently, callSiteInformation);
}
// TODO(b/69962188): MethodDevirtualizer can perform optimizations using type analysis.
- codeRewriter.removeCasts(code, typeAnalysis);
+ codeRewriter.removeCasts(code, typeEnvironment);
codeRewriter.rewriteLongCompareAndRequireNonNull(code, options);
codeRewriter.commonSubexpressionElimination(code);
codeRewriter.simplifyArrayConstruction(code);
@@ -581,9 +586,9 @@
new SparseConditionalConstantPropagation(code).run();
codeRewriter.rewriteSwitch(code);
codeRewriter.processMethodsNeverReturningNormally(code);
- codeRewriter.simplifyIf(code, typeAnalysis);
- if (options.addNonNull && nonNullMarker != null) {
- nonNullMarker.cleanupNonNull(code);
+ codeRewriter.simplifyIf(code, typeEnvironment);
+ if (options.enableNonNullTracking && nonNullTracker != null) {
+ nonNullTracker.cleanupNonNull(code);
assert code.isConsistentSSA();
}
if (!options.debug) {
@@ -671,7 +676,7 @@
private void markProcessed(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
// After all the optimizations have take place, we compute whether method should be inlinedex.
Constraint state;
- if (!options.inlineAccessors || inliner == null) {
+ if (!options.enableInlining || inliner == null) {
state = Constraint.NEVER;
} else {
state = inliner.computeInliningConstraint(code, method);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 9cd430a..cdb419f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -30,6 +30,7 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.NewArrayEmpty;
+import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
@@ -196,7 +197,15 @@
newArrayEmpty.size(), newType);
iterator.replaceCurrentInstruction(newNewArray);
}
- }
+ } else if (current.isNewInstance()) {
+ NewInstance newInstance= current.asNewInstance();
+ DexType newClazz = graphLense.lookupType(newInstance.clazz, method);
+ if (newClazz != newInstance.clazz) {
+ NewInstance newNewInstance =
+ new NewInstance(newClazz, makeOutValue(newInstance, code));
+ iterator.replaceCurrentInstruction(newNewInstance);
+ }
+ }
}
}
assert code.isConsistentSSA();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
index 267a93f..1b29233 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
@@ -13,5 +13,5 @@
void methodNeverReturnsNull(DexEncodedMethod method);
void methodNeverReturnsNormally(DexEncodedMethod method);
void markProcessed(DexEncodedMethod method, Constraint state);
- void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method);
+ void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
index 93e48cc..1fd3a2f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
@@ -35,7 +35,7 @@
}
@Override
- public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method) {
- method.markCheckNullReceiverBeforeAnySideEffect();
+ public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
+ method.markCheckNullReceiverBeforeAnySideEffect(mark);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
index 5a6bd66..e409818 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
@@ -25,5 +25,5 @@
public void markProcessed(DexEncodedMethod method, Constraint state) {}
@Override
- public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method) {}
+ public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index bb29d3d..05a4d56 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -302,7 +302,9 @@
assert implMethod.holder == accessedFrom;
assert descriptor.targetFoundInClass(accessedFrom);
assert descriptor.getAccessibility() != null;
- assert descriptor.getAccessibility().isPrivate();
+ // When coming from javac these are also private, but we don't assert that, as the
+ // accessibility could have been modified (e.g. due to -allowaccessmodification).
+
assert descriptor.getAccessibility().isSynthetic();
if (implHandle.type.isInvokeStatic()) {
@@ -493,9 +495,9 @@
// TODO(ager): Should we give the new first parameter an actual name? Maybe 'this'?
encodedMethod.accessFlags.setStatic();
encodedMethod.accessFlags.unsetPrivate();
- if (implMethodHolder.isInterface()) {
- encodedMethod.accessFlags.setPublic();
- }
+ // Always make the method public to provide access when r8 minification is allowed to move
+ // the lambda class accessing this method to another package (-allowaccessmodification).
+ encodedMethod.accessFlags.setPublic();
DexCode dexCode = newMethod.getCode().asDexCode();
dexCode.setDebugInfo(dexCode.debugInfoWithAdditionalFirstParameter(null));
assert (dexCode.getDebugInfo() == null)
@@ -522,11 +524,11 @@
DexProgramClass accessorClass = programDefinitionFor(callTarget.holder);
assert accessorClass != null;
+ // Always make the method public to provide access when r8 minification is allowed to move
+ // the lambda class accessing this method to another package (-allowaccessmodification).
MethodAccessFlags accessorFlags =
MethodAccessFlags.fromSharedAccessFlags(
- Constants.ACC_SYNTHETIC
- | Constants.ACC_STATIC
- | (accessorClass.isInterface() ? Constants.ACC_PUBLIC : 0),
+ Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC,
false);
DexEncodedMethod accessorEncodedMethod = new DexEncodedMethod(
callTarget, accessorFlags, DexAnnotationSet.empty(), DexAnnotationSetRefList.empty(),
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 910de36..769228d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -145,6 +145,15 @@
return true;
}
+ private static boolean isFallthroughBlock(BasicBlock block) {
+ for (BasicBlock pred : block.getPredecessors()) {
+ if (pred.exit().fallthroughBlock() == block) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private static void collapsTrivialGoto(
IRCode code, BasicBlock block, BasicBlock nextBlock, List<BasicBlock> blocksToRemove) {
@@ -156,21 +165,18 @@
BasicBlock target = block.endOfGotoChain();
boolean needed = false;
+
if (target == null) {
// This implies we are in a loop of GOTOs. In that case, we will iteratively remove each
// trivial GOTO one-by-one until the above base case (one block targeting itself) is left.
target = block.exit().asGoto().getTarget();
- } else if (target != nextBlock) {
+ }
+
+ if (target != nextBlock) {
// Not targeting the fallthrough block, determine if we need this goto. We need it if
// a fallthrough can hit this block. That is the case if the block is the entry block
// or if one of the predecessors fall through to the block.
- needed = code.blocks.get(0) == block;
- for (BasicBlock pred : block.getPredecessors()) {
- if (pred.exit().fallthroughBlock() == block) {
- needed = true;
- break;
- }
- }
+ needed = code.blocks.get(0) == block || isFallthroughBlock(block);
}
if (!needed) {
@@ -707,9 +713,8 @@
DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
if (!method.isStaticMethod()) {
final Value receiver = code.getThis();
- if (receiver.isUsed() && allPathsCheckNullReceiverBeforeSideEffect(code, receiver)) {
- feedback.markCheckNullReceiverBeforeAnySideEffect(method);
- }
+ feedback.markCheckNullReceiverBeforeAnySideEffect(method,
+ receiver.isUsed() && allPathsCheckNullReceiverBeforeSideEffect(code, receiver));
}
}
@@ -1118,27 +1123,37 @@
continue;
}
- DexType inType = typeEnvironment.getObjectType(inValue);
- DexType outType = typeEnvironment.getObjectType(outValue);
- // Be careful about a down-cast verification error:
- // A < B < C
- // c = ... // Even though we know c is of type A,
- // a' = (B) c; // (this could be removed, since chained below.)
- // a'' = (A) a'; // this should remain for runtime verification.
- if (outType.isStrictSubtypeOf(inType, appInfo)) {
- continue;
- }
- // 1) Trivial cast.
- // A a = ...
- // A a' = (A) a;
- // 2) Up-cast: we already have finer type info.
- // A < B
- // A a = ...
- // B b = (B) a;
- if (inType.isSubtypeOf(castType, appInfo)) {
- needToRemoveTrivialPhis = needToRemoveTrivialPhis || outValue.numberOfPhiUsers() != 0;
- outValue.replaceUsers(inValue);
- it.removeOrReplaceByDebugLocalRead();
+ TypeLatticeElement inTypeLattice = typeEnvironment.getLatticeElement(inValue);
+ if (!inTypeLattice.isTop()) {
+ TypeLatticeElement outTypeLattice = typeEnvironment.getLatticeElement(outValue);
+ TypeLatticeElement castTypeLattice =
+ TypeLatticeElement.fromDexType(castType, inTypeLattice.isNullable());
+ // Special case: null cast, e.g., getMethod(..., (Class[]) null);
+ // This cast should be kept no matter what.
+ if (inTypeLattice.mustBeNull()) {
+ assert outTypeLattice.equals(castTypeLattice);
+ continue;
+ }
+ // 1) Trivial cast.
+ // A a = ...
+ // A a' = (A) a;
+ // 2) Up-cast: we already have finer type info.
+ // A < B
+ // A a = ...
+ // B b = (B) a;
+ if (TypeLatticeElement.lessThanOrEqual(appInfo, inTypeLattice, castTypeLattice)) {
+ assert outTypeLattice.equals(inTypeLattice);
+ needToRemoveTrivialPhis = needToRemoveTrivialPhis || outValue.numberOfPhiUsers() != 0;
+ outValue.replaceUsers(inValue);
+ it.removeOrReplaceByDebugLocalRead();
+ continue;
+ }
+ // Otherwise, keep the checkcast to preserve verification errors. E.g., down-cast:
+ // A < B < C
+ // c = ... // Even though we know c is of type A,
+ // a' = (B) c; // (this could be removed, since chained below.)
+ // a'' = (A) a'; // this should remain for runtime verification.
+ assert outTypeLattice.equals(castTypeLattice);
}
}
// ... v1
@@ -1419,7 +1434,7 @@
// and the dominator or the original block has catch handlers.
continue;
}
- if (!dominator.isSimpleAlwaysThrowingPath()) {
+ if (!dominator.isSimpleAlwaysThrowingPath(false)) {
// Only move string constants into blocks being part of simple
// always throwing path.
continue;
@@ -1859,11 +1874,16 @@
public void simplifyIf(IRCode code, TypeEnvironment typeEnvironment) {
int color = code.reserveMarkingColor();
+ boolean ifBranchFlipped = false;
for (BasicBlock block : code.blocks) {
if (block.isMarked(color)) {
continue;
}
if (block.exit().isIf()) {
+ // Flip then/else branches if needed.
+ if (flipIfBranchesIfNeeded(block, code.hasDebugPositions)) {
+ ifBranchFlipped = true;
+ }
// First rewrite zero comparison.
rewriteIfWithConstZero(block);
@@ -1875,62 +1895,64 @@
If theIf = block.exit().asIf();
List<Value> inValues = theIf.inValues();
- int cond;
-
if (inValues.get(0).isConstNumber()
&& (theIf.isZeroTest() || inValues.get(1).isConstNumber())) {
// Zero test with a constant of comparison between between two constants.
if (theIf.isZeroTest()) {
- cond = inValues.get(0).getConstInstruction().asConstNumber().getIntValue();
+ int cond = inValues.get(0).getConstInstruction().asConstNumber().getIntValue();
+ simplifyIfWithKnownCondition(code, block, theIf, cond, color);
} else {
long left = (long) inValues.get(0).getConstInstruction().asConstNumber().getIntValue();
long right = (long) inValues.get(1).getConstInstruction().asConstNumber().getIntValue();
- cond = Long.signum(left - right);
+ simplifyIfWithKnownCondition(code, block, theIf, Long.signum(left - right), color);
}
} else if (inValues.get(0).hasValueRange()
&& (theIf.isZeroTest() || inValues.get(1).hasValueRange())) {
// Zero test with a value range, or comparison between between two values,
// each with a value ranges.
if (theIf.isZeroTest()) {
- if (inValues.get(0).isValueInRange(0)) {
- // Zero in in the range - can't determine the comparison.
- continue;
+ if (!inValues.get(0).isValueInRange(0)) {
+ int cond = Long.signum(inValues.get(0).getValueRange().getMin());
+ simplifyIfWithKnownCondition(code, block, theIf, cond, color);
}
- cond = Long.signum(inValues.get(0).getValueRange().getMin());
} else {
LongInterval leftRange = inValues.get(0).getValueRange();
LongInterval rightRange = inValues.get(1).getValueRange();
- if (leftRange.overlapsWith(rightRange)) {
- // Ranges overlap - can't determine the comparison.
- continue;
+ if (!leftRange.overlapsWith(rightRange)) {
+ int cond = Long.signum(leftRange.getMin() - rightRange.getMin());
+ simplifyIfWithKnownCondition(code, block, theIf, cond, color);
}
- // There is no overlap.
- cond = Long.signum(leftRange.getMin() - rightRange.getMin());
}
} else if (theIf.isZeroTest() && !inValues.get(0).isConstNumber()) {
- // TODO(b/72693244): annotate type lattice to value
- TypeLatticeElement l = typeEnvironment.getLatticeElement(inValues.get(0));
- if (!l.isPrimitive() && !l.isNullable()) {
- // Any non-zero value should work.
- cond = 1;
+ if (inValues.get(0).isNeverNull()) {
+ simplifyIfWithKnownCondition(code, block, theIf, 1, color);
} else {
- // We are still not sure.
- continue;
+ // TODO(b/72693244): annotate type lattice to value
+ TypeLatticeElement l = typeEnvironment.getLatticeElement(inValues.get(0));
+ if (!l.isPrimitive() && !l.isNullable()) {
+ // Any non-zero value should work.
+ simplifyIfWithKnownCondition(code, block, theIf, 1, color);
+ }
}
- } else {
- continue;
}
- BasicBlock target = theIf.targetFromCondition(cond);
- BasicBlock deadTarget =
- target == theIf.getTrueTarget() ? theIf.fallthroughBlock() : theIf.getTrueTarget();
- rewriteIfToGoto(code, block, theIf, target, deadTarget, color);
}
}
code.removeMarkedBlocks(color);
code.returnMarkingColor(color);
+ if (ifBranchFlipped) {
+ code.traceBlocks();
+ }
assert code.isConsistentSSA();
}
+ private void simplifyIfWithKnownCondition(
+ IRCode code, BasicBlock block, If theIf, int cond, int markingColor) {
+ BasicBlock target = theIf.targetFromCondition(cond);
+ BasicBlock deadTarget =
+ target == theIf.getTrueTarget() ? theIf.fallthroughBlock() : theIf.getTrueTarget();
+ rewriteIfToGoto(code, block, theIf, target, deadTarget, markingColor);
+ }
+
// Find all method invocations that never returns normally, split the block
// after each such invoke instruction and follow it with a block throwing a
// null value (which should result in NPE). Note that this throw is not
@@ -1951,12 +1973,12 @@
InstructionListIterator insnIterator = block.listIterator();
while (insnIterator.hasNext()) {
Instruction insn = insnIterator.next();
- if (!insn.isInvoke()) {
+ if (!insn.isInvokeMethod()) {
continue;
}
DexEncodedMethod singleTarget =
- insn.asInvoke().computeSingleTarget(appInfoWithLiveness);
+ insn.asInvokeMethod().computeSingleTarget(appInfoWithLiveness);
if (singleTarget == null || !singleTarget.getOptimizationInfo().neverReturnsNormally()) {
continue;
}
@@ -2185,6 +2207,28 @@
}
}
+ private boolean flipIfBranchesIfNeeded(BasicBlock block, boolean failOnMissingPosition) {
+ If theIf = block.exit().asIf();
+ BasicBlock trueTarget = theIf.getTrueTarget();
+ BasicBlock fallthrough = theIf.fallthroughBlock();
+ assert trueTarget != fallthrough;
+
+ if (!fallthrough.isSimpleAlwaysThrowingPath(failOnMissingPosition) ||
+ trueTarget.isSimpleAlwaysThrowingPath(failOnMissingPosition)) {
+ return false;
+ }
+
+ // In case fall-through block always trows there is a good chance that it
+ // is created for error checks and 'trueTarget' represents most more common
+ // non-error case. Flipping the if in this case may result in faster code
+ // on older Android versions.
+ List<Value> inValues = theIf.inValues();
+ If newIf = new If(theIf.getType().inverted(), inValues);
+ block.replaceLastInstruction(newIf);
+ block.swapSuccessors(trueTarget, fallthrough);
+ return true;
+ }
+
public void rewriteLongCompareAndRequireNonNull(IRCode code, InternalOptions options) {
if (options.canUseLongCompareAndObjectsNonNull()) {
return;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index abf73b2..da28ce0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -13,7 +13,7 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
@@ -53,10 +53,22 @@
private final Set<DexEncodedMethod> doubleInlineSelectedTargets = Sets.newIdentityHashSet();
private final Map<DexEncodedMethod, DexEncodedMethod> doubleInlineeCandidates = new HashMap<>();
+ private final Set<DexMethod> blackList = Sets.newIdentityHashSet();
+
public Inliner(AppInfoWithLiveness appInfo, GraphLense graphLense, InternalOptions options) {
this.appInfo = appInfo;
this.graphLense = graphLense;
this.options = options;
+ fillInBlackList(appInfo);
+ }
+
+ private void fillInBlackList(AppInfoWithLiveness appInfo) {
+ blackList.add(appInfo.dexItemFactory.kotlin.intrinsics.throwParameterIsNullException);
+ blackList.add(appInfo.dexItemFactory.kotlin.intrinsics.throwNpe);
+ }
+
+ public boolean isBlackListed(DexMethod method) {
+ return blackList.contains(method);
}
private Constraint instructionAllowedForInlining(
@@ -105,6 +117,12 @@
return target.isSamePackage(context);
}
+ synchronized boolean isDoubleInliningTarget(
+ CallSiteInformation callSiteInformation, DexEncodedMethod candidate) {
+ return callSiteInformation.hasDoubleCallSite(candidate)
+ || doubleInlineSelectedTargets.contains(candidate);
+ }
+
synchronized DexEncodedMethod doubleInlining(DexEncodedMethod method,
DexEncodedMethod target) {
if (!applyDoubleInlining) {
@@ -191,6 +209,14 @@
public static Constraint classIsVisible(DexType context, DexType clazz,
AppInfoWithSubtyping appInfo) {
+ if (clazz.isArrayType()) {
+ return classIsVisible(context, clazz.toArrayElementType(appInfo.dexItemFactory), appInfo);
+ }
+
+ if (clazz.isPrimitiveType()) {
+ return ALWAYS;
+ }
+
DexClass definition = appInfo.definitionFor(clazz);
return definition == null ? NEVER
: deriveConstraint(context, clazz, definition.accessFlags, appInfo);
@@ -243,8 +269,8 @@
if (!target.isProcessed()) {
new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
}
- if (options.addNonNull) {
- new NonNullMarker().addNonNull(code);
+ if (options.enableNonNullTracking) {
+ new NonNullTracker().addNonNull(code);
}
return code;
}
@@ -339,7 +365,7 @@
public void performInlining(
DexEncodedMethod method,
IRCode code,
- TypeAnalysis typeAnalysis,
+ TypeEnvironment typeEnvironment,
Predicate<DexEncodedMethod> isProcessedConcurrently,
CallSiteInformation callSiteInformation)
throws ApiLevelException {
@@ -349,7 +375,7 @@
return;
}
InliningOracle oracle = new InliningOracle(
- this, method, typeAnalysis, callSiteInformation, isProcessedConcurrently);
+ this, method, typeEnvironment, callSiteInformation, isProcessedConcurrently);
List<BasicBlock> blocksToRemove = new ArrayList<>();
ListIterator<BasicBlock> blockIterator = code.listIterator();
@@ -363,7 +389,7 @@
Instruction current = iterator.next();
if (current.isInvokeMethod()) {
InvokeMethod invoke = current.asInvokeMethod();
- InlineAction result = invoke.computeInlining(oracle);
+ InlineAction result = invoke.computeInlining(oracle, method.method.holder);
if (result != null) {
DexEncodedMethod target = result.target;
Position invokePosition = invoke.getPosition();
@@ -390,7 +416,7 @@
Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString());
}
performInlining(
- target, inlinee, typeAnalysis, isProcessedConcurrently, callSiteInformation);
+ target, inlinee, typeEnvironment, isProcessedConcurrently, callSiteInformation);
}
// Make sure constructor inlining is legal.
assert !target.isClassInitializer();
@@ -413,13 +439,19 @@
if (instruction_allowance >= 0 || result.ignoreInstructionBudget()) {
iterator.inlineInvoke(code, inlinee, blockIterator, blocksToRemove, downcast);
// Update type env for inlined blocks.
- typeAnalysis.updateBlocks(inlinee.topologicallySortedBlocks());
+ typeEnvironment.analyzeBlocks(inlinee.topologicallySortedBlocks());
// TODO(b/69964136): need a test where refined env in inlinee affects the caller.
- }
- // If we inlined the invoke from a bridge method, it is no longer a bridge method.
- if (method.accessFlags.isBridge()) {
- method.accessFlags.unsetSynthetic();
- method.accessFlags.unsetBridge();
+
+ // If we inlined the invoke from a bridge method, it is no longer a bridge method.
+ if (method.accessFlags.isBridge()) {
+ method.accessFlags.unsetSynthetic();
+ method.accessFlags.unsetBridge();
+ }
+
+ // Record that the current method uses identifier name string if the inlinee did so.
+ if (target.getOptimizationInfo().useIdentifierNameString()) {
+ method.markUseIdentifierNameString();
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index de39887..0e3228c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -53,8 +53,9 @@
}
}
- private DexEncodedMethod validateCandidate(InvokeMethod invoke) {
- DexEncodedMethod candidate = invoke.computeSingleTarget(inliner.appInfo);
+ private DexEncodedMethod validateCandidate(InvokeMethod invoke, DexType invocationContext) {
+ DexEncodedMethod candidate =
+ invoke.computeSingleTarget(inliner.appInfo, typeEnvironment, invocationContext);
if ((candidate == null)
|| (candidate.getCode() == null)
|| inliner.appInfo.definitionFor(candidate.method.getHolder()).isLibraryClass()) {
@@ -129,7 +130,7 @@
private synchronized boolean isDoubleInliningTarget(DexEncodedMethod candidate) {
// 10 is found from measuring.
- return callSiteInformation.hasDoubleCallSite(candidate)
+ return inliner.isDoubleInliningTarget(callSiteInformation, candidate)
&& candidate.getCode().isDexCode()
&& (candidate.getCode().asDexCode().instructions.length <= 10);
}
@@ -202,9 +203,10 @@
return true;
}
- public InlineAction computeForInvokeWithReceiver(InvokeMethodWithReceiver invoke) {
- DexEncodedMethod candidate = validateCandidate(invoke);
- if (candidate == null) {
+ public InlineAction computeForInvokeWithReceiver(
+ InvokeMethodWithReceiver invoke, DexType invocationContext) {
+ DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
+ if (candidate == null || inliner.isBlackListed(candidate.method)) {
return null;
}
@@ -242,9 +244,9 @@
return new InlineAction(candidate, invoke, reason);
}
- public InlineAction computeForInvokeStatic(InvokeStatic invoke) {
- DexEncodedMethod candidate = validateCandidate(invoke);
- if (candidate == null) {
+ public InlineAction computeForInvokeStatic(InvokeStatic invoke, DexType invocationContext) {
+ DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
+ if (candidate == null || inliner.isBlackListed(candidate.method)) {
return null;
}
@@ -276,7 +278,8 @@
return new InlineAction(candidate, invoke, reason);
}
- public InlineAction computeForInvokePolymorpic(InvokePolymorphic invoke) {
+ public InlineAction computeForInvokePolymorpic(
+ InvokePolymorphic invoke, DexType invocationContext) {
// TODO: No inlining of invoke polymorphic for now.
if (info != null) {
info.exclude(invoke, "inlining through invoke signature polymorpic is not supported");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullMarker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullMarker.java
deleted file mode 100644
index 1054343..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullMarker.java
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.optimize;
-
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.DominatorTree;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.NonNull;
-import com.android.tools.r8.ir.code.Phi;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueType;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Sets;
-import java.util.ListIterator;
-import java.util.Set;
-
-public class NonNullMarker {
-
- public NonNullMarker() {
- }
-
- @VisibleForTesting
- static boolean throwsOnNullInput(Instruction instruction) {
- return (instruction.isInvokeMethodWithReceiver() && !instruction.isInvokeDirect())
- || instruction.isInstanceGet()
- || instruction.isInstancePut()
- || instruction.isArrayGet()
- || instruction.isArrayPut()
- || instruction.isMonitor();
- }
-
- private Value getNonNullInput(Instruction instruction) {
- if (instruction.isInvokeMethodWithReceiver()) {
- return instruction.asInvokeMethodWithReceiver().getReceiver();
- } else if (instruction.isInstanceGet()) {
- return instruction.asInstanceGet().object();
- } else if (instruction.isInstancePut()) {
- return instruction.asInstancePut().object();
- } else if (instruction.isArrayGet()) {
- return instruction.asArrayGet().array();
- } else if (instruction.isArrayPut()) {
- return instruction.asArrayPut().array();
- } else if (instruction.isMonitor()) {
- return instruction.asMonitor().object();
- }
- throw new Unreachable("Should conform to throwsOnNullInput.");
- }
-
- public void addNonNull(IRCode code) {
- ListIterator<BasicBlock> blocks = code.blocks.listIterator();
- while (blocks.hasNext()) {
- BasicBlock block = blocks.next();
- InstructionListIterator iterator = block.listIterator();
- while (iterator.hasNext()) {
- Instruction current = iterator.next();
- if (!throwsOnNullInput(current)) {
- continue;
- }
- Value knownToBeNonNullValue = getNonNullInput(current);
- // Avoid adding redundant non-null instruction.
- if (knownToBeNonNullValue.isNeverNull()) {
- // Otherwise, we will have something like:
- // non_null_rcv <- non-null(rcv)
- // ...
- // another_rcv <- non-null(non_null_rcv)
- continue;
- }
- // First, if the current block has catch handler, split into two blocks, e.g.,
- //
- // ...x
- // invoke(rcv, ...)
- // ...y
- //
- // ~>
- //
- // ...x
- // invoke(rcv, ...)
- // goto A
- //
- // A: ...y // nonNullSuccessor
- //
- BasicBlock nonNullSuccessor =
- block.hasCatchHandlers() ? iterator.split(code, blocks) : block;
- // Next, add non-null fake IR, e.g.,
- // ...x
- // invoke(rcv, ...)
- // goto A
- // ...
- // A: non_null_rcv <- non-null(rcv)
- // ...y
- Value nonNullValue =
- code.createValue(ValueType.OBJECT, knownToBeNonNullValue.getLocalInfo());
- NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue);
- nonNull.setPosition(current.getPosition());
- if (block.hasCatchHandlers()) {
- // If we split, add non-null IR on top of the new split block.
- nonNullSuccessor.listIterator().add(nonNull);
- } else {
- // Otherwise, just add it to the current position of the iterator.
- iterator.add(nonNull);
- }
- // Then, replace all users of the original value that are dominated by either the current
- // block or the new split-off block. Since NPE can be explicitly caught, nullness should be
- // propagated through dominance.
- Set<Instruction> users = knownToBeNonNullValue.uniqueUsers();
- Set<Phi> phiUsers = knownToBeNonNullValue.uniquePhiUsers();
- Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
- Set<Phi> dominatedPhiUsers = Sets.newIdentityHashSet();
- DominatorTree dominatorTree = new DominatorTree(code);
- for (BasicBlock dominatee : dominatorTree.dominatedBlocks(nonNullSuccessor)) {
- boolean passNonNullOrDontCare = dominatee != nonNullSuccessor;
- InstructionListIterator dominateeIterator = dominatee.listIterator();
- while (dominateeIterator.hasNext()) {
- Instruction potentialUser = dominateeIterator.next();
- // Avoid replacing values in instructions that happen *before* the current non-null.
- if (!passNonNullOrDontCare) {
- passNonNullOrDontCare = potentialUser == nonNull;
- }
- // Avoid replacing values in instructions we are referring.
- if (potentialUser == current || potentialUser == nonNull) {
- continue;
- }
- if (passNonNullOrDontCare && users.contains(potentialUser)) {
- dominatedUsers.add(potentialUser);
- }
- }
- for (Phi phi : dominatee.getPhis()) {
- if (phiUsers.contains(phi)) {
- dominatedPhiUsers.add(phi);
- }
- }
- }
- knownToBeNonNullValue.replaceSelectiveUsers(
- nonNullValue, dominatedUsers, dominatedPhiUsers);
- }
- }
- }
-
- public void cleanupNonNull(IRCode code) {
- InstructionIterator it = code.instructionIterator();
- boolean needToCheckTrivialPhis = false;
- while (it.hasNext()) {
- Instruction instruction = it.next();
- // non_null_rcv <- non-null(rcv) // deleted
- // ...
- // non_null_rcv#foo
- //
- // ~>
- //
- // rcv#foo
- if (instruction.isNonNull()) {
- NonNull nonNull = instruction.asNonNull();
- Value src = nonNull.src();
- Value dest = nonNull.dest();
- needToCheckTrivialPhis = needToCheckTrivialPhis || dest.uniquePhiUsers().size() != 0;
- dest.replaceUsers(src);
- it.remove();
- }
- }
- // non-null might introduce a phi, e.g.,
- // non_null_rcv <- non-null(rcv)
- // ...
- // v <- phi(rcv, non_null_rcv)
- //
- // Cleaning up that non-null may result in a trivial phi:
- // v <- phi(rcv, rcv)
- if (needToCheckTrivialPhis) {
- code.removeAllTrivialPhis();
- }
- }
-
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
new file mode 100644
index 0000000..9f15bc2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -0,0 +1,245 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.DominatorTree;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.NonNull;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Sets;
+import java.util.ListIterator;
+import java.util.Set;
+
+public class NonNullTracker {
+
+ public NonNullTracker() {
+ }
+
+ @VisibleForTesting
+ static boolean throwsOnNullInput(Instruction instruction) {
+ return (instruction.isInvokeMethodWithReceiver() && !instruction.isInvokeDirect())
+ || instruction.isInstanceGet()
+ || instruction.isInstancePut()
+ || instruction.isArrayGet()
+ || instruction.isArrayPut()
+ || instruction.isArrayLength()
+ || instruction.isMonitor();
+ }
+
+ private Value getNonNullInput(Instruction instruction) {
+ if (instruction.isInvokeMethodWithReceiver()) {
+ return instruction.asInvokeMethodWithReceiver().getReceiver();
+ } else if (instruction.isInstanceGet()) {
+ return instruction.asInstanceGet().object();
+ } else if (instruction.isInstancePut()) {
+ return instruction.asInstancePut().object();
+ } else if (instruction.isArrayGet()) {
+ return instruction.asArrayGet().array();
+ } else if (instruction.isArrayPut()) {
+ return instruction.asArrayPut().array();
+ } else if (instruction.isArrayLength()) {
+ return instruction.asArrayLength().array();
+ } else if (instruction.isMonitor()) {
+ return instruction.asMonitor().object();
+ }
+ throw new Unreachable("Should conform to throwsOnNullInput.");
+ }
+
+ public void addNonNull(IRCode code) {
+ ListIterator<BasicBlock> blocks = code.blocks.listIterator();
+ while (blocks.hasNext()) {
+ BasicBlock block = blocks.next();
+ // Add non-null after instructions that implicitly indicate receiver/array is not null.
+ InstructionListIterator iterator = block.listIterator();
+ while (iterator.hasNext()) {
+ Instruction current = iterator.next();
+ if (!throwsOnNullInput(current)) {
+ continue;
+ }
+ Value knownToBeNonNullValue = getNonNullInput(current);
+ // Avoid adding redundant non-null instruction.
+ if (knownToBeNonNullValue.isNeverNull()) {
+ // Otherwise, we will have something like:
+ // non_null_rcv <- non-null(rcv)
+ // ...
+ // another_rcv <- non-null(non_null_rcv)
+ continue;
+ }
+ // First, if the current block has catch handler, split into two blocks, e.g.,
+ //
+ // ...x
+ // invoke(rcv, ...)
+ // ...y
+ //
+ // ~>
+ //
+ // ...x
+ // invoke(rcv, ...)
+ // goto A
+ //
+ // A: ...y // blockWithNonNullInstruction
+ //
+ BasicBlock blockWithNonNullInstruction =
+ block.hasCatchHandlers() ? iterator.split(code, blocks) : block;
+ // Next, add non-null fake IR, e.g.,
+ // ...x
+ // invoke(rcv, ...)
+ // goto A
+ // ...
+ // A: non_null_rcv <- non-null(rcv)
+ // ...y
+ Value nonNullValue =
+ code.createValue(ValueType.OBJECT, knownToBeNonNullValue.getLocalInfo());
+ NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue);
+ nonNull.setPosition(current.getPosition());
+ if (blockWithNonNullInstruction != block) {
+ // If we split, add non-null IR on top of the new split block.
+ blockWithNonNullInstruction.listIterator().add(nonNull);
+ } else {
+ // Otherwise, just add it to the current block at the position of the iterator.
+ iterator.add(nonNull);
+ }
+ // Then, replace all users of the original value that are dominated by either the current
+ // block or the new split-off block. Since NPE can be explicitly caught, nullness should be
+ // propagated through dominance.
+ Set<Instruction> users = knownToBeNonNullValue.uniqueUsers();
+ Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
+ Set<Phi> dominatedPhiUsers = Sets.newIdentityHashSet();
+ DominatorTree dominatorTree = new DominatorTree(code);
+ Set<BasicBlock> dominatedBlocks = Sets.newIdentityHashSet();
+ for (BasicBlock dominatee : dominatorTree.dominatedBlocks(blockWithNonNullInstruction)) {
+ dominatedBlocks.add(dominatee);
+ InstructionListIterator dominateeIterator = dominatee.listIterator();
+ if (dominatee == blockWithNonNullInstruction) {
+ // In the block with the inserted non null instruction, skip instructions up to and
+ // including the newly inserted instruction.
+ dominateeIterator.nextUntil(instruction -> instruction == nonNull);
+ }
+ while (dominateeIterator.hasNext()) {
+ Instruction potentialUser = dominateeIterator.next();
+ assert potentialUser != nonNull;
+ if (users.contains(potentialUser)) {
+ dominatedUsers.add(potentialUser);
+ }
+ }
+ }
+ for (Phi user : knownToBeNonNullValue.uniquePhiUsers()) {
+ if (dominatedBlocks.contains(user.getBlock())) {
+ dominatedPhiUsers.add(user);
+ }
+ }
+ knownToBeNonNullValue.replaceSelectiveUsers(
+ nonNullValue, dominatedUsers, dominatedPhiUsers);
+ }
+
+ // Add non-null on top of the successor block if the current block ends with a null check.
+ if (block.exit().isIf() && block.exit().asIf().isZeroTest()) {
+ // if v EQ blockX
+ // ... (fallthrough)
+ // blockX: ...
+ //
+ // ~>
+ //
+ // if v EQ blockX
+ // non_null_value <- non-null(v)
+ // ...
+ // blockX: ...
+ //
+ // or
+ //
+ // if v NE blockY
+ // ...
+ // blockY: ...
+ //
+ // ~>
+ //
+ // blockY: non_null_value <- non-null(v)
+ // ...
+ If theIf = block.exit().asIf();
+ Value knownToBeNonNullValue = theIf.inValues().get(0);
+ // Avoid adding redundant non-null instruction.
+ if (!knownToBeNonNullValue.isNeverNull()) {
+ BasicBlock target = theIf.targetFromCondition(1L);
+ // Ignore uncommon empty blocks.
+ if (!target.isEmpty()) {
+ DominatorTree dominatorTree = new DominatorTree(code);
+ // Make sure there are no paths to the target block without passing the current block.
+ if (dominatorTree.dominatedBy(target, block)) {
+ // Collect users of the original value that are dominated by the target block.
+ Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
+ Set<Phi> dominatedPhiUsers = Sets.newIdentityHashSet();
+ Set<BasicBlock> dominatedBlocks =
+ Sets.newHashSet(dominatorTree.dominatedBlocks(target));
+ for (Instruction user : knownToBeNonNullValue.uniqueUsers()) {
+ if (dominatedBlocks.contains(user.getBlock())) {
+ dominatedUsers.add(user);
+ }
+ }
+ for (Phi user : knownToBeNonNullValue.uniquePhiUsers()) {
+ if (dominatedBlocks.contains(user.getBlock())) {
+ dominatedPhiUsers.add(user);
+ }
+ }
+ // Avoid adding a non-null for the value without meaningful users.
+ if (!dominatedUsers.isEmpty() && !dominatedPhiUsers.isEmpty()) {
+ Value nonNullValue = code.createValue(
+ knownToBeNonNullValue.outType(), knownToBeNonNullValue.getLocalInfo());
+ NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue);
+ InstructionListIterator targetIterator = target.listIterator();
+ nonNull.setPosition(targetIterator.next().getPosition());
+ targetIterator.previous();
+ targetIterator.add(nonNull);
+ knownToBeNonNullValue.replaceSelectiveUsers(
+ nonNullValue, dominatedUsers, dominatedPhiUsers);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public void cleanupNonNull(IRCode code) {
+ InstructionIterator it = code.instructionIterator();
+ boolean needToCheckTrivialPhis = false;
+ while (it.hasNext()) {
+ Instruction instruction = it.next();
+ // non_null_rcv <- non-null(rcv) // deleted
+ // ...
+ // non_null_rcv#foo
+ //
+ // ~>
+ //
+ // rcv#foo
+ if (instruction.isNonNull()) {
+ NonNull nonNull = instruction.asNonNull();
+ Value src = nonNull.src();
+ Value dest = nonNull.dest();
+ needToCheckTrivialPhis = needToCheckTrivialPhis || dest.uniquePhiUsers().size() != 0;
+ dest.replaceUsers(src);
+ it.remove();
+ }
+ }
+ // non-null might introduce a phi, e.g.,
+ // non_null_rcv <- non-null(rcv)
+ // ...
+ // v <- phi(rcv, non_null_rcv)
+ //
+ // Cleaning up that non-null may result in a trivial phi:
+ // v <- phi(rcv, rcv)
+ if (needToCheckTrivialPhis) {
+ code.removeAllTrivialPhis();
+ }
+ }
+
+}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 7af0451..24def66 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -73,6 +73,7 @@
public static final int REGISTER_CANDIDATE_NOT_FOUND = -1;
public static final int MIN_CONSTANT_FREE_FOR_POSITIONS = 5;
+ private static final int RESERVED_MOVE_EXCEPTION_REGISTER = 0;
private enum ArgumentReuseMode {
ALLOW_ARGUMENT_REUSE,
@@ -141,6 +142,9 @@
// List of intervals that no register has been allocated to sorted by first live range.
protected PriorityQueue<LiveIntervals> unhandled = new PriorityQueue<>();
+ // List of intervals for the result of move-exception instructions.
+ private List<LiveIntervals> moveExceptionIntervals = new ArrayList<>();
+
// The first register used for parallel moves. After register allocation the parallel move
// temporary registers are [firstParallelMoveTemporary, maxRegisterNumber].
private int firstParallelMoveTemporary = NO_REGISTER;
@@ -153,6 +157,11 @@
// register.
private boolean hasDedicatedMoveExceptionRegister = false;
+ private int getMoveExceptionRegister() {
+ return numberOfArgumentRegisters + NUMBER_OF_SENTINEL_REGISTERS +
+ RESERVED_MOVE_EXCEPTION_REGISTER;
+ }
+
public LinearScanRegisterAllocator(IRCode code, InternalOptions options) {
this.code = code;
this.options = options;
@@ -663,6 +672,7 @@
active.clear();
inactive.clear();
unhandled.clear();
+ moveExceptionIntervals.clear();
for (LiveIntervals intervals : liveIntervals) {
intervals.clearRegisterAssignment();
}
@@ -752,7 +762,6 @@
// Force all move exception ranges to start out with the exception in a fixed register. Split
// their live ranges which will force another register if used.
int moveExceptionRegister = NO_REGISTER;
- List<LiveIntervals> moveExceptionIntervals = new ArrayList<>();
boolean overlappingMoveExceptionIntervals = false;
for (BasicBlock block : code.blocks) {
for (Instruction instruction : block.getInstructions()) {
@@ -761,8 +770,12 @@
Value exceptionValue = instruction.outValue();
LiveIntervals intervals = exceptionValue.getLiveIntervals();
unhandled.remove(intervals);
+ moveExceptionIntervals.add(intervals);
if (moveExceptionRegister == NO_REGISTER) {
+ assert RESERVED_MOVE_EXCEPTION_REGISTER == 0;
moveExceptionRegister = getFreeConsecutiveRegisters(1);
+ assert moveExceptionRegister == getMoveExceptionRegister();
+ assert !freeRegisters.contains(moveExceptionRegister);
}
intervals.setRegister(moveExceptionRegister);
if (!overlappingMoveExceptionIntervals) {
@@ -770,7 +783,6 @@
overlappingMoveExceptionIntervals |= other.overlaps(intervals);
}
}
- moveExceptionIntervals.add(intervals);
}
}
}
@@ -966,8 +978,14 @@
}
current = current.getNextConsecutive();
}
- // Select registers.
current = unhandledInterval.getStartOfConsecutive();
+ // Exclude move exception register if the first interval overlaps a move exception interval.
+ if (overlapsMoveExceptionInterval(current) &&
+ freeRegisters.remove(getMoveExceptionRegister())) {
+ assert RESERVED_MOVE_EXCEPTION_REGISTER == 0;
+ excludedRegisters.add(getMoveExceptionRegister());
+ }
+ // Select registers.
int numberOfRegister = current.numberOfConsecutiveRegisters();
int firstRegister = getFreeConsecutiveRegisters(numberOfRegister);
for (int i = 0; i < numberOfRegister; i++) {
@@ -1124,6 +1142,54 @@
return false;
}
+ // Intervals overlap a move exception interval if one of the splits of the intervals does.
+ // Since spill and restore moves are always put after the move exception we cannot give
+ // a non-move exception interval the same register as a move exception instruction.
+ //
+ // For example:
+ //
+ // B0:
+ // const v0, 0
+ // invoke throwing_method v0 (catch handler B2)
+ // goto B1
+ // B1:
+ // ...
+ // B2:
+ // move-exception v1
+ // invoke method v0
+ // return
+ //
+ // During register allocation we could split the const number intervals into multiple
+ // parts. We have to avoid assigning the same register to v1 and and v0 in B0 even
+ // if v0 has a different register in B2. That is because the spill/restore move when
+ // transitioning from B0 to B2 has to be after the move-exception instruction.
+ //
+ // Assuming that v0 has register 0 in B0 and register 4 in B2 and v1 has register 0 in B2
+ // we would generate the following incorrect code:
+ //
+ // B0:
+ // const r0, 0
+ // invoke throwing_method r0 (catch handler B2)
+ // goto B1
+ // B1:
+ // ...
+ // B2:
+ // move-exception r0
+ // move r4, r0 // Whoops.
+ // invoke method r4
+ // return
+ private boolean overlapsMoveExceptionInterval(LiveIntervals intervals) {
+ if (!hasDedicatedMoveExceptionRegister) {
+ return false;
+ }
+ for (LiveIntervals moveExceptionInterval : moveExceptionIntervals) {
+ if (intervals.anySplitOverlaps(moveExceptionInterval)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private boolean allocateSingleInterval(LiveIntervals unhandledInterval, ArgumentReuseMode mode) {
int registerConstraint = unhandledInterval.getRegisterLimit();
assert registerConstraint <= Constants.U16BIT_MAX;
@@ -1182,8 +1248,8 @@
// register. If we cannot find a free valid register for the move exception value we have no
// place to put a spill move (because the move exception instruction has to be the
// first instruction in the handler block).
- if (hasDedicatedMoveExceptionRegister) {
- int moveExceptionRegister = numberOfArgumentRegisters + NUMBER_OF_SENTINEL_REGISTERS;
+ if (overlapsMoveExceptionInterval(unhandledInterval)) {
+ int moveExceptionRegister = getMoveExceptionRegister();
if (moveExceptionRegister <= registerConstraint) {
freePositions.set(moveExceptionRegister, 0);
}
@@ -1530,8 +1596,8 @@
}
// Disallow reuse of the move exception register if we have reserved one.
- if (hasDedicatedMoveExceptionRegister) {
- usePositions.set(numberOfArgumentRegisters + NUMBER_OF_SENTINEL_REGISTERS, 0);
+ if (overlapsMoveExceptionInterval(unhandledInterval)) {
+ usePositions.set(getMoveExceptionRegister(), 0);
}
// Treat active linked argument intervals as pinned. They cannot be given another register
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index b6d8b79..2bbdaf9 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -325,6 +325,19 @@
return nextOverlap(other) != -1;
}
+ public boolean anySplitOverlaps(LiveIntervals other) {
+ LiveIntervals parent = getSplitParent();
+ if (parent.overlaps(other)) {
+ return true;
+ }
+ for (LiveIntervals child : parent.getSplitChildren()) {
+ if (child.overlaps(other)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public int nextOverlap(LiveIntervals other) {
Iterator<LiveRange> it = other.ranges.iterator();
LiveRange otherRange = it.next();
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 4181f7e..03c4c33 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -37,7 +37,7 @@
}
public NamingLens run(Timing timing) {
- assert !options.skipMinification;
+ assert options.enableMinification;
timing.begin("MinifyClasses");
Map<DexType, DexString> classRenaming =
new ClassNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
index 3fad0d4..521229e 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.naming;
-import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexClass;
@@ -11,7 +10,6 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
@@ -133,15 +131,12 @@
DexClass clazz = appInfo.definitionFor(from);
if (clazz == null) return;
- final Set<DexItem> membersNotMapped = Sets.newIdentityHashSet();
final Set<MemberNaming> appliedMemberNaming = Sets.newIdentityHashSet();
clazz.forEachField(encodedField -> {
MemberNaming memberNaming = classNaming.lookupByOriginalItem(encodedField.field);
if (memberNaming != null) {
appliedMemberNaming.add(memberNaming);
applyFieldMapping(encodedField.field, memberNaming);
- } else if (clazz.isLibraryClass()) {
- membersNotMapped.add(encodedField.field);
}
});
@@ -150,26 +145,9 @@
if (memberNaming != null) {
appliedMemberNaming.add(memberNaming);
applyMethodMapping(encodedMethod.method, memberNaming);
- } else if (clazz.isLibraryClass()) {
- // <clinit> mapping could be omitted.
- if (encodedMethod.isClassInitializer()) {
- return;
- }
- membersNotMapped.add(encodedMethod.method);
}
});
- // The presence of members that are not mapped indicates incomplete mappings for libraries.
- if (!membersNotMapped.isEmpty()) {
- StringBuilder builder = new StringBuilder();
- builder.append("Incomplete mappings:");
- for (DexItem member : membersNotMapped) {
- builder.append(System.lineSeparator()).append(" ").append(member);
- }
- builder.append(System.lineSeparator());
- throw new CompilationError(builder.toString(), clazz.origin);
- }
-
// We need to handle a lib class that extends another lib class where some members are not
// overridden, resulting in absence of definitions. References to those members need to be
// redirected via lense as well.
diff --git a/src/main/java/com/android/tools/r8/origin/ArchiveEntryOrigin.java b/src/main/java/com/android/tools/r8/origin/ArchiveEntryOrigin.java
index 1313665..0a6c84a 100644
--- a/src/main/java/com/android/tools/r8/origin/ArchiveEntryOrigin.java
+++ b/src/main/java/com/android/tools/r8/origin/ArchiveEntryOrigin.java
@@ -4,8 +4,6 @@
package com.android.tools.r8.origin;
-import com.android.tools.r8.origin.Origin;
-
/**
* Origin representing an entry in an archive.
*/
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index bd2ad76..871812a 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -92,6 +92,10 @@
annotationType = annotationType.toBaseType(appInfo.dexItemFactory);
}
DexClass definition = appInfo.definitionFor(annotationType);
+ // TODO(73102187): How to handle annotations without definition.
+ if (options.enableTreeShaking && definition == null) {
+ return false;
+ }
return definition == null || definition.isLibraryClass()
|| appInfo.liveTypes.contains(annotationType);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 1d32bde..ead6bb7 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -42,9 +42,11 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Queues;
import com.google.common.collect.Sets;
@@ -79,6 +81,7 @@
* field descriptions for details.
*/
public class Enqueuer {
+ private boolean tracingMainDex = false;
private final AppInfoWithSubtyping appInfo;
private final InternalOptions options;
@@ -500,13 +503,19 @@
}
private void ensureFromLibraryOrThrow(DexType type, DexType context) {
+ if (tracingMainDex) {
+ // b/72312389: android.jar contains parts of JUnit and most developers include JUnit in
+ // their programs. This leads to library classes extending program classes. When tracing
+ // main dex lists we allow this.
+ return;
+ }
+
DexClass holder = appInfo.definitionFor(type);
if (holder != null && !holder.isLibraryClass()) {
Diagnostic message = new StringDiagnostic("Library class " + context.toSourceString()
+ (holder.isInterface() ? " implements " : " extends ")
+ "program class " + type.toSourceString());
- if (options.forceProguardCompatibility
- || options.allowLibraryClassesToExtendProgramClasses) {
+ if (options.forceProguardCompatibility) {
options.reporter.warning(message);
} else {
options.reporter.error(message);
@@ -945,6 +954,7 @@
}
public Set<DexType> traceMainDex(RootSet rootSet, Timing timing) {
+ this.tracingMainDex = true;
this.rootSet = rootSet;
// Translate the result of root-set computation into enqueuer actions.
enqueueRootItems(rootSet.noShrinking);
@@ -1762,39 +1772,54 @@
* For mapping invoke virtual instruction to single target method.
*/
public DexEncodedMethod lookupSingleVirtualTarget(DexMethod method) {
+ return lookupSingleVirtualTarget(method, method.holder);
+ }
+
+ public DexEncodedMethod lookupSingleVirtualTarget(
+ DexMethod method, DexType refinedReceiverType) {
// This implements the logic from
// https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html#jvms-6.5.invokevirtual
assert method != null;
+ assert refinedReceiverType.isSubtypeOf(method.holder, this);
DexClass holder = definitionFor(method.holder);
- if ((holder == null) || holder.isLibraryClass() || holder.isInterface()) {
+ if (holder == null || holder.isLibraryClass() || holder.isInterface()) {
return null;
}
- if (method.isSingleVirtualMethodCached()) {
- return method.getSingleVirtualMethodCache();
+ boolean refinedReceiverIsStrictSubType = refinedReceiverType != method.holder;
+ DexClass refinedHolder =
+ refinedReceiverIsStrictSubType ? definitionFor(refinedReceiverType) : holder;
+ assert refinedHolder != null;
+ assert !refinedHolder.isLibraryClass();
+ if (method.isSingleVirtualMethodCached(refinedReceiverType)) {
+ return method.getSingleVirtualMethodCache(refinedReceiverType);
}
// For kept types we cannot ensure a single target.
if (pinnedItems.contains(method.holder)) {
- method.setSingleVirtualMethodCache(null);
+ method.setSingleVirtualMethodCache(refinedReceiverType, null);
return null;
}
- // First get the target for receiver type method.type.
+ // First get the target for the holder type.
ResolutionResult topMethod = resolveMethod(method.holder, method);
// We might hit none or multiple targets. Both make this fail at runtime.
if (!topMethod.hasSingleTarget() || !topMethod.asSingleTarget().isVirtualMethod()) {
- method.setSingleVirtualMethodCache(null);
+ method.setSingleVirtualMethodCache(refinedReceiverType, null);
return null;
}
+ // Now, resolve the target with the refined receiver type.
+ if (refinedReceiverIsStrictSubType) {
+ topMethod = resolveMethod(refinedReceiverType, method);
+ }
DexEncodedMethod topSingleTarget = topMethod.asSingleTarget();
DexClass topHolder = definitionFor(topSingleTarget.method.holder);
// We need to know whether the top method is from an interface, as that would allow it to be
// shadowed by a default method from an interface further down.
boolean topIsFromInterface = topHolder.isInterface();
// Now look at all subtypes and search for overrides.
- DexEncodedMethod result = findSingleTargetFromSubtypes(method.holder, method,
- topSingleTarget, !holder.accessFlags.isAbstract(), topIsFromInterface);
+ DexEncodedMethod result = findSingleTargetFromSubtypes(refinedReceiverType, method,
+ topSingleTarget, !refinedHolder.accessFlags.isAbstract(), topIsFromInterface);
// Map the failure case of SENTINEL to null.
result = result == DexEncodedMethod.SENTINEL ? null : result;
- method.setSingleVirtualMethodCache(result);
+ method.setSingleVirtualMethodCache(refinedReceiverType, result);
return result;
}
@@ -1886,6 +1911,11 @@
}
public DexEncodedMethod lookupSingleInterfaceTarget(DexMethod method) {
+ return lookupSingleInterfaceTarget(method, method.holder);
+ }
+
+ public DexEncodedMethod lookupSingleInterfaceTarget(
+ DexMethod method, DexType refinedReceiverType) {
DexClass holder = definitionFor(method.holder);
if ((holder == null) || holder.isLibraryClass() || !holder.accessFlags.isInterface()) {
return null;
@@ -1903,7 +1933,12 @@
DexEncodedMethod result = null;
// The loop will ignore abstract classes that are not kept as they should not be a target
// at runtime.
- for (DexType type : subtypes(method.holder)) {
+ Iterable<DexType> subTypesToExplore =
+ refinedReceiverType == method.holder
+ ? subtypes(method.holder)
+ : Iterables.concat(
+ ImmutableList.of(refinedReceiverType), subtypes(refinedReceiverType));
+ for (DexType type : subTypesToExplore) {
if (pinnedItems.contains(type)) {
// For kept classes we cannot ensure a single target.
return null;
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 9a57b07..27a6405 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -41,8 +41,7 @@
public DexApplication run() {
application.timing.begin("Pruning application...");
- if (options.debugKeepRules && !options.skipMinification) {
-
+ if (options.debugKeepRules && options.enableMinification) {
options.reporter.info(
new StringDiagnostic(
"Debugging keep rules on a minified build might yield broken builds, as "
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index 1d424bb..cccbb4b 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -92,16 +92,29 @@
}
/**
- * Determine the given {@param typeName} is valid java type name or not.
+ * Determine the given {@param typeName} is a valid jvms binary name or not (jvms 4.2.1).
*
- * @param typeName the java type name
- * @return true if and only if the given type name is valid java type
+ * @param typeName the jvms binary name
+ * @return true if and only if the given type name is valid jvms binary name
*/
public static boolean isValidJavaType(String typeName) {
- return typeName.length() > 0
- && Character.isJavaIdentifierStart(typeName.charAt(0))
- && typeName.substring(1).chars().allMatch(
- ch -> Character.isJavaIdentifierPart(ch) || ch == JAVA_PACKAGE_SEPARATOR);
+ if (typeName.length() == 0) {
+ return false;
+ }
+ char last = 0;
+ for (int i = 0; i < typeName.length(); i++) {
+ char c = typeName.charAt(i);
+ if (c == ';' ||
+ c == '[' ||
+ c == '/') {
+ return false;
+ }
+ if (c == '.' && (i == 0 || last == '.')) {
+ return false;
+ }
+ last = c;
+ }
+ return true;
}
/**
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
new file mode 100644
index 0000000..ebb692f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
@@ -0,0 +1,174 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Provides a mappings of classes to modules. The structure of the input file is as follows:
+ * packageOrClass:module
+ *
+ * <p>Lines with a # prefix are ignored.
+ *
+ * <p>We will do most specific matching, i.e., com.google.foobar.*:feature2 com.google.*:base will
+ * put everything in the com.google namespace into base, except classes in com.google.foobar that
+ * will go to feature2. Class based mappings takes precedence over packages (since they are more
+ * specific): com.google.A:feature2 com.google.*:base Puts A into feature2, and all other classes
+ * from com.google into base.
+ *
+ * <p>Note that this format does not allow specifying inter-module dependencies, this is simply a
+ * placement tool.
+ */
+public class FeatureClassMapping {
+
+ HashMap<String, String> parsedRules = new HashMap<>(); // Already parsed rules.
+
+ HashSet<FeaturePredicate> mappings = new HashSet<>();
+ Path mappingFile;
+
+ static final String COMMENT = "#";
+ static final String SEPARATOR = ":";
+
+ public static FeatureClassMapping fromSpecification(Path file)
+ throws FeatureMappingException, IOException {
+ FeatureClassMapping mapping = new FeatureClassMapping();
+ List<String> lines = FileUtils.readAllLines(file);
+ for (int i = 0; i < lines.size(); i++) {
+ String line = lines.get(i);
+ mapping.parseAndAdd(line, i);
+ }
+ return mapping;
+ }
+
+ public static FeatureClassMapping fromJarFiles(List<String> jarFiles)
+ throws FeatureMappingException, IOException {
+ FeatureClassMapping mapping = new FeatureClassMapping();
+ for (String jar : jarFiles) {
+ Path jarPath = Paths.get(jar);
+ String featureName = jarPath.getFileName().toString();
+ if (featureName.endsWith(".jar") || featureName.endsWith(".zip")) {
+ featureName = featureName.substring(0, featureName.length() - 4);
+ }
+
+ ArchiveClassFileProvider provider = new ArchiveClassFileProvider(jarPath);
+ for (String javaDescriptor : provider.getClassDescriptors()) {
+ String javaType = DescriptorUtils.descriptorToJavaType(javaDescriptor);
+ mapping.addMapping(javaType, featureName);
+ }
+ }
+ return mapping;
+ }
+
+ private FeatureClassMapping() {}
+
+ public void addMapping(String clazz, String feature) throws FeatureMappingException {
+ addRule(clazz, feature, 0);
+ }
+
+ FeatureClassMapping(List<String> lines) throws FeatureMappingException {
+ for (int i = 0; i < lines.size(); i++) {
+ String line = lines.get(i);
+ parseAndAdd(line, i);
+ }
+ }
+
+ public String featureForClass(String clazz) throws FeatureMappingException {
+ // Todo(ricow): improve performance (e.g., direct lookup of class predicates through hashmap).
+ FeaturePredicate bestMatch = null;
+ for (FeaturePredicate mapping : mappings) {
+ if (mapping.match(clazz)) {
+ if (bestMatch == null || bestMatch.predicate.length() < mapping.predicate.length()) {
+ bestMatch = mapping;
+ }
+ }
+ }
+ if (bestMatch == null) {
+ throw new FeatureMappingException("Class: " + clazz + " is not mapped to any feature");
+ }
+ return bestMatch.feature;
+ }
+
+ private void parseAndAdd(String line, int lineNumber) throws FeatureMappingException {
+ if (line.startsWith(COMMENT)) {
+ return; // Ignore comments
+ }
+ if (line.isEmpty()) {
+ return; // Ignore blank lines
+ }
+
+ if (!line.contains(SEPARATOR)) {
+ error("Mapping lines must contain a " + SEPARATOR, lineNumber);
+ }
+ String[] values = line.split(SEPARATOR);
+ if (values.length != 2) {
+ error("Mapping lines can only contain one " + SEPARATOR, lineNumber);
+ }
+
+ String predicate = values[0];
+ String feature = values[1];
+ addRule(predicate, feature, lineNumber);
+ }
+
+ private void addRule(String predicate, String feature, int lineNumber)
+ throws FeatureMappingException {
+ if (parsedRules.containsKey(predicate)) {
+ if (!parsedRules.get(predicate).equals(feature)) {
+ error("Redefinition of predicate " + predicate + "not allowed", lineNumber);
+ }
+ return; // Already have this rule.
+ }
+ parsedRules.put(predicate, feature);
+ FeaturePredicate featurePredicate = new FeaturePredicate(predicate, feature);
+ mappings.add(featurePredicate);
+ }
+
+ private void error(String error, int line) throws FeatureMappingException {
+ throw new FeatureMappingException(
+ "Invalid mappings specification: " + error + "\n in file " + mappingFile + ":" + line);
+ }
+
+ public static class FeatureMappingException extends Exception {
+ FeatureMappingException(String message) {
+ super(message);
+ }
+ }
+
+ /** A feature predicate can either be a wildcard or class predicate. */
+ private static class FeaturePredicate {
+ private static Pattern identifier = Pattern.compile("[A-Za-z_\\-][A-Za-z0-9_$\\-]*");
+ final String predicate;
+ final String feature;
+ // False implies class predicate.
+ final boolean isWildcard;
+
+ FeaturePredicate(String predicate, String feature) throws FeatureMappingException {
+ isWildcard = predicate.endsWith(".*");
+ if (isWildcard) {
+ this.predicate = predicate.substring(0, predicate.length() - 3);
+ } else {
+ this.predicate = predicate;
+ }
+ if (!DescriptorUtils.isValidJavaType(this.predicate)) {
+ throw new FeatureMappingException(this.predicate + " is not a valid identifier");
+ }
+ this.feature = feature;
+ }
+
+ boolean match(String className) {
+ if (isWildcard) {
+ return className.startsWith(predicate);
+ } else {
+ // We also put inner classes into the same feature.
+ return className.startsWith(predicate);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 1c32575..8c3c6ed 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -65,6 +65,15 @@
this.reporter = reporter;
this.proguardConfiguration = proguardConfiguration;
itemFactory = proguardConfiguration.getDexItemFactory();
+ // -dontoptimize disables optimizations by flipping related flags.
+ if (!proguardConfiguration.isOptimizing()) {
+ enableClassMerging = false;
+ enableNonNullTracking = false;
+ enableInlining = false;
+ enableSwitchMapRemoval = false;
+ outline.enabled = false;
+ enableValuePropagation = false;
+ }
}
public boolean printTimes = false;
@@ -73,12 +82,12 @@
public boolean passthroughDexCode = false;
// Optimization-related flags. These should conform to -dontoptimize.
- public boolean skipClassMerging = true;
- public boolean addNonNull = true;
- public boolean inlineAccessors = true;
- public boolean removeSwitchMaps = true;
+ public boolean enableClassMerging = false;
+ public boolean enableNonNullTracking = true;
+ public boolean enableInlining = true;
+ public boolean enableSwitchMapRemoval = true;
public final OutlineOptions outline = new OutlineOptions();
- public boolean propagateMemberValue = true;
+ public boolean enableValuePropagation = true;
// Number of threads to use while processing the dex files.
public int numberOfThreads = ThreadUtils.NOT_SPECIFIED;
@@ -162,21 +171,17 @@
// to disable the check that the build makes sense for multi-dexing.
public boolean enableMainDexListCheck = true;
- public boolean useTreeShaking = true;
+ public boolean enableTreeShaking = true;
public boolean printCfg = false;
public String printCfgFile;
public boolean ignoreMissingClasses = false;
// EXPERIMENTAL flag to get behaviour as close to Proguard as possible.
public boolean forceProguardCompatibility = false;
- public boolean skipMinification = false;
+ public boolean enableMinification = true;
public boolean disableAssertions = true;
public boolean debugKeepRules = false;
- // TODO(72312389): android.jar contains parts of JUnit and most developers include JUnit in
- // their programs, which can lead to library classes extending program classes.
- public boolean allowLibraryClassesToExtendProgramClasses = false;
-
public boolean debug = false;
public final TestingOptions testing = new TestingOptions();
diff --git a/src/main/java/com/android/tools/r8/utils/OptionsParsing.java b/src/main/java/com/android/tools/r8/utils/OptionsParsing.java
new file mode 100644
index 0000000..ece6bb2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/OptionsParsing.java
@@ -0,0 +1,93 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class OptionsParsing {
+
+ /**
+ * Try parsing the switch {@code name} and zero or more non-switch args after it. Also supports
+ * the <name>=arg syntax.
+ */
+ public static List<String> tryParseMulti(ParseContext context, String name) {
+ List<String> result = null;
+ String head = context.head();
+ if (head.equals(name)) {
+ context.next();
+ result = new ArrayList<>();
+ while (context.head() != null && !context.head().startsWith("-")) {
+ result.add(context.head());
+ context.next();
+ }
+ } else if (head.startsWith(name) && head.charAt(name.length()) == '=') {
+ result = Collections.singletonList(head.substring(name.length() + 1));
+ context.next();
+ }
+ return result;
+ }
+
+ /**
+ * Try parsing the switch {@code name} and one arg after it. Also supports the <name>=arg syntax.
+ */
+ public static String tryParseSingle(ParseContext context, String name, String shortName) {
+ String head = context.head();
+ if (head.equals(name) || head.equals(shortName)) {
+ String next = context.next();
+ if (next == null) {
+ throw new RuntimeException(String.format("Missing argument for '%s'.", head));
+ }
+ context.next();
+ return next;
+ }
+
+ if (head.startsWith(name) && head.charAt(name.length()) == '=') {
+ context.next();
+ return head.substring(name.length() + 1);
+ }
+
+ return null;
+ }
+
+ /**
+ * Try parsing the switch {@code name} as a boolean switch or its negation, with a 'no' between
+ * the dashes and the word.
+ */
+ public static Boolean tryParseBoolean(ParseContext context, String name) {
+ if (context.head().equals(name)) {
+ context.next();
+ return true;
+ }
+ assert name.startsWith("--");
+ if (context.head().equals("--no" + name.substring(2))) {
+ context.next();
+ return false;
+ }
+ return null;
+ }
+
+ public static class ParseContext {
+ private final String[] args;
+ private int nextIndex = 0;
+
+ public ParseContext(String[] args) {
+ this.args = args;
+ }
+
+ public String head() {
+ return nextIndex < args.length ? args[nextIndex] : null;
+ }
+
+ public String next() {
+ if (nextIndex < args.length) {
+ ++nextIndex;
+ return head();
+ } else {
+ throw new RuntimeException("Iterating over the end of argument list.");
+ }
+ }
+ }
+}
diff --git a/src/test/examples/adaptclassstrings/keep-rules-1.txt b/src/test/examples/adaptclassstrings/keep-rules-1.txt
index 72562ed..21ab3c8a 100644
--- a/src/test/examples/adaptclassstrings/keep-rules-1.txt
+++ b/src/test/examples/adaptclassstrings/keep-rules-1.txt
@@ -9,4 +9,3 @@
}
-dontshrink
--dontoptimize
diff --git a/src/test/examples/adaptclassstrings/keep-rules-2.txt b/src/test/examples/adaptclassstrings/keep-rules-2.txt
index fc8be9d..b06eebf 100644
--- a/src/test/examples/adaptclassstrings/keep-rules-2.txt
+++ b/src/test/examples/adaptclassstrings/keep-rules-2.txt
@@ -9,6 +9,5 @@
}
-dontshrink
--dontoptimize
-adaptclassstrings *.*A
diff --git a/src/test/examples/atomicfieldupdater/keep-rules.txt b/src/test/examples/atomicfieldupdater/keep-rules.txt
index 3b2d2ef..44c60f5 100644
--- a/src/test/examples/atomicfieldupdater/keep-rules.txt
+++ b/src/test/examples/atomicfieldupdater/keep-rules.txt
@@ -10,7 +10,6 @@
-keep,allowobfuscation class atomicfieldupdater.A
-dontshrink
--dontoptimize
# This will be added to CompatProguard by default.
# We are testing whether R8 shows the same behavior.
diff --git a/src/test/examples/dexsplitsample/Class1.java b/src/test/examples/dexsplitsample/Class1.java
new file mode 100644
index 0000000..c0cd25f
--- /dev/null
+++ b/src/test/examples/dexsplitsample/Class1.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package dexsplitsample;
+
+public class Class1 {
+ public static void main(String[] args) {
+ System.out.println("Class1");
+ }
+
+ protected String getClass1String() {
+ return "Class1String";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java b/src/test/examples/dexsplitsample/Class2.java
similarity index 62%
copy from src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
copy to src/test/examples/dexsplitsample/Class2.java
index 09e94f7..39d73b8 100644
--- a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
+++ b/src/test/examples/dexsplitsample/Class2.java
@@ -1,8 +1,11 @@
// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.nonnull;
-public class FieldAccessTest {
- String fld;
+package dexsplitsample;
+
+public class Class2 {
+ public static void main(String[] args) {
+ System.out.println("Class2");
+ }
}
diff --git a/src/test/examples/dexsplitsample/Class3.java b/src/test/examples/dexsplitsample/Class3.java
new file mode 100644
index 0000000..98469a5
--- /dev/null
+++ b/src/test/examples/dexsplitsample/Class3.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package dexsplitsample;
+
+public class Class3 extends Class1 {
+ public static void main(String[] args) {
+ Class3 clazz = new Class3();
+ if (clazz.getClass1String() != "Class1String") {
+ throw new RuntimeException("Can't call method from super");
+ }
+ if (!new InnerClass().success()) {
+ throw new RuntimeException("Can't call method on inner class");
+ }
+ System.out.println("Class3");
+ }
+
+ private static class InnerClass {
+ public boolean success() {
+ return true;
+ }
+ }
+}
diff --git a/src/test/examples/forname/keep-rules.txt b/src/test/examples/forname/keep-rules.txt
index 34a498b..4131646 100644
--- a/src/test/examples/forname/keep-rules.txt
+++ b/src/test/examples/forname/keep-rules.txt
@@ -10,7 +10,6 @@
-keep,allowobfuscation class forname.A
-dontshrink
--dontoptimize
# This will be added to CompatProguard by default.
# We are testing whether R8 shows the same behavior.
diff --git a/src/test/examples/getmembers/A.java b/src/test/examples/getmembers/A.java
index 7a231c0..b13a28c 100644
--- a/src/test/examples/getmembers/A.java
+++ b/src/test/examples/getmembers/A.java
@@ -8,4 +8,7 @@
String bar(String s) {
return foo + s;
}
+ public static String baz() {
+ return foo;
+ }
}
diff --git a/src/test/examples/getmembers/B.java b/src/test/examples/getmembers/B.java
new file mode 100644
index 0000000..d29d0a2
--- /dev/null
+++ b/src/test/examples/getmembers/B.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package getmembers;
+
+import java.lang.reflect.Method;
+
+public class B {
+
+ private String toBeInlined() throws Exception {
+ Method baz = A.class.getMethod("baz", (Class[]) null);
+ assert baz != null;
+ String bazResult = (String) baz.invoke(null, null);
+ assert bazResult.startsWith("foo");
+ return bazResult;
+ }
+
+ synchronized static String inliner() throws Exception {
+ B self = new B();
+ return self.toBeInlined();
+ }
+
+}
diff --git a/src/test/examples/getmembers/Main.java b/src/test/examples/getmembers/Main.java
index ee82c05..7d26710 100644
--- a/src/test/examples/getmembers/Main.java
+++ b/src/test/examples/getmembers/Main.java
@@ -12,11 +12,17 @@
Field foo = a.getDeclaredField("foo");
assert foo != null;
assert foo.get(null).equals("foo");
+ System.out.println(foo.get(null));
Method bar = a.getDeclaredMethod("bar", new Class[] { String.class });
assert bar != null;
A instanceA = new A();
- String barResult = (String) bar.invoke(instanceA);
+ String barResult = (String) bar.invoke(instanceA, "bar");
assert barResult.startsWith("foo");
+ System.out.println(barResult);
+
+ String bazResult = B.inliner();
+ assert bazResult.startsWith("foo");
+ System.out.println(bazResult);
}
}
diff --git a/src/test/examples/getmembers/keep-rules.txt b/src/test/examples/getmembers/keep-rules.txt
index 23f8d6c..68c4a5d 100644
--- a/src/test/examples/getmembers/keep-rules.txt
+++ b/src/test/examples/getmembers/keep-rules.txt
@@ -7,10 +7,13 @@
public static void main(...);
}
--keep,allowobfuscation class getmembers.A
+-keep,allowobfuscation class getmembers.A {
+ <methods>;
+}
--dontshrink
--dontoptimize
+-alwaysinline class getmembers.B {
+ private String toBeInlined();
+}
# This will be added to CompatProguard by default.
# We are testing whether R8 shows the same behavior.
diff --git a/src/test/examples/identifiernamestring/keep-rules-1.txt b/src/test/examples/identifiernamestring/keep-rules-1.txt
index bc18a3f..682999f 100644
--- a/src/test/examples/identifiernamestring/keep-rules-1.txt
+++ b/src/test/examples/identifiernamestring/keep-rules-1.txt
@@ -10,4 +10,3 @@
-keepnames class identifiernamestring.A
-dontshrink
--dontoptimize
diff --git a/src/test/examples/identifiernamestring/keep-rules-2.txt b/src/test/examples/identifiernamestring/keep-rules-2.txt
index 87584e1..74af143 100644
--- a/src/test/examples/identifiernamestring/keep-rules-2.txt
+++ b/src/test/examples/identifiernamestring/keep-rules-2.txt
@@ -10,7 +10,6 @@
-keepnames class identifiernamestring.A
-dontshrink
--dontoptimize
-identifiernamestring class * {
@identifiernamestring.IdentifierNameString *;
diff --git a/src/test/examples/identifiernamestring/keep-rules-3.txt b/src/test/examples/identifiernamestring/keep-rules-3.txt
index 5ac8b93..aa23772 100644
--- a/src/test/examples/identifiernamestring/keep-rules-3.txt
+++ b/src/test/examples/identifiernamestring/keep-rules-3.txt
@@ -10,7 +10,6 @@
-keepnames class identifiernamestring.A
-dontshrink
--dontoptimize
-identifiernamestring class * {
static java.lang.reflect.Field *(java.lang.Class, java.lang.String);
diff --git a/src/test/examples/naming001/keep-rules-106.txt b/src/test/examples/naming001/keep-rules-106.txt
new file mode 100644
index 0000000..ca320eb
--- /dev/null
+++ b/src/test/examples/naming001/keep-rules-106.txt
@@ -0,0 +1,5 @@
+# Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+-applymapping mapping-106.txt
\ No newline at end of file
diff --git a/src/test/examples/naming001/mapping-106.txt b/src/test/examples/naming001/mapping-106.txt
new file mode 100644
index 0000000..1b3606d
--- /dev/null
+++ b/src/test/examples/naming001/mapping-106.txt
@@ -0,0 +1 @@
+naming001.E -> a.a:
\ No newline at end of file
diff --git a/src/test/examplesAndroidApi/instrumentationtest/InstrumentationTest.java b/src/test/examplesAndroidApi/instrumentationtest/InstrumentationTest.java
new file mode 100644
index 0000000..5541ccd
--- /dev/null
+++ b/src/test/examplesAndroidApi/instrumentationtest/InstrumentationTest.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package instrumentationtest;
+
+import android.content.Context;
+import android.test.InstrumentationTestCase;
+
+public class InstrumentationTest extends InstrumentationTestCase {
+ Context context;
+
+ public void setUp() throws Exception {
+ super.setUp();
+ context = getInstrumentation().getContext();
+ assertNotNull(context);
+ }
+
+ public void testSomething() {
+ assertEquals(false, true);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/AsmTestBase.java b/src/test/java/com/android/tools/r8/AsmTestBase.java
index 562abaf..db26fe6 100644
--- a/src/test/java/com/android/tools/r8/AsmTestBase.java
+++ b/src/test/java/com/android/tools/r8/AsmTestBase.java
@@ -12,19 +12,11 @@
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.io.ByteStreams;
-import java.io.File;
import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
import org.junit.Assert;
public class AsmTestBase extends TestBase {
@@ -41,9 +33,10 @@
exceptionClass);
}
- protected void ensureSameOutput(String main, int apiLevel, byte[]... classes) throws Exception {
+ protected void ensureSameOutput(String main, AndroidApiLevel apiLevel, byte[]... classes)
+ throws Exception {
AndroidApp app = buildAndroidApp(classes);
- Consumer<InternalOptions> setMinApiLevel = o -> o.minApiLevel = apiLevel;
+ Consumer<InternalOptions> setMinApiLevel = o -> o.minApiLevel = apiLevel.getLevel();
ProcessResult javaResult = runOnJava(main, classes);
ProcessResult d8Result = runOnArtRaw(compileWithD8(app, setMinApiLevel), main);
ProcessResult r8Result = runOnArtRaw(compileWithR8(app, setMinApiLevel), main);
@@ -105,20 +98,6 @@
ensureSameOutput(main, mergedApp, classes);
}
- protected byte[] asBytes(Class clazz) throws IOException {
- return ByteStreams
- .toByteArray(clazz.getResourceAsStream(clazz.getSimpleName() + ".class"));
- }
-
- protected AndroidApp buildAndroidApp(byte[]... classes) throws IOException {
- AndroidApp.Builder builder = AndroidApp.builder();
- for (byte[] clazz : classes) {
- builder.addClassProgramData(clazz, Origin.unknown());
- }
- builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.N.getLevel()));
- return builder.build();
- }
-
protected static AndroidApp readClassesAndAsmDump(List<Class> classes, List<byte[]> asmClasses)
throws IOException {
AndroidApp.Builder builder = AndroidApp.builder();
@@ -136,32 +115,6 @@
assertTrue(result.stderr, result.stderr.contains(exception.getCanonicalName()));
}
- protected ProcessResult runOnJava(String main, byte[]... classes) throws IOException {
- Path file = writeToZip(classes);
- return ToolHelper.runJavaNoVerify(file, main);
- }
-
- private Path writeToZip(byte[]... classes) throws IOException {
- DumpLoader dumpLoader = new DumpLoader();
- File result = temp.newFile();
- try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(result.toPath(),
- StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
- for (byte[] clazz : classes) {
- String name = dumpLoader.loadClass(clazz).getTypeName();
- ZipEntry zipEntry = new ZipEntry(DescriptorUtils.getPathFromJavaType(name));
- zipEntry.setSize(clazz.length);
- out.putNextEntry(zipEntry);
- out.write(clazz);
- out.closeEntry();
- }
- }
- return result.toPath();
- }
-
- protected static Class loadClassFromDump(byte[] dump) {
- return new DumpLoader().loadClass(dump);
- }
-
@FunctionalInterface
protected interface AsmDump {
@@ -170,7 +123,7 @@
protected static Class loadClassFromAsmClass(AsmDump asmDump) {
try {
- return new DumpLoader().loadClass(asmDump.dump());
+ return loadClassFromDump(asmDump.dump());
} catch (Exception e) {
throw new ClassFormatError(e.toString());
}
@@ -183,17 +136,4 @@
throw new ClassFormatError(e.toString());
}
}
-
- protected static byte[] getBytesFromJavaClass(Class clazz) throws IOException {
- return Files.readAllBytes(ToolHelper.getClassFileForTestClass(clazz));
- }
-
- private static class DumpLoader extends ClassLoader {
-
- @SuppressWarnings("deprecation")
- public Class loadClass(byte[] clazz) {
- return defineClass(clazz, 0, clazz.length);
- }
- }
-
}
diff --git a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
index 4cde5d9..6d0ef5a 100644
--- a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
+++ b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
@@ -65,7 +65,8 @@
"--main-dex-list",
mainDexList.toString(),
"--lib",
- ToolHelper.getAndroidJar(minApiLevel).toString(),
+ ToolHelper.getAndroidJar(
+ AndroidApiLevel.getAndroidApiLevel(minApiLevel)).toString(),
"--classpath",
lib1.toString(),
"--classpath",
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 05d303c..caa1d48 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.OffOrAuto;
@@ -49,8 +50,8 @@
}
@Override
- D8IncrementalTestRunner withMinApiLevel(int minApiLevel) {
- return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel));
+ D8IncrementalTestRunner withMinApiLevel(AndroidApiLevel minApiLevel) {
+ return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel.getLevel()));
}
@Override
@@ -175,8 +176,8 @@
} else {
throw new Unreachable("Unexpected output mode " + outputMode);
}
- addLibraryReference(builder, ToolHelper
- .getAndroidJar(androidJarVersion == null ? builder.getMinApiLevel() : androidJarVersion));
+ addLibraryReference(builder, ToolHelper.getAndroidJar(
+ androidJarVersion == null ? builder.getMinApiLevel() : androidJarVersion.getLevel()));
try {
return ToolHelper.runD8(builder, this::combinedOptionConsumer);
} catch (Unimplemented | CompilationError | InternalCompilerError re) {
@@ -324,7 +325,7 @@
@Override
protected Path buildDexThroughIntermediate(String packageName, Path input, OutputMode outputMode,
- int minApi, String... mainDexClasses) throws Throwable {
+ AndroidApiLevel minApi, String... mainDexClasses) throws Throwable {
// tests using this should already been skipped.
throw new Unreachable();
}
diff --git a/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
index 832a781..dc4a9b4 100644
--- a/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
@@ -66,7 +66,7 @@
@Test
public void dexPerClassFileWithDesugaringAndFolderClasspath() throws Throwable {
- int minAPILevel = AndroidApiLevel.K.getLevel();
+ AndroidApiLevel minAPILevel = AndroidApiLevel.K;
Path inputFile =
Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR, "interfacemethods" + JAR_EXTENSION);
Path tmpClassesDir = temp.newFolder().toPath();
@@ -78,7 +78,7 @@
{
D8Command.Builder command =
D8Command.builder()
- .setMinApiLevel(minAPILevel)
+ .setMinApiLevel(minAPILevel.getLevel())
.addLibraryFiles(androidJar)
.addProgramFiles(inputFile);
@@ -98,7 +98,7 @@
for (Path classFile : individualClassFiles) {
D8Command.Builder builder =
D8Command.builder()
- .setMinApiLevel(minAPILevel)
+ .setMinApiLevel(minAPILevel.getLevel())
.addLibraryFiles(androidJar)
.addClasspathFiles(tmpClassesDir)
.addProgramFiles(classFile);
@@ -111,7 +111,7 @@
});
individalDexes.add(individualResult.getDexProgramResourcesForTesting().get(0));
}
- AndroidApp mergedResult = mergeDexResources(minAPILevel, individalDexes);
+ AndroidApp mergedResult = mergeDexResources(minAPILevel.getLevel(), individalDexes);
assertTrue(Arrays.equals(
readResource(fullBuildResult.getDexProgramResourcesForTesting().get(0)),
diff --git a/src/test/java/com/android/tools/r8/D8NonLazyRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8NonLazyRunExamplesAndroidOTest.java
index 905d209..e16ecd1 100644
--- a/src/test/java/com/android/tools/r8/D8NonLazyRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8NonLazyRunExamplesAndroidOTest.java
@@ -22,8 +22,8 @@
@Override
void addLibraryReference(D8Command.Builder builder, Path location) throws IOException {
- builder.addLibraryFiles(ToolHelper
- .getAndroidJar(androidJarVersion == null ? builder.getMinApiLevel() : androidJarVersion));
+ builder.addLibraryFiles(ToolHelper.getAndroidJar(
+ androidJarVersion == null ? builder.getMinApiLevel() : androidJarVersion.getLevel()));
}
@Override
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
index edb73f7..b5802f4 100644
--- a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
@@ -27,8 +27,8 @@
}
@Override
- D8TestRunner withMinApiLevel(int minApiLevel) {
- return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel));
+ D8TestRunner withMinApiLevel(AndroidApiLevel minApiLevel) {
+ return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel.getLevel()));
}
D8TestRunner withClasspath(Path... classpath) {
@@ -43,7 +43,7 @@
}
builder.addLibraryFiles(
ToolHelper.getAndroidJar(
- androidJarVersion == null ? builder.getMinApiLevel() : androidJarVersion));
+ androidJarVersion == null ? builder.getMinApiLevel() : androidJarVersion.getLevel()));
builder.addProgramFiles(inputFile);
try {
ToolHelper.runD8(builder, this::combinedOptionConsumer);
@@ -70,7 +70,7 @@
D8TestRunner lib1 =
test("testDefaultInInterfaceWithoutDesugaring", "desugaringwithmissingclasslib1", "N/A")
.withInterfaceMethodDesugaring(OffOrAuto.Off)
- .withMinApiLevel(AndroidApiLevel.K.getLevel());
+ .withMinApiLevel(AndroidApiLevel.K);
try {
lib1.build();
@@ -87,7 +87,7 @@
D8TestRunner lib1 =
test("desugaringwithmissingclasslib1", "desugaringwithmissingclasslib1", "N/A")
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
- .withMinApiLevel(AndroidApiLevel.K.getLevel());
+ .withMinApiLevel(AndroidApiLevel.K);
lib1.build();
// lib2: interface B extends A { default String foo() { return "B"; } }
@@ -96,7 +96,7 @@
test("desugaringwithmissingclasslib2", "desugaringwithmissingclasslib2", "N/A")
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
.withClasspath(lib1.getInputJar())
- .withMinApiLevel(AndroidApiLevel.K.getLevel());
+ .withMinApiLevel(AndroidApiLevel.K);
lib2.build();
// test: class ImplementMethodsWithDefault implements A, B {} should get its foo implementation
@@ -107,7 +107,7 @@
test("desugaringwithmissingclasstest1", "desugaringwithmissingclasstest1", "N/A")
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
.withClasspath(lib1.getInputJar())
- .withMinApiLevel(AndroidApiLevel.K.getLevel());
+ .withMinApiLevel(AndroidApiLevel.K);
test.build();
// TODO check compilation warnings are correctly reported
@@ -116,7 +116,7 @@
@Test
public void testMissingInterfaceDesugared2AndroidK() throws Throwable {
- int minApi = AndroidApiLevel.K.getLevel();
+ AndroidApiLevel minApi = AndroidApiLevel.K;
// lib1: interface A { default String foo() { return "A"; } }
D8TestRunner lib1 =
@@ -170,7 +170,7 @@
@Test
public void testMissingInterfaceDesugared2AndroidO() throws Throwable {
- int minApi = AndroidApiLevel.O.getLevel();
+ AndroidApiLevel minApi = AndroidApiLevel.O;
// lib1: interface A { default String foo() { return "A"; } }
D8TestRunner lib1 =
test("desugaringwithmissingclasslib1", "desugaringwithmissingclasslib1", "N/A")
@@ -218,7 +218,7 @@
@Test
public void testCallToMissingSuperInterfaceDesugaredAndroidK() throws Throwable {
- int minApi = AndroidApiLevel.K.getLevel();
+ AndroidApiLevel minApi = AndroidApiLevel.K;
// lib1: interface A { default String foo() { return "A"; } }
D8TestRunner lib1 =
test("desugaringwithmissingclasslib1", "desugaringwithmissingclasslib1", "N/A")
@@ -271,7 +271,7 @@
@Test
public void testCallToMissingSuperInterfaceDesugaredAndroidO() throws Throwable {
- int minApi = AndroidApiLevel.O.getLevel();
+ AndroidApiLevel minApi = AndroidApiLevel.O;
// lib1: interface A { default String foo() { return "A"; } }
D8TestRunner lib1 =
test("desugaringwithmissingclasslib1", "desugaringwithmissingclasslib1", "N/A")
@@ -318,7 +318,7 @@
@Test
public void testMissingSuperDesugaredAndroidK() throws Throwable {
- int minApi = AndroidApiLevel.K.getLevel();
+ AndroidApiLevel minApi = AndroidApiLevel.K;
// lib1: interface A { default String foo() { return "A"; } }
D8TestRunner lib1 =
@@ -366,7 +366,7 @@
@Test
public void testMissingSuperDesugaredAndroidO() throws Throwable {
- int minApi = AndroidApiLevel.O.getLevel();
+ AndroidApiLevel minApi = AndroidApiLevel.O;
// lib1: interface A { default String foo() { return "A"; } }
D8TestRunner lib1 =
@@ -416,7 +416,7 @@
@Test
public void testMissingSuperDesugaredWithProgramCrossImplementationAndroidK() throws Throwable {
- int minApi = AndroidApiLevel.K.getLevel();
+ AndroidApiLevel minApi = AndroidApiLevel.K;
// lib1: interface A { default String foo() { return "A"; } }
// interface A2 { default String foo2() { return "A2"; } }
@@ -461,7 +461,7 @@
@Test
public void testMissingSuperDesugaredWithClasspathCrossImplementationAndroidK() throws Throwable {
- int minApi = AndroidApiLevel.K.getLevel();
+ AndroidApiLevel minApi = AndroidApiLevel.K;
// lib1: interface A { default String foo() { return "A"; } }
// interface A2 { default String foo2() { return "A2"; } }
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java
index b8e9675..0ae485a 100644
--- a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java
@@ -37,7 +37,7 @@
}
// TODO(mikaelpeltier) Add new android.jar build from aosp and use it
builder
- .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.O.getLevel()))
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.O))
.addProgramFiles(inputFile)
.setOutput(out, OutputMode.DexIndexed);
try {
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/D8RunExamplesJava9Test.java
index 71ffde2..ece7f31 100644
--- a/src/test/java/com/android/tools/r8/D8RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesJava9Test.java
@@ -37,7 +37,7 @@
}
// TODO(mikaelpeltier) Add new android.jar build from aosp and use it
builder
- .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel()))
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.addProgramFiles(inputFile)
.setOutput(out, OutputMode.DexIndexed);
try {
diff --git a/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java b/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
index 8e8ca36..6533e08 100644
--- a/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
@@ -8,6 +8,7 @@
import static org.junit.Assert.assertFalse;
import com.android.tools.r8.naming.ProguardMapSupplier;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.VersionProperties;
import java.nio.file.Paths;
import java.util.HashSet;
@@ -17,15 +18,15 @@
public class ProguardMapMarkerTest {
@Test
public void proguardMapMarkerTest24() throws CompilationFailedException {
- proguardMapMarkerTest(24);
+ proguardMapMarkerTest(AndroidApiLevel.N);
}
@Test
public void proguardMapMarkerTest26() throws CompilationFailedException {
- proguardMapMarkerTest(26);
+ proguardMapMarkerTest(AndroidApiLevel.O);
}
- private void proguardMapMarkerTest(int minApiLevel) throws CompilationFailedException {
+ private void proguardMapMarkerTest(AndroidApiLevel minApiLevel) throws CompilationFailedException {
String classFile = ToolHelper.EXAMPLES_BUILD_DIR + "classes/trivial/Trivial.class";
R8.run(
R8Command.builder()
@@ -43,10 +44,10 @@
public void finished(DiagnosticsHandler handler) {}
})
.addLibraryFiles(ToolHelper.getAndroidJar(minApiLevel))
- .setMinApiLevel(minApiLevel)
+ .setMinApiLevel(minApiLevel.getLevel())
.setProguardMapConsumer(
(proguardMap, handler) -> {
- verifyMarkers(proguardMap, minApiLevel);
+ verifyMarkers(proguardMap, minApiLevel.getLevel());
})
.build());
}
diff --git a/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
index 59536de..28fe5dc 100644
--- a/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
+++ b/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
@@ -22,7 +22,7 @@
static final Path JAR = Paths.get("tests", "r8_api_usage_sample.jar");
static final String MAIN = "com.android.tools.apiusagesample.R8ApiUsageSample";
- static final int MIN_API = AndroidApiLevel.K.getLevel();
+ static final AndroidApiLevel MIN_API = AndroidApiLevel.K;
@Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
@@ -54,7 +54,7 @@
"--output",
temp.newFolder().toString(),
"--min-api",
- Integer.toString(MIN_API),
+ Integer.toString(MIN_API.getLevel()),
"--pg-conf",
pgConf.toString(),
"--main-dex-rules",
diff --git a/src/test/java/com/android/tools/r8/R8IgnoreMissingClassesTest.java b/src/test/java/com/android/tools/r8/R8IgnoreMissingClassesTest.java
index 697158a..83bc302 100644
--- a/src/test/java/com/android/tools/r8/R8IgnoreMissingClassesTest.java
+++ b/src/test/java/com/android/tools/r8/R8IgnoreMissingClassesTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8;
import com.android.tools.r8.origin.EmbeddedOrigin;
+import com.android.tools.r8.utils.AndroidApiLevel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
@@ -11,14 +12,14 @@
public class R8IgnoreMissingClassesTest {
- private static final int MIN_API = 26;
+ private static final AndroidApiLevel MIN_API = AndroidApiLevel.O;
private static final Path EXAMPLE = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "usestdlib.jar");
private static final Path LIBRARY = ToolHelper.getAndroidJar(MIN_API);
private R8Command.Builder config() {
return R8Command.builder()
.addProgramFiles(EXAMPLE)
- .setMinApiLevel(MIN_API)
+ .setMinApiLevel(MIN_API.getLevel())
.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 2278d67..32c0175 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -105,46 +105,46 @@
"third_party/gradle/gradle/lib/plugins/hamcrest-core-1.3.jar";
// Test that required to set min-api to a specific value.
- private static Map<String, Integer> needMinSdkVersion =
- new ImmutableMap.Builder<String, Integer>()
+ private static Map<String, AndroidApiLevel> needMinSdkVersion =
+ new ImmutableMap.Builder<String, AndroidApiLevel>()
// Android O
- .put("952-invoke-custom", AndroidApiLevel.O.getLevel())
- .put("952-invoke-custom-kinds", AndroidApiLevel.O.getLevel())
- .put("953-invoke-polymorphic-compiler", AndroidApiLevel.O.getLevel())
- .put("957-methodhandle-transforms", AndroidApiLevel.O.getLevel())
- .put("958-methodhandle-stackframe", AndroidApiLevel.O.getLevel())
- .put("959-invoke-polymorphic-accessors", AndroidApiLevel.O.getLevel())
- .put("979-const-method-handle", AndroidApiLevel.P.getLevel())
- .put("990-method-handle-and-mr", AndroidApiLevel.O.getLevel())
+ .put("952-invoke-custom", AndroidApiLevel.O)
+ .put("952-invoke-custom-kinds", AndroidApiLevel.O)
+ .put("953-invoke-polymorphic-compiler", AndroidApiLevel.O)
+ .put("957-methodhandle-transforms", AndroidApiLevel.O)
+ .put("958-methodhandle-stackframe", AndroidApiLevel.O)
+ .put("959-invoke-polymorphic-accessors", AndroidApiLevel.O)
+ .put("979-const-method-handle", AndroidApiLevel.P)
+ .put("990-method-handle-and-mr", AndroidApiLevel.O)
// Test intentionally asserts presence of bridge default methods desugar removes.
- .put("044-proxy", AndroidApiLevel.N.getLevel())
+ .put("044-proxy", AndroidApiLevel.N)
// Test intentionally asserts absence of default interface method in a class.
- .put("048-reflect-v8", AndroidApiLevel.N.getLevel())
+ .put("048-reflect-v8", AndroidApiLevel.N)
// Uses default interface methods.
- .put("162-method-resolution", AndroidApiLevel.N.getLevel())
- .put("616-cha-interface-default", AndroidApiLevel.N.getLevel())
- .put("1910-transform-with-default", AndroidApiLevel.N.getLevel())
+ .put("162-method-resolution", AndroidApiLevel.N)
+ .put("616-cha-interface-default", AndroidApiLevel.N)
+ .put("1910-transform-with-default", AndroidApiLevel.N)
// Interface initializer is not triggered after desugaring.
- .put("962-iface-static", AndroidApiLevel.N.getLevel())
+ .put("962-iface-static", AndroidApiLevel.N)
// Interface initializer is not triggered after desugaring.
- .put("964-default-iface-init-gen", AndroidApiLevel.N.getLevel())
+ .put("964-default-iface-init-gen", AndroidApiLevel.N)
// AbstractMethodError (for method not implemented in class) instead of
// IncompatibleClassChangeError (for conflict of default interface methods).
- .put("968-default-partial-compile-gen", AndroidApiLevel.N.getLevel())
+ .put("968-default-partial-compile-gen", AndroidApiLevel.N)
// NoClassDefFoundError (for companion class) instead of NoSuchMethodError.
- .put("970-iface-super-resolution-gen", AndroidApiLevel.N.getLevel())
+ .put("970-iface-super-resolution-gen", AndroidApiLevel.N)
// NoClassDefFoundError (for companion class) instead of AbstractMethodError.
- .put("971-iface-super", AndroidApiLevel.N.getLevel())
+ .put("971-iface-super", AndroidApiLevel.N)
// Test for miranda methods is not relevant for desugaring scenario.
- .put("972-default-imt-collision", AndroidApiLevel.N.getLevel())
+ .put("972-default-imt-collision", AndroidApiLevel.N)
// Uses default interface methods.
- .put("972-iface-super-multidex", AndroidApiLevel.N.getLevel())
+ .put("972-iface-super-multidex", AndroidApiLevel.N)
// java.util.Objects is missing and test has default methods.
- .put("973-default-multidex", AndroidApiLevel.N.getLevel())
+ .put("973-default-multidex", AndroidApiLevel.N)
// a.klass.that.does.not.Exist is missing and test has default methods.
- .put("974-verify-interface-super", AndroidApiLevel.N.getLevel())
+ .put("974-verify-interface-super", AndroidApiLevel.N)
// Desugaring of interface private methods is not yet supported.
- .put("975-iface-private", AndroidApiLevel.N.getLevel())
+ .put("975-iface-private", AndroidApiLevel.N)
.build();
// Tests that timeout when run with Art.
@@ -1390,13 +1390,13 @@
.setMode(mode)
.addProgramFiles(ListUtils.map(fileNames, Paths::get))
.setOutput(Paths.get(resultPath), OutputMode.DexIndexed);
- Integer minSdkVersion = needMinSdkVersion.get(name);
+ AndroidApiLevel minSdkVersion = needMinSdkVersion.get(name);
if (minSdkVersion != null) {
- builder.setMinApiLevel(minSdkVersion);
+ builder.setMinApiLevel(minSdkVersion.getLevel());
builder.addLibraryFiles(ToolHelper.getAndroidJar(minSdkVersion));
} else {
builder
- .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.getDefault().getLevel()));
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.getDefault()));
}
D8.run(builder.build());
break;
@@ -1409,12 +1409,12 @@
.setOutput(Paths.get(resultPath), OutputMode.DexIndexed);
// Add program files directly to the underlying app to avoid errors on DEX inputs.
ToolHelper.getAppBuilder(builder).addProgramFiles(ListUtils.map(fileNames, Paths::get));
- Integer minSdkVersion = needMinSdkVersion.get(name);
+ AndroidApiLevel minSdkVersion = needMinSdkVersion.get(name);
if (minSdkVersion != null) {
- builder.setMinApiLevel(minSdkVersion);
+ builder.setMinApiLevel(minSdkVersion.getLevel());
ToolHelper.addFilteredAndroidJar(builder, minSdkVersion);
} else {
- ToolHelper.addFilteredAndroidJar(builder, AndroidApiLevel.getDefault().getLevel());
+ ToolHelper.addFilteredAndroidJar(builder, AndroidApiLevel.getDefault());
}
if (keepRulesFile != null) {
builder.addProguardConfigurationFiles(Paths.get(keepRulesFile));
@@ -1424,7 +1424,7 @@
builder.build(),
options -> {
if (disableInlining) {
- options.inlineAccessors = false;
+ options.enableInlining = false;
}
options.lineNumberOptimization = LineNumberOptimization.OFF;
// Some tests actually rely on missing classes for what they test.
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 751c34e..b6b9ba8 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -48,7 +48,7 @@
@Test
public void invokeCustomWithShrinking() throws Throwable {
test("invokecustom-with-shrinking", "invokecustom", "InvokeCustom")
- .withMinApiLevel(AndroidApiLevel.O.getLevel())
+ .withMinApiLevel(AndroidApiLevel.O)
.withBuilderTransformation(builder ->
builder.addProguardConfigurationFiles(
Paths.get(ToolHelper.EXAMPLES_ANDROID_O_DIR, "invokecustom/keep-rules.txt")))
@@ -62,8 +62,8 @@
}
@Override
- R8TestRunner withMinApiLevel(int minApiLevel) {
- return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel));
+ R8TestRunner withMinApiLevel(AndroidApiLevel minApiLevel) {
+ return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel.getLevel()));
}
@Override
@@ -72,8 +72,8 @@
for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
builder = transformation.apply(builder);
}
- builder.addLibraryFiles(ToolHelper
- .getAndroidJar(androidJarVersion == null ? builder.getMinApiLevel() : androidJarVersion));
+ builder.addLibraryFiles(ToolHelper.getAndroidJar(
+ androidJarVersion == null ? builder.getMinApiLevel() : androidJarVersion.getLevel()));
R8Command command = builder.addProgramFiles(inputFile).build();
ToolHelper.runR8(command, this::combinedOptionConsumer);
}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
index becfa6d..f4665a9 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
@@ -72,7 +72,7 @@
builder = transformation.apply(builder);
}
// TODO(mikaelpeltier) Add new android.jar build from aosp and use it
- builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.O.getLevel()));
+ builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.O));
R8Command command =
builder.addProgramFiles(inputFile).setOutput(out, OutputMode.DexIndexed).build();
ToolHelper.runR8(command, this::combinedOptionConsumer);
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
index 1a307a5..670e6be 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
@@ -28,7 +28,7 @@
builder = transformation.apply(builder);
}
// TODO(mikaelpeltier) Add new android.jar build from aosp and use it
- builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel()));
+ builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P));
R8Command command =
builder.addProgramFiles(inputFile).setOutput(out, OutputMode.DexIndexed).build();
ToolHelper.runR8(command, this::combinedOptionConsumer);
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index fa507b0..1bbcfdc 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -57,7 +57,7 @@
final String packageName;
final String mainClass;
- Integer androidJarVersion = null;
+ AndroidApiLevel androidJarVersion = null;
final List<Consumer<InternalOptions>> optionConsumers = new ArrayList<>();
final List<Consumer<DexInspector>> dexInspectorChecks = new ArrayList<>();
@@ -162,9 +162,9 @@
execute(testName, qualifiedMainClass, new Path[]{inputFile}, new Path[]{out});
}
- abstract C withMinApiLevel(int minApiLevel);
+ abstract C withMinApiLevel(AndroidApiLevel minApiLevel);
- C withAndroidJar(int androidJarVersion) {
+ C withAndroidJar(AndroidApiLevel androidJarVersion) {
assert this.androidJarVersion == null;
this.androidJarVersion = androidJarVersion;
return self();
@@ -291,56 +291,56 @@
@Test
public void stringConcat() throws Throwable {
test("stringconcat", "stringconcat", "StringConcat")
- .withMinApiLevel(AndroidApiLevel.K.getLevel())
+ .withMinApiLevel(AndroidApiLevel.K)
.run();
}
@Test
public void invokeCustom() throws Throwable {
test("invokecustom", "invokecustom", "InvokeCustom")
- .withMinApiLevel(AndroidApiLevel.O.getLevel())
+ .withMinApiLevel(AndroidApiLevel.O)
.run();
}
@Test
public void invokeCustom2() throws Throwable {
test("invokecustom2", "invokecustom2", "InvokeCustom")
- .withMinApiLevel(AndroidApiLevel.O.getLevel())
+ .withMinApiLevel(AndroidApiLevel.O)
.run();
}
@Test
public void invokeCustomErrorDueToMinSdk() throws Throwable {
test("invokecustom-error-due-to-min-sdk", "invokecustom", "InvokeCustom")
- .withMinApiLevel(25)
+ .withMinApiLevel(AndroidApiLevel.N_MR1)
.run();
}
@Test
public void invokePolymorphic() throws Throwable {
test("invokepolymorphic", "invokepolymorphic", "InvokePolymorphic")
- .withMinApiLevel(AndroidApiLevel.O.getLevel())
+ .withMinApiLevel(AndroidApiLevel.O)
.run();
}
@Test
public void invokePolymorphicErrorDueToMinSdk() throws Throwable {
test("invokepolymorphic-error-due-to-min-sdk", "invokepolymorphic", "InvokePolymorphic")
- .withMinApiLevel(25)
+ .withMinApiLevel(AndroidApiLevel.N_MR1)
.run();
}
@Test
public void lambdaDesugaring() throws Throwable {
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
- .withMinApiLevel(AndroidApiLevel.K.getLevel())
+ .withMinApiLevel(AndroidApiLevel.K)
.run();
}
@Test
public void lambdaDesugaringNPlus() throws Throwable {
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
- .withMinApiLevel(AndroidApiLevel.K.getLevel())
+ .withMinApiLevel(AndroidApiLevel.K)
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
.run();
}
@@ -348,8 +348,8 @@
@Test
public void desugarDefaultMethodInAndroidJar25() throws Throwable {
test("DefaultMethodInAndroidJar25", "desugaringwithandroidjar25", "DefaultMethodInAndroidJar25")
- .withMinApiLevel(AndroidApiLevel.K.getLevel())
- .withAndroidJar(AndroidApiLevel.O.getLevel())
+ .withMinApiLevel(AndroidApiLevel.K)
+ .withAndroidJar(AndroidApiLevel.O)
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
.run();
}
@@ -357,8 +357,8 @@
@Test
public void desugarStaticMethodInAndroidJar25() throws Throwable {
test("StaticMethodInAndroidJar25", "desugaringwithandroidjar25", "StaticMethodInAndroidJar25")
- .withMinApiLevel(AndroidApiLevel.K.getLevel())
- .withAndroidJar(AndroidApiLevel.O.getLevel())
+ .withMinApiLevel(AndroidApiLevel.K)
+ .withAndroidJar(AndroidApiLevel.O)
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
.run();
}
@@ -366,14 +366,14 @@
@Test
public void lambdaDesugaringValueAdjustments() throws Throwable {
test("lambdadesugaring-value-adjustments", "lambdadesugaring", "ValueAdjustments")
- .withMinApiLevel(AndroidApiLevel.K.getLevel())
+ .withMinApiLevel(AndroidApiLevel.K)
.run();
}
@Test
public void paramNames() throws Throwable {
test("paramnames", "paramnames", "ParameterNames")
- .withMinApiLevel(AndroidApiLevel.O.getLevel())
+ .withMinApiLevel(AndroidApiLevel.O)
.run();
}
@@ -469,7 +469,7 @@
int expectedMainDexListSize,
String... mainDexClasses)
throws Throwable {
- int minApi = AndroidApiLevel.K.getLevel();
+ AndroidApiLevel minApi = AndroidApiLevel.K;
// Full build, will be used as reference.
TestRunner<?> full =
@@ -514,7 +514,7 @@
String packageName,
Path input,
OutputMode outputMode,
- int minApi,
+ AndroidApiLevel minApi,
String... mainDexClasses)
throws Throwable {
Path intermediateDex =
@@ -522,7 +522,7 @@
// Build intermediate with D8.
D8Command.Builder command = D8Command.builder()
.setOutput(intermediateDex, outputMode)
- .setMinApiLevel(minApi)
+ .setMinApiLevel(minApi.getLevel())
.addLibraryFiles(ToolHelper.getAndroidJar(minApi))
.setIntermediate(true)
.addProgramFiles(input);
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index cf13f41..77f9ad9 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -7,6 +7,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.graph.DexCode;
@@ -14,6 +15,7 @@
import com.android.tools.r8.graph.SmaliWriter;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -32,6 +34,7 @@
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
@@ -43,6 +46,7 @@
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
@@ -61,6 +65,18 @@
}
/**
+ * Build an AndroidApp with the specified test classes as byte array.
+ */
+ protected AndroidApp buildAndroidApp(byte[]... classes) throws IOException {
+ AndroidApp.Builder builder = AndroidApp.builder();
+ for (byte[] clazz : classes) {
+ builder.addClassProgramData(clazz, Origin.unknown());
+ }
+ builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.N.getLevel()));
+ return builder.build();
+ }
+
+ /**
* Build an AndroidApp with the specified test classes.
*/
protected static AndroidApp readClasses(Class... classes) throws IOException {
@@ -95,6 +111,22 @@
return builder.build();
}
+ protected static AndroidApp readClassesAndAndriodJar(List<Class> programClasses)
+ throws IOException {
+ return readClassesAndAndriodJar(programClasses, ToolHelper.getMinApiLevelForDexVm());
+ }
+
+ protected static AndroidApp readClassesAndAndriodJar(
+ List<Class> programClasses, AndroidApiLevel androidLibrary)
+ throws IOException {
+ AndroidApp.Builder builder = AndroidApp.builder();
+ for (Class clazz : programClasses) {
+ builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
+ }
+ builder.addLibraryFiles(ToolHelper.getAndroidJar(androidLibrary));
+ return builder.build();
+ }
+
/**
* Create a temporary JAR file containing the specified test classes.
*/
@@ -348,12 +380,20 @@
}
/**
+ * Run application on the specified version of Art with the specified main class.
+ */
+ protected ProcessResult runOnArtRaw(AndroidApp app, String mainClass, DexVm version)
+ throws IOException {
+ Path out = File.createTempFile("junit", ".zip", temp.getRoot()).toPath();
+ app.writeToZip(out, OutputMode.DexIndexed);
+ return ToolHelper.runArtRaw(ImmutableList.of(out.toString()), mainClass, null, version);
+ }
+
+ /**
* Run application on Art with the specified main class.
*/
protected ProcessResult runOnArtRaw(AndroidApp app, String mainClass) throws IOException {
- Path out = File.createTempFile("junit", ".zip", temp.getRoot()).toPath();
- app.writeToZip(out, OutputMode.DexIndexed);
- return ToolHelper.runArtRaw(ImmutableList.of(out.toString()), mainClass, null);
+ return runOnArtRaw(app, mainClass, null);
}
/**
@@ -387,7 +427,17 @@
/**
* Run application on Art with the specified main class and provided arguments.
*/
- protected String runOnArt(AndroidApp app, String mainClass, List<String> args) throws IOException {
+ protected String runOnArt(AndroidApp app, String mainClass, List<String> args)
+ throws IOException {
+ return runOnArt(app, mainClass, args, null);
+ }
+
+ /**
+ * Run application on Art with the specified main class, provided arguments, and specified VM
+ * version.
+ */
+ protected String runOnArt(AndroidApp app, String mainClass, List<String> args, DexVm dexVm)
+ throws IOException {
Path out = File.createTempFile("junit", ".zip", temp.getRoot()).toPath();
app.writeToZip(out, OutputMode.DexIndexed);
return ToolHelper.runArtNoVerificationErrors(
@@ -397,7 +447,8 @@
for (String arg : args) {
builder.appendProgramArgument(arg);
}
- });
+ },
+ dexVm);
}
/**
@@ -429,6 +480,38 @@
return result.stdout;
}
+ protected ProcessResult runOnJava(String main, byte[]... classes) throws IOException {
+ Path file = writeToZip(classes);
+ return ToolHelper.runJavaNoVerify(file, main);
+ }
+
+ private Path writeToZip(byte[]... classes) throws IOException {
+ File result = temp.newFile();
+ try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(result.toPath(),
+ StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
+ for (byte[] clazz : classes) {
+ String name = loadClassFromDump(clazz).getTypeName();
+ ZipEntry zipEntry = new ZipEntry(DescriptorUtils.getPathFromJavaType(name));
+ zipEntry.setSize(clazz.length);
+ out.putNextEntry(zipEntry);
+ out.write(clazz);
+ out.closeEntry();
+ }
+ }
+ return result.toPath();
+ }
+
+ protected static Class loadClassFromDump(byte[] dump) {
+ return new DumpLoader().loadClass(dump);
+ }
+
+ private static class DumpLoader extends ClassLoader {
+
+ @SuppressWarnings("deprecation")
+ public Class loadClass(byte[] clazz) {
+ return defineClass(clazz, 0, clazz.length);
+ }
+ }
/**
* Disassemble the content of an application. Only works for an application with only dex code.
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 7883df8..cc325e2 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -88,7 +88,7 @@
public static final String DEFAULT_PROGUARD_MAP_FILE = "proguard.map";
private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
- private static final int DEFAULT_MIN_SDK = AndroidApiLevel.I.getLevel();
+ private static final AndroidApiLevel DEFAULT_MIN_SDK = AndroidApiLevel.I;
private static final String PROGUARD = "third_party/proguard/proguard5.2.1/bin/proguard.sh";
@@ -440,6 +440,10 @@
private static final String ANGLER_DIR = TOOLS + "/linux/art/product/angler";
private static final String ANGLER_BOOT_IMAGE = ANGLER_DIR + "/system/framework/boot.art";
+ public static byte[] getClassAsBytes(Class clazz) throws IOException {
+ return ByteStreams.toByteArray(clazz.getResourceAsStream(clazz.getSimpleName() + ".class"));
+ }
+
public static String getArtDir(DexVm version) {
String dir = ART_DIRS.get(version);
if (dir == null) {
@@ -479,38 +483,49 @@
}
public static Path getDefaultAndroidJar() {
- return getAndroidJar(AndroidApiLevel.getDefault().getLevel());
+ return getAndroidJar(AndroidApiLevel.getDefault());
}
- public static Path getAndroidJar(int minSdkVersion) {
- if (minSdkVersion == AndroidApiLevel.P.getLevel()) {
+ public static Path getAndroidJar(int apiLevel) {
+ return getAndroidJar(AndroidApiLevel.getAndroidApiLevel(apiLevel));
+ }
+
+ public static Path getAndroidJar(AndroidApiLevel apiLevel) {
+ if (apiLevel == AndroidApiLevel.P) {
// TODO(mikaelpeltier) Android P does not yet have his android.jar use the O version
- minSdkVersion = AndroidApiLevel.O.getLevel();
+ apiLevel = AndroidApiLevel.O;
}
String jar = String.format(
ANDROID_JAR_PATTERN,
- minSdkVersion == AndroidApiLevel.getDefault().getLevel() ? DEFAULT_MIN_SDK : minSdkVersion);
+ (apiLevel == AndroidApiLevel.getDefault() ? DEFAULT_MIN_SDK : apiLevel).getLevel());
assert Files.exists(Paths.get(jar))
- : "Expected android jar to exist for API level " + minSdkVersion;
+ : "Expected android jar to exist for API level " + apiLevel;
return Paths.get(jar);
}
- public static Path getJdwpTestsCfJarPath(int minSdk) {
- if (minSdk >= AndroidApiLevel.N.getLevel()) {
+ public static Path getJdwpTestsCfJarPath(AndroidApiLevel minSdk) {
+ if (minSdk.getLevel() >= AndroidApiLevel.N.getLevel()) {
return Paths.get("third_party", "jdwp-tests", "apache-harmony-jdwp-tests-host.jar");
} else {
return Paths.get(ToolHelper.BUILD_DIR, "libs", "jdwp-tests-preN.jar");
}
}
- public static Path getJdwpTestsDexJarPath(int minSdk) {
- if (minSdk >= AndroidApiLevel.N.getLevel()) {
+ public static Path getJdwpTestsDexJarPath(AndroidApiLevel minSdk) {
+ if (minSdk.getLevel() >= AndroidApiLevel.N.getLevel()) {
return Paths.get("third_party", "jdwp-tests", "apache-harmony-jdwp-tests-hostdex.jar");
} else {
return Paths.get(ToolHelper.BUILD_DIR, "libs", "jdwp-tests-preN-dex.jar");
}
}
+ /**
+ * Get the junit jar bundled with the framework.
+ */
+ public static Path getFrameworkJunitJarPath(DexVm version) {
+ return Paths.get(getArtDir(version), "framework", "junit.jar");
+ }
+
static class RetainedTemporaryFolder extends TemporaryFolder {
RetainedTemporaryFolder(java.io.File parentFolder) {
@@ -582,20 +597,24 @@
}
}
- public static int getMinApiLevelForDexVm(DexVm dexVm) {
+ public static AndroidApiLevel getMinApiLevelForDexVm() {
+ return getMinApiLevelForDexVm(ToolHelper.getDexVm());
+ }
+
+ public static AndroidApiLevel getMinApiLevelForDexVm(DexVm dexVm) {
switch (dexVm.version) {
case DEFAULT:
- return AndroidApiLevel.O.getLevel();
+ return AndroidApiLevel.O;
case V7_0_0:
- return AndroidApiLevel.N.getLevel();
+ return AndroidApiLevel.N;
case V6_0_1:
- return AndroidApiLevel.M.getLevel();
+ return AndroidApiLevel.M;
case V5_1_1:
- return AndroidApiLevel.L_MR1.getLevel();
+ return AndroidApiLevel.L_MR1;
case V4_4_4:
- return AndroidApiLevel.K.getLevel();
+ return AndroidApiLevel.K;
case V4_0_4:
- return AndroidApiLevel.I_MR1.getLevel();
+ return AndroidApiLevel.I_MR1;
default:
throw new Unreachable("Missing min api level for dex vm " + dexVm);
}
@@ -769,7 +788,7 @@
// Add the android library matching the minsdk. We filter out junit and testing classes
// from the android jar to avoid duplicate classes in art and jctf tests.
AndroidApp.Builder builder = AndroidApp.builder(app);
- addFilteredAndroidJar(builder, command.getMinApiLevel());
+ addFilteredAndroidJar(builder, AndroidApiLevel.getAndroidApiLevel(command.getMinApiLevel()));
app = builder.build();
}
InternalOptions options = command.getInternalOptions();
@@ -781,15 +800,15 @@
return compatSink.build();
}
- public static void addFilteredAndroidJar(BaseCommand.Builder builder, int minSdkVersion)
+ public static void addFilteredAndroidJar(BaseCommand.Builder builder, AndroidApiLevel apiLevel)
throws IOException {
- addFilteredAndroidJar(getAppBuilder(builder), minSdkVersion);
+ addFilteredAndroidJar(getAppBuilder(builder), apiLevel);
}
- public static void addFilteredAndroidJar(AndroidApp.Builder builder, int minSdkVersion)
+ public static void addFilteredAndroidJar(AndroidApp.Builder builder, AndroidApiLevel apiLevel)
throws IOException {
builder.addFilteredLibraryArchives(Collections.singletonList(
- new FilteredClassPath(getAndroidJar(minSdkVersion),
+ new FilteredClassPath(getAndroidJar(apiLevel),
ImmutableList.of("!junit/**", "!android/test/**"))));
}
@@ -1226,4 +1245,19 @@
options,
null);
}
+
+ public enum KotlinTargetVersion {
+ JAVA_6("JAVA_6"),
+ JAVA_8("JAVA_8");
+
+ private final String folderName;
+
+ KotlinTargetVersion(String folderName) {
+ this.folderName = folderName;
+ }
+
+ public String getFolderName() {
+ return folderName;
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/VmTestRunner.java b/src/test/java/com/android/tools/r8/VmTestRunner.java
index f377543..978cbe7 100644
--- a/src/test/java/com/android/tools/r8/VmTestRunner.java
+++ b/src/test/java/com/android/tools/r8/VmTestRunner.java
@@ -19,6 +19,16 @@
public class VmTestRunner extends BlockJUnit4ClassRunner {
/**
+ * Ignores the test for all VM versions up to {@link #value()}.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.METHOD, ElementType.TYPE})
+ public @interface IgnoreIfVmOlderThan {
+
+ DexVm.Version value();
+ }
+
+ /**
* Ignores the test for all VM versions up to and includion {@link #value()}.
*/
@Retention(RetentionPolicy.RUNTIME)
@@ -65,6 +75,12 @@
return true;
}
DexVm.Version currentVersion = ToolHelper.getDexVm().getVersion();
+ IgnoreIfVmOlderThan ignoreIfVmOlderThan =
+ child.getAnnotation(IgnoreIfVmOlderThan.class);
+ if (ignoreIfVmOlderThan != null
+ && !currentVersion.isAtLeast(ignoreIfVmOlderThan.value())) {
+ return true;
+ }
IgnoreIfVmOlderOrEqualThan ignoreIfVmOlderOrEqualThan =
child.getAnnotation(IgnoreIfVmOlderOrEqualThan.class);
if (ignoreIfVmOlderOrEqualThan != null
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
index 18e17f9..34b92b0 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
@@ -25,7 +25,7 @@
R8Command.Builder builder = R8Command.builder();
builder.addProgramFiles(ToolHelper.getClassFilesForTestPackage(A.class.getPackage()));
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm()));
+ builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
// Note: we use '-checkdiscard' to indirectly check that the access relaxation is
// done which leads to inlining of all pB*** methods so they are removed. Without
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
index 585b949..55ff3a2 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
@@ -39,7 +39,9 @@
SimpleDataAdapter.class.getMethod("registerObserver", DataAdapter.Observer.class);
MethodSubject subject = inspector.method(registerObserver);
assertTrue(subject.isPresent());
- assertTrue(subject.isBridge());
+ // The method is there, but it might be unmarked as a bridge if
+ // another method is inlined into it.
+ // assertTrue(subject.isBridge());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/Main.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/Main.java
index 4d80056..44cbe28 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/Main.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/Main.java
@@ -7,11 +7,21 @@
// Reduced test case from code where removal of bridge methods caused failure.
public class Main {
- public static void registerObserver(DataAdapter dataAdapter) {
- dataAdapter.registerObserver(null);
+ private static class DataAdapterObserver implements DataAdapter.Observer {
+ }
+
+ private static class ObservableListObserver implements ObservableList.Observer {
+ }
+
+ static void registerObserver(DataAdapter dataAdapter) {
+ dataAdapter.registerObserver(new DataAdapterObserver());
}
public static void main(String[] args) {
registerObserver(new SimpleDataAdapter());
+
+ // To prevent SimpleObservableList#registerObserver from being inlined.
+ SimpleObservableList<ObservableListObserver> originalImpl = new SimpleObservableList<>();
+ originalImpl.registerObserver(new ObservableListObserver());
}
}
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleObservableList.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleObservableList.java
index 5fa667b..5af0ff2 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleObservableList.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleObservableList.java
@@ -5,11 +5,17 @@
package com.android.tools.r8.bridgeremoval.bridgestokeep;
import com.android.tools.r8.bridgeremoval.bridgestokeep.ObservableList.Observer;
+import java.util.List;
public class SimpleObservableList<O extends Observer>
implements ObservableList<O> {
+ private List<O> observers;
+
@Override
public void registerObserver(O observer) {
+ if (observer != null && observers != null && !observers.contains(observer)) {
+ observers.add(observer);
+ }
}
}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
index a56c0c8..1938c46 100644
--- a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
@@ -36,7 +36,7 @@
}
private void noInlining(InternalOptions options) {
- options.inlineAccessors = false;
+ options.enableInlining = false;
}
private String checkDiscardRule(boolean member, Class annotation) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index 52066a6..e256ae4 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -38,7 +38,7 @@
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
private void configure(InternalOptions options) {
- options.skipClassMerging = false;
+ options.enableClassMerging = true;
}
private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
diff --git a/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTest.java b/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTest.java
new file mode 100644
index 0000000..76fcd19f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTest.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+public class BreakInOneLineFinallyTest {
+ int i = 0;
+
+ void foo() {
+ try { bar(); }
+ finally { baz(); } // Java will fail to break here on exceptional exit.
+ }
+
+ int bar() {
+ if (i++ % 2 == 0) {
+ System.out.println("bar return " + i);
+ return i;
+ }
+ System.out.println("bar throw " + i);
+ throw new RuntimeException("" + i);
+ }
+
+ void baz() {
+ System.out.println("baz");
+ }
+
+ public static void main(String[] args) {
+ BreakInOneLineFinallyTest test = new BreakInOneLineFinallyTest();
+ test.foo();
+ try {
+ test.foo();
+ } catch (RuntimeException e) {
+ System.out.println("Caught expected exception: " + e.getMessage());
+ return;
+ }
+ throw new RuntimeException("Test failed...");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTestRunner.java b/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTestRunner.java
new file mode 100644
index 0000000..6d7d1a9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTestRunner.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+import com.android.tools.r8.ToolHelper;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BreakInOneLineFinallyTestRunner extends DebugTestBase {
+
+ private static final Class CLASS = BreakInOneLineFinallyTest.class;
+ private static final String FILE = CLASS.getSimpleName() + ".java";
+ private static final String NAME = CLASS.getCanonicalName();
+
+ private final DebugTestConfig config;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection<Object[]> setup() {
+ DelayedDebugTestConfig cf =
+ temp -> new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
+ DelayedDebugTestConfig d8 =
+ temp -> new D8DebugTestConfig().compileAndAddClasses(temp, CLASS);
+ return ImmutableList.of(new Object[]{"CF", cf}, new Object[]{"D8", d8});
+ }
+
+ public BreakInOneLineFinallyTestRunner(String name, DelayedDebugTestConfig config) {
+ this.config = config.getConfig(temp);
+ }
+
+ @Test
+ public void testHitBreakpointOnNormalAndExceptionalFlow() throws Throwable {
+ Assume.assumeFalse(
+ "b/72933440 : JavaC doesn't duplicate line-table entries when duplicating finally blocks",
+ config instanceof D8DebugTestConfig);
+ runDebugTest(
+ config,
+ NAME,
+ breakpoint(NAME, "foo", 11),
+ run(),
+ checkLine(FILE, 11), // hit finally on normal flow
+ breakpoint(NAME, "main", 34), // can't hit the exceptional block :-(
+ run(),
+ checkLine(FILE, 34),
+ run());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTest.java b/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTest.java
new file mode 100644
index 0000000..eec9d13
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTest.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+public class BreakInTwoLinesFinallyTest {
+ int i = 0;
+
+ void foo() {
+ try { bar(); }
+ finally {
+ baz();
+ }
+ }
+
+ int bar() {
+ if (i++ % 2 == 0) {
+ System.out.println("bar return " + i);
+ return i;
+ }
+ System.out.println("bar throw " + i);
+ throw new RuntimeException("" + i);
+ }
+
+ void baz() {
+ System.out.println("baz");
+ }
+
+ public static void main(String[] args) {
+ BreakInTwoLinesFinallyTest test = new BreakInTwoLinesFinallyTest();
+ test.foo();
+ try {
+ test.foo();
+ } catch (RuntimeException e) {
+ System.out.println("Caught expected exception: " + e.getMessage());
+ return;
+ }
+ throw new RuntimeException("Test failed...");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTestRunner.java b/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTestRunner.java
new file mode 100644
index 0000000..7691a2f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTestRunner.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BreakInTwoLinesFinallyTestRunner extends DebugTestBase {
+
+ private static final Class CLASS = BreakInTwoLinesFinallyTest.class;
+ private static final String FILE = CLASS.getSimpleName() + ".java";
+ private static final String NAME = CLASS.getCanonicalName();
+
+ private final DebugTestConfig config;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection<Object[]> setup() {
+ DelayedDebugTestConfig cf =
+ temp -> new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
+ DelayedDebugTestConfig d8 =
+ temp -> new D8DebugTestConfig().compileAndAddClasses(temp, CLASS);
+ return ImmutableList.of(new Object[]{"CF", cf}, new Object[]{"D8", d8});
+ }
+
+ public BreakInTwoLinesFinallyTestRunner(String name, DelayedDebugTestConfig config) {
+ this.config = config.getConfig(temp);
+ }
+
+ @Test
+ public void testHitBreakpointOnNormalAndExceptionalFlow() throws Throwable {
+ Assume.assumeTrue(ToolHelper.getDexVm().isNewerThan(DexVm.ART_6_0_1_HOST));
+ runDebugTest(
+ config,
+ NAME,
+ breakpoint(NAME, "foo", 12),
+ run(),
+ checkLine(FILE, 12), // hit finally on normal flow
+ run(),
+ checkLine(FILE, 12), // hit finally on exceptional flow
+ breakpoint(NAME, "main", 36),
+ run(),
+ checkLine(FILE, 36),
+ run());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/CfDebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/CfDebugTestConfig.java
index 6049b7d..e8fa240 100644
--- a/src/test/java/com/android/tools/r8/debug/CfDebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/CfDebugTestConfig.java
@@ -13,8 +13,7 @@
/** Base test configuration with CF version of JDWP. */
public class CfDebugTestConfig extends DebugTestConfig {
- public static final Path JDWP_JAR =
- ToolHelper.getJdwpTestsCfJarPath(AndroidApiLevel.N.getLevel());
+ public static final Path JDWP_JAR = ToolHelper.getJdwpTestsCfJarPath(AndroidApiLevel.N);
public CfDebugTestConfig() {
this(Collections.emptyList());
diff --git a/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java
index d535f72..1f13006 100644
--- a/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java
@@ -7,10 +7,11 @@
import com.android.tools.r8.D8Command;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
@@ -21,12 +22,12 @@
public static AndroidApp d8Compile(List<Path> paths, Consumer<InternalOptions> optionsConsumer) {
try {
- int minSdk = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm());
+ AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
D8Command.Builder builder = D8Command.builder();
return ToolHelper.runD8(
builder
.addProgramFiles(paths)
- .setMinApiLevel(minSdk)
+ .setMinApiLevel(minSdk.getLevel())
.setMode(CompilationMode.DEBUG)
.addLibraryFiles(ToolHelper.getAndroidJar(minSdk)),
optionsConsumer);
@@ -35,6 +36,14 @@
}
}
+ public D8DebugTestConfig compileAndAddClasses(TemporaryFolder temp, Class... classes) {
+ return compileAndAddClasses(temp, Arrays.asList(classes));
+ }
+
+ public D8DebugTestConfig compileAndAddClasses(TemporaryFolder temp, List<Class> classes) {
+ return compileAndAdd(temp, ListUtils.map(classes, ToolHelper::getClassFileForTestClass), null);
+ }
+
public D8DebugTestConfig compileAndAdd(TemporaryFolder temp, Path... paths) {
return compileAndAdd(temp, Arrays.asList(paths), null);
}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
index 3b7e4e8..6432530 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
@@ -8,10 +8,10 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -33,7 +33,7 @@
boolean writeProguardMap)
throws Exception {
assert outputMode == OutputMode.DexIndexed || outputMode == OutputMode.ClassFile;
- int minSdk = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm());
+ AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
String prefix = outputMode == OutputMode.ClassFile ? "cf" : "dex";
Path outdir = temp.newFolder().toPath();
Path outjar = outdir.resolve(prefix + "_r8_compiled.jar");
@@ -41,7 +41,7 @@
R8Command.Builder builder =
R8Command.builder()
.addProgramFiles(DEBUGGEE_JAR)
- .setMinApiLevel(minSdk)
+ .setMinApiLevel(minSdk.getLevel())
.addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
.setMode(CompilationMode.RELEASE)
.setOutput(outjar, outputMode);
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 94d5666..cbfcca6 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -115,7 +115,7 @@
protected static final boolean supportsDefaultMethod(DebugTestConfig config) {
return config.isCfRuntime()
- || ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm()) >= AndroidApiLevel.N.getLevel();
+ || ToolHelper.getMinApiLevelForDexVm().getLevel() >= AndroidApiLevel.N.getLevel();
}
protected final void runDebugTest(
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
index aff3708..556e17f 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
@@ -37,12 +37,14 @@
return paths;
}
- public void addPaths(Path... paths) {
+ public DebugTestConfig addPaths(Path... paths) {
addPaths(Arrays.asList(paths));
+ return this;
}
- public void addPaths(List<Path> paths) {
+ public DebugTestConfig addPaths(List<Path> paths) {
this.paths.addAll(paths);
+ return this;
}
/** Proguard map that the debuggee has been translated according to, null if not present. */
@@ -50,8 +52,9 @@
return proguardMap;
}
- public void setProguardMap(Path proguardMap) {
+ public DebugTestConfig setProguardMap(Path proguardMap) {
this.proguardMap = proguardMap;
+ return this;
}
@Override
diff --git a/src/test/java/com/android/tools/r8/debug/DexDebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/DexDebugTestConfig.java
index 27e3d30..babf170 100644
--- a/src/test/java/com/android/tools/r8/debug/DexDebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/DexDebugTestConfig.java
@@ -13,7 +13,7 @@
public class DexDebugTestConfig extends DebugTestConfig {
public static final Path JDWP_DEX_JAR =
- ToolHelper.getJdwpTestsDexJarPath(ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm()));
+ ToolHelper.getJdwpTestsDexJarPath(ToolHelper.getMinApiLevelForDexVm());
public DexDebugTestConfig() {
this(Collections.emptyList());
diff --git a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
index d7e4d25..d944a75 100644
--- a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
@@ -7,9 +7,9 @@
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import java.nio.file.Path;
-import java.nio.file.Paths;
import org.junit.Test;
/** Tests source file and line numbers on inlined methods. */
@@ -31,14 +31,14 @@
boolean writeProguardMap,
boolean dontOptimizeByEnablingDebug)
throws Exception {
- int minSdk = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm());
+ AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
Path outdir = temp.newFolder().toPath();
Path outjar = outdir.resolve("r8_compiled.jar");
Path proguardMapPath = writeProguardMap ? outdir.resolve("proguard.map") : null;
R8Command.Builder builder =
R8Command.builder()
.addProgramFiles(DEBUGGEE_JAR)
- .setMinApiLevel(minSdk)
+ .setMinApiLevel(minSdk.getLevel())
.addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
.setMode(dontOptimizeByEnablingDebug ? CompilationMode.DEBUG : CompilationMode.RELEASE)
.setOutput(outjar, OutputMode.DexIndexed);
@@ -51,7 +51,7 @@
if (!dontOptimizeByEnablingDebug) {
options.lineNumberOptimization = lineNumberOptimization;
}
- options.inlineAccessors = false;
+ options.enableInlining = false;
});
DebugTestConfig config = new D8DebugTestConfig();
config.addPaths(outjar);
diff --git a/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTest.java b/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTest.java
new file mode 100644
index 0000000..9abc732
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTest.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+public class LocalChangeOnSameLineTest {
+ int i = 0;
+
+ int bar() {
+ System.out.println("bar call " + ++i);
+ return i;
+ }
+
+ void foo() {
+ { int x = bar(); int y = bar(); }
+ { int x = bar(); int y = bar(); }
+ }
+
+ public static void main(String[] args) {
+ new LocalChangeOnSameLineTest().foo();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTestRunner.java b/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTestRunner.java
new file mode 100644
index 0000000..b588590
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTestRunner.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+import com.android.tools.r8.ToolHelper;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LocalChangeOnSameLineTestRunner extends DebugTestBase {
+
+ private static final Class CLASS = LocalChangeOnSameLineTest.class;
+ private static final String FILE = CLASS.getSimpleName() + ".java";
+ private static final String NAME = CLASS.getCanonicalName();
+
+ private final DebugTestConfig config;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection<Object[]> setup() {
+ DelayedDebugTestConfig cf =
+ temp -> new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
+ DelayedDebugTestConfig d8 =
+ temp -> new D8DebugTestConfig().compileAndAddClasses(temp, CLASS);
+ return ImmutableList.of(new Object[]{"CF", cf}, new Object[]{"D8", d8});
+ }
+
+ public LocalChangeOnSameLineTestRunner(String name, DelayedDebugTestConfig config) {
+ this.config = config.getConfig(temp);
+ }
+
+ /** Test that only hit the break point at line 15 once. */
+ @Test
+ public void testHitBreakpointOnce() throws Throwable {
+ Assume.assumeFalse("b/72933440 : invalid line info table", config instanceof D8DebugTestConfig);
+ runDebugTest(
+ config,
+ NAME,
+ breakpoint(NAME, "foo", 15),
+ run(),
+ checkLine(FILE, 15),
+ breakpoint(NAME, "main", 21),
+ run(),
+ checkLine(FILE, 21),
+ run());
+ }
+
+ /** Test that locals are correct in the frame of foo each time we break in bar. */
+ @Test
+ public void testLocalsOnBreakpoint() throws Throwable {
+ runDebugTest(
+ config,
+ NAME,
+ breakpoint(NAME, "bar"),
+ run(),
+ checkLine(FILE, 10),
+ inspect(t -> t.getFrame(1).checkNoLocal("x")),
+ run(),
+ checkLine(FILE, 10),
+ inspect(t -> t.getFrame(1).checkLocal("x")),
+ run(),
+ checkLine(FILE, 10),
+ inspect(t -> t.getFrame(1).checkNoLocal("x")),
+ run(),
+ checkLine(FILE, 10),
+ inspect(t -> t.getFrame(1).checkLocal("x")),
+ run());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/MinificationTest.java b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
index 7bf989c..5f63038 100644
--- a/src/test/java/com/android/tools/r8/debug/MinificationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
@@ -9,10 +9,10 @@
import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -68,14 +68,14 @@
proguardConfigurations = builder.build();
}
- int minSdk = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm());
+ AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
Path dexOutputDir = temp.newFolder().toPath();
Path proguardMap = writeProguardMap ? dexOutputDir.resolve("proguard.map") : null;
R8Command.Builder builder =
R8Command.builder()
.addProgramFiles(DEBUGGEE_JAR)
.setOutput(dexOutputDir, OutputMode.DexIndexed)
- .setMinApiLevel(minSdk)
+ .setMinApiLevel(minSdk.getLevel())
.setMode(CompilationMode.DEBUG)
.addLibraryFiles(ToolHelper.getAndroidJar(minSdk));
if (proguardMap != null) {
diff --git a/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java b/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
index d5bbe18..4916e1d 100644
--- a/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
@@ -8,10 +8,10 @@
import com.android.tools.r8.R8;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
import java.nio.file.Path;
-import java.nio.file.Paths;
import org.junit.rules.TemporaryFolder;
// Shared test configuration for R8/CF compiled resources from the "debugTestResources" target.
@@ -21,11 +21,11 @@
private static synchronized AndroidApp getCompiledResources() throws Throwable {
if (compiledResources == null) {
- int minApi = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm());
+ AndroidApiLevel minApi = ToolHelper.getMinApiLevelForDexVm();
AndroidAppConsumers sink = new AndroidAppConsumers();
R8.run(
R8Command.builder()
- .setMinApiLevel(minApi)
+ .setMinApiLevel(minApi.getLevel())
.setMode(CompilationMode.DEBUG)
.addProgramFiles(DebugTestBase.DEBUGGEE_JAR)
.setProgramConsumer(sink.wrapClassFileConsumer(null))
diff --git a/src/test/java/com/android/tools/r8/desugar/BasicTestDependenciesDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/BasicTestDependenciesDesugaringTest.java
index ab5ce01..97047e8 100644
--- a/src/test/java/com/android/tools/r8/desugar/BasicTestDependenciesDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/BasicTestDependenciesDesugaringTest.java
@@ -85,7 +85,7 @@
D8Command.builder()
.addClasspathFiles(classpath)
.addProgramFiles(toCompile)
- .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K.getLevel()))
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K))
.setMinApiLevel(AndroidApiLevel.K.getLevel()),
options -> options.interfaceMethodDesugaring = OffOrAuto.Auto);
}
@@ -99,7 +99,7 @@
D8Command.builder()
.addClasspathFiles(classpath)
.addProgramFiles(toCompile)
- .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K.getLevel()))
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K))
.setMinApiLevel(AndroidApiLevel.K.getLevel()),
options -> options.interfaceMethodDesugaring = OffOrAuto.Off);
}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
new file mode 100644
index 0000000..750a5c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
@@ -0,0 +1,252 @@
+// Copyright (c) 2017, 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.dexsplitter;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class DexSplitterTests {
+
+ private static final String CLASS_DIR = ToolHelper.EXAMPLES_BUILD_DIR + "classes/dexsplitsample";
+ private static final String CLASS1_CLASS = CLASS_DIR + "/Class1.class";
+ private static final String CLASS2_CLASS = CLASS_DIR + "/Class2.class";
+ private static final String CLASS3_CLASS = CLASS_DIR + "/Class3.class";
+ private static final String CLASS3_INNER_CLASS = CLASS_DIR + "/Class3$InnerClass.class";
+
+ @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ /**
+ * To test the file splitting we have 3 classes that we distribute like this: Class1 -> base
+ * Class2 -> feature1 Class3 -> feature1
+ *
+ * <p>Class1 and Class2 works independently of each other, but Class3 extends Class1, and
+ * therefore can't run without the base being loaded.
+ */
+ @Test
+ public void splitFilesNoObfuscation() throws CompilationFailedException, IOException {
+ // Initial normal compile to create dex files.
+ Path inputDex = temp.newFolder().toPath().resolve("input.zip");
+ D8.run(
+ D8Command.builder()
+ .setOutput(inputDex, OutputMode.DexIndexed)
+ .addProgramFiles(Paths.get(CLASS1_CLASS))
+ .addProgramFiles(Paths.get(CLASS2_CLASS))
+ .addProgramFiles(Paths.get(CLASS3_CLASS))
+ .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
+ .build());
+
+ Path outputDex = temp.getRoot().toPath().resolve("output");
+ Path splitSpec = createSplitSpec();
+
+ DexSplitter.main(
+ new String[] {
+ "--input", inputDex.toString(),
+ "--output", outputDex.toString(),
+ "--feature-splits", splitSpec.toString()
+ });
+
+
+ Path base = outputDex.getParent().resolve("output.base.zip");
+ Path feature = outputDex.getParent().resolve("output.feature1.zip");
+ validateUnobfuscatedOutput(base, feature);
+ }
+
+ private void validateUnobfuscatedOutput(Path base, Path feature) throws IOException {
+ // Both classes should still work if we give all dex files to the system.
+ for (String className : new String[] {"Class1", "Class2", "Class3"}) {
+ ArtCommandBuilder builder = new ArtCommandBuilder();
+ builder.appendClasspath(base.toString());
+ builder.appendClasspath(feature.toString());
+ builder.setMainClass("dexsplitsample." + className);
+ String out = ToolHelper.runArt(builder);
+ assertEquals(out, className + "\n");
+ }
+ // Individual classes should also work from the individual files.
+ String className = "Class1";
+ ArtCommandBuilder builder = new ArtCommandBuilder();
+ builder.appendClasspath(base.toString());
+ builder.setMainClass("dexsplitsample." + className);
+ String out = ToolHelper.runArt(builder);
+ assertEquals(out, className + "\n");
+
+ className = "Class2";
+ builder = new ArtCommandBuilder();
+ builder.appendClasspath(feature.toString());
+ builder.setMainClass("dexsplitsample." + className);
+ out = ToolHelper.runArt(builder);
+ assertEquals(out, className + "\n");
+
+ className = "Class3";
+ builder = new ArtCommandBuilder();
+ builder.appendClasspath(feature.toString());
+ builder.setMainClass("dexsplitsample." + className);
+ try {
+ ToolHelper.runArt(builder);
+ assertFalse(true);
+ } catch (AssertionError assertionError) {
+ // We expect this to throw since base is not in the path and Class3 depends on Class1
+ }
+ }
+
+ private Path createSplitSpec() throws FileNotFoundException, UnsupportedEncodingException {
+ Path splitSpec = temp.getRoot().toPath().resolve("split_spec");
+ try (PrintWriter out = new PrintWriter(splitSpec.toFile(), "UTF-8")) {
+ out.write(
+ "dexsplitsample.Class1:base\n"
+ + "dexsplitsample.Class2:feature1\n"
+ + "dexsplitsample.Class3:feature1");
+ }
+ return splitSpec;
+ }
+
+ private List<String> getProguardConf() {
+ return ImmutableList.of(
+ "-keep class dexsplitsample.Class3 {",
+ " public static void main(java.lang.String[]);",
+ "}");
+ }
+
+ @Test
+ public void splitFilesFromJar() throws IOException, CompilationFailedException {
+ // Initial normal compile to create dex files.
+ Path inputDex = temp.newFolder().toPath().resolve("input.zip");
+ D8.run(
+ D8Command.builder()
+ .setOutput(inputDex, OutputMode.DexIndexed)
+ .addProgramFiles(Paths.get(CLASS1_CLASS))
+ .addProgramFiles(Paths.get(CLASS2_CLASS))
+ .addProgramFiles(Paths.get(CLASS3_CLASS))
+ .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
+ .build());
+
+ Path outputDex = temp.getRoot().toPath().resolve("output");
+ Path baseJar = temp.getRoot().toPath().resolve("base.jar");
+ Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
+ ZipOutputStream baseStream = new ZipOutputStream(Files.newOutputStream(baseJar));
+ String name = "dexsplitsample/Class1.class";
+ baseStream.putNextEntry(new ZipEntry(name));
+ baseStream.write(Files.readAllBytes(Paths.get(CLASS1_CLASS)));
+ baseStream.closeEntry();
+ baseStream.close();
+
+ ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
+ name = "dexsplitsample/Class2.class";
+ featureStream.putNextEntry(new ZipEntry(name));
+ featureStream.write(Files.readAllBytes(Paths.get(CLASS2_CLASS)));
+ featureStream.closeEntry();
+ name = "dexsplitsample/Class3.class";
+ featureStream.putNextEntry(new ZipEntry(name));
+ featureStream.write(Files.readAllBytes(Paths.get(CLASS3_CLASS)));
+ featureStream.closeEntry();
+ name = "dexsplitsample/Class3$InnerClass.class";
+ featureStream.putNextEntry(new ZipEntry(name));
+ featureStream.write(Files.readAllBytes(Paths.get(CLASS3_INNER_CLASS)));
+ featureStream.closeEntry();
+ featureStream.close();
+
+ DexSplitter.main(
+ new String[] {
+ "--input",
+ inputDex.toString(),
+ "--output",
+ outputDex.toString(),
+ "--feature-jar",
+ baseJar.toString(),
+ "--feature-jar",
+ featureJar.toString()
+ });
+ Path base = outputDex.getParent().resolve("output.base.zip");
+ Path feature = outputDex.getParent().resolve("output.feature1.zip");
+ validateUnobfuscatedOutput(base, feature);
+ }
+
+ @Test
+ public void splitFilesObfuscation()
+ throws CompilationFailedException, IOException, ExecutionException {
+ // Initial normal compile to create dex files.
+ Path inputDex = temp.newFolder().toPath().resolve("input.zip");
+ Path proguardMap = temp.getRoot().toPath().resolve("proguard.map");
+
+ R8.run(
+ R8Command.builder()
+ .setOutput(inputDex, OutputMode.DexIndexed)
+ .addProgramFiles(Paths.get(CLASS1_CLASS))
+ .addProgramFiles(Paths.get(CLASS2_CLASS))
+ .addProgramFiles(Paths.get(CLASS3_CLASS))
+ .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+ .setProguardMapOutputPath(proguardMap)
+ .addProguardConfiguration(getProguardConf(), null)
+ .build());
+
+ Path outputDex = temp.getRoot().toPath().resolve("output");
+ Path splitSpec = createSplitSpec();
+
+ DexSplitter.main(
+ new String[] {
+ "--input", inputDex.toString(),
+ "--output", outputDex.toString(),
+ "--feature-splits", splitSpec.toString(),
+ "--proguard-map", proguardMap.toString()
+ });
+
+ Path base = outputDex.getParent().resolve("output.base.zip");
+ Path feature = outputDex.getParent().resolve("output.feature1.zip");
+ String class3 = "dexsplitsample.Class3";
+ // We should still be able to run the Class3 which we kept, it has a call to the obfuscated
+ // class1 which is in base.
+ ArtCommandBuilder builder = new ArtCommandBuilder();
+ builder.appendClasspath(base.toString());
+ builder.appendClasspath(feature.toString());
+ builder.setMainClass(class3);
+ String out = ToolHelper.runArt(builder);
+ assertEquals(out, "Class3\n");
+
+ // Class1 should not be in the feature, it should still be in base.
+ builder = new ArtCommandBuilder();
+ builder.appendClasspath(feature.toString());
+ builder.setMainClass(class3);
+ try {
+ ToolHelper.runArt(builder);
+ assertFalse(true);
+ } catch (AssertionError assertionError) {
+ // We expect this to throw since base is not in the path and Class3 depends on Class1.
+ }
+
+ // Ensure that the Class1 is actually in the correct split. Note that Class2 would have been
+ // shaken away.
+ DexInspector inspector = new DexInspector(base, proguardMap.toString());
+ ClassSubject subject = inspector.clazz("dexsplitsample.Class1");
+ assertTrue(subject.isPresent());
+ assertTrue(subject.isRenamed());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
index 7b1cf3e..5bf5a36 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.VmTestRunner;
import com.android.tools.r8.VmTestRunner.IgnoreForRangeOfVmVersions;
@@ -26,24 +27,24 @@
@IgnoreForRangeOfVmVersions(from = Version.V5_1_1, to = Version.V6_0_1)
public void testInvokeSuperTargets() throws Exception {
ensureSameOutput(MainClass.class.getCanonicalName(),
- asBytes(MainClass.class),
- asBytes(Consumer.class),
- asBytes(Super.class),
- asBytes(SubLevel1.class),
- asBytes(SubLevel2.class),
+ ToolHelper.getClassAsBytes(MainClass.class),
+ ToolHelper.getClassAsBytes(Consumer.class),
+ ToolHelper.getClassAsBytes(Super.class),
+ ToolHelper.getClassAsBytes(SubLevel1.class),
+ ToolHelper.getClassAsBytes(SubLevel2.class),
InvokerClassDump.dump(),
- asBytes(SubclassOfInvokerClass.class));
+ ToolHelper.getClassAsBytes(SubclassOfInvokerClass.class));
}
@Test
public void testInvokeSuperTargetsNonVerifying() throws Exception {
ensureR8FailsWithCompilationError(MainClassFailing.class.getCanonicalName(),
- asBytes(MainClassFailing.class),
- asBytes(Consumer.class),
- asBytes(Super.class),
- asBytes(SubLevel1.class),
- asBytes(SubLevel2.class),
+ ToolHelper.getClassAsBytes(MainClassFailing.class),
+ ToolHelper.getClassAsBytes(Consumer.class),
+ ToolHelper.getClassAsBytes(Super.class),
+ ToolHelper.getClassAsBytes(SubLevel1.class),
+ ToolHelper.getClassAsBytes(SubLevel2.class),
InvokerClassFailingDump.dump(),
- asBytes(SubclassOfInvokerClass.class));
+ ToolHelper.getClassAsBytes(SubclassOfInvokerClass.class));
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index 39cf369..5150223 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -7,7 +7,8 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexApplication;
@@ -24,12 +25,14 @@
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.NonNull;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.nonnull.NonNullAfterArrayAccess;
-import com.android.tools.r8.ir.nonnull.NonNullAfterFieldAccess;
-import com.android.tools.r8.ir.nonnull.NonNullAfterInvoke;
-import com.android.tools.r8.ir.optimize.NonNullMarker;
+import com.android.tools.r8.ir.optimize.nonnull.FieldAccessTest;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterArrayAccess;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterFieldAccess;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterInvoke;
+import com.android.tools.r8.ir.optimize.NonNullTracker;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Timing;
@@ -38,7 +41,7 @@
import java.util.function.BiConsumer;
import org.junit.Test;
-public class NullabilityTest extends AsmTestBase {
+public class NullabilityTest extends TestBase {
private static final InternalOptions TEST_OPTIONS = new InternalOptions();
private void buildAndTest(
@@ -47,7 +50,7 @@
boolean npeCaught,
BiConsumer<AppInfo, TypeAnalysis> inspector)
throws Exception {
- AndroidApp app = buildAndroidApp(asBytes(mainClass));
+ AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(mainClass));
DexApplication dexApplication =
new ApplicationReader(app, TEST_OPTIONS, new Timing("NullabilityTest.appReader"))
.read().toDirect();
@@ -55,8 +58,8 @@
DexInspector dexInspector = new DexInspector(appInfo.app);
DexEncodedMethod foo = dexInspector.clazz(mainClass.getName()).method(signature).getMethod();
IRCode irCode = foo.buildIR(TEST_OPTIONS);
- NonNullMarker nonNullMarker = new NonNullMarker();
- nonNullMarker.addNonNull(irCode);
+ NonNullTracker nonNullTracker = new NonNullTracker();
+ nonNullTracker.addNonNull(irCode);
TypeAnalysis analysis = new TypeAnalysis(appInfo, foo, irCode);
inspector.accept(appInfo, analysis);
verifyLastInvoke(irCode, analysis, npeCaught);
@@ -112,7 +115,7 @@
buildAndTest(NonNullAfterInvoke.class, signature, false, (appInfo, typeAnalysis) -> {
DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
DexType mainClass = appInfo.dexItemFactory.createType(
- "Lcom/android/tools/r8/ir/nonnull/NonNullAfterInvoke;");
+ DescriptorUtils.javaTypeToDescriptor(NonNullAfterInvoke.class.getCanonicalName()));
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
InvokeVirtual.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, true),
NonNull.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, false),
@@ -128,7 +131,7 @@
buildAndTest(NonNullAfterInvoke.class, signature, true, (appInfo, typeAnalysis) -> {
DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
DexType mainClass = appInfo.dexItemFactory.createType(
- "Lcom/android/tools/r8/ir/nonnull/NonNullAfterInvoke;");
+ DescriptorUtils.javaTypeToDescriptor(NonNullAfterInvoke.class.getCanonicalName()));
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
InvokeVirtual.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, true),
NonNull.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, false),
@@ -144,7 +147,7 @@
buildAndTest(NonNullAfterArrayAccess.class, signature, false, (appInfo, typeAnalysis) -> {
DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
DexType mainClass = appInfo.dexItemFactory.createType(
- "Lcom/android/tools/r8/ir/nonnull/NonNullAfterArrayAccess;");
+ DescriptorUtils.javaTypeToDescriptor(NonNullAfterArrayAccess.class.getCanonicalName()));
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
// An element inside a non-null array could be null.
ArrayGet.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, true),
@@ -170,7 +173,7 @@
buildAndTest(NonNullAfterArrayAccess.class, signature, true, (appInfo, typeAnalysis) -> {
DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
DexType mainClass = appInfo.dexItemFactory.createType(
- "Lcom/android/tools/r8/ir/nonnull/NonNullAfterArrayAccess;");
+ DescriptorUtils.javaTypeToDescriptor(NonNullAfterArrayAccess.class.getCanonicalName()));
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
// An element inside a non-null array could be null.
ArrayGet.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, true),
@@ -192,13 +195,13 @@
@Test
public void nonNullAfterSafeFieldAccess() throws Exception {
MethodSignature signature = new MethodSignature("foo", "int",
- new String[]{"com.android.tools.r8.ir.nonnull.FieldAccessTest"});
+ new String[]{FieldAccessTest.class.getCanonicalName()});
buildAndTest(NonNullAfterFieldAccess.class, signature, false, (appInfo, typeAnalysis) -> {
DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
DexType mainClass = appInfo.dexItemFactory.createType(
- "Lcom/android/tools/r8/ir/nonnull/NonNullAfterFieldAccess;");
+ DescriptorUtils.javaTypeToDescriptor(NonNullAfterFieldAccess.class.getCanonicalName()));
DexType testClass = appInfo.dexItemFactory.createType(
- "Lcom/android/tools/r8/ir/nonnull/FieldAccessTest;");
+ DescriptorUtils.javaTypeToDescriptor(FieldAccessTest.class.getCanonicalName()));
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
Argument.class, new ClassTypeLatticeElement(testClass, true),
NonNull.class, new ClassTypeLatticeElement(testClass, false),
@@ -212,13 +215,13 @@
@Test
public void stillNullAfterExceptionCatch_iget() throws Exception {
MethodSignature signature = new MethodSignature("bar", "int",
- new String[]{"com.android.tools.r8.ir.nonnull.FieldAccessTest"});
+ new String[]{FieldAccessTest.class.getCanonicalName()});
buildAndTest(NonNullAfterFieldAccess.class, signature, true, (appInfo, typeAnalysis) -> {
DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
DexType mainClass = appInfo.dexItemFactory.createType(
- "Lcom/android/tools/r8/ir/nonnull/NonNullAfterFieldAccess;");
+ DescriptorUtils.javaTypeToDescriptor(NonNullAfterFieldAccess.class.getCanonicalName()));
DexType testClass = appInfo.dexItemFactory.createType(
- "Lcom/android/tools/r8/ir/nonnull/FieldAccessTest;");
+ DescriptorUtils.javaTypeToDescriptor(FieldAccessTest.class.getCanonicalName()));
Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
Argument.class, new ClassTypeLatticeElement(testClass, true),
NonNull.class, new ClassTypeLatticeElement(testClass, false),
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index 117c1e1..118a75a 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.analysis.type;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.ToolHelper;
@@ -20,6 +21,9 @@
import org.junit.Test;
public class TypeLatticeTest {
+ private static final String IO_EXCEPTION = "Ljava/io/IOException;";
+ private static final String NOT_FOUND = "Ljava/io/FileNotFoundException;";
+ private static final String INTERRUPT = "Ljava/io/InterruptedIOException;";
private static DexItemFactory factory;
private static AppInfoWithSubtyping appInfo;
@@ -58,7 +62,15 @@
private TypeLatticeElement join(TypeLatticeElement... elements) {
assertTrue(elements.length > 1);
- return Arrays.stream(elements).reduce(TypeLatticeElement.joiner(appInfo)).get();
+ return TypeLatticeElement.join(appInfo, Arrays.stream(elements));
+ }
+
+ private boolean strictlyLessThan(TypeLatticeElement l1, TypeLatticeElement l2) {
+ return TypeLatticeElement.strictlyLessThan(appInfo, l1, l2);
+ }
+
+ private boolean lessThanOrEqual(TypeLatticeElement l1, TypeLatticeElement l2) {
+ return TypeLatticeElement.lessThanOrEqual(appInfo, l1, l2);
}
@Test
@@ -97,10 +109,10 @@
@Test
public void joinToNonJavaLangObject() {
assertEquals(
- element(factory.createType("Ljava/io/IOException;")),
+ element(factory.createType(IO_EXCEPTION)),
join(
- element(factory.createType("Ljava/io/FileNotFoundException;")),
- element(factory.createType("Ljava/io/InterruptedIOException;"))));
+ element(factory.createType(NOT_FOUND)),
+ element(factory.createType(INTERRUPT))));
}
@Test
@@ -192,4 +204,51 @@
array(3, factory.stringType),
array(4, factory.stringType)));
}
+
+ @Test
+ public void testPartialOrders() {
+ assertTrue(lessThanOrEqual(
+ element(factory.objectType),
+ element(factory.objectType)));
+ assertFalse(strictlyLessThan(
+ element(factory.objectType),
+ element(factory.objectType)));
+
+ assertTrue(strictlyLessThan(
+ element(factory.createType(NOT_FOUND)),
+ element(factory.createType(IO_EXCEPTION))));
+ assertTrue(strictlyLessThan(
+ element(factory.createType(INTERRUPT)),
+ element(factory.createType(IO_EXCEPTION))));
+ assertFalse(lessThanOrEqual(
+ element(factory.createType(NOT_FOUND)),
+ element(factory.createType(INTERRUPT))));
+ assertFalse(lessThanOrEqual(
+ element(factory.createType(INTERRUPT)),
+ element(factory.createType(NOT_FOUND))));
+
+ assertTrue(strictlyLessThan(
+ array(1, factory.stringType),
+ array(1, factory.objectType)));
+ assertFalse(lessThanOrEqual(
+ array(1, factory.stringType),
+ array(2, factory.objectType)));
+ assertTrue(strictlyLessThan(
+ array(2, factory.stringType),
+ array(1, factory.objectType)));
+
+ assertFalse(lessThanOrEqual(
+ array(3, factory.stringType),
+ array(4, factory.stringType)));
+ assertFalse(lessThanOrEqual(
+ array(4, factory.stringType),
+ array(3, factory.stringType)));
+
+ assertTrue(strictlyLessThan(
+ array(2, factory.objectType),
+ array(1, factory.objectType)));
+ assertTrue(strictlyLessThan(
+ NullLatticeElement.getInstance(),
+ array(1, factory.classType)));
+ }
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java
index f133d7e..e438efb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java
@@ -5,9 +5,18 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.code.AgetObject;
+import com.android.tools.r8.code.AputObject;
import com.android.tools.r8.code.CheckCast;
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.IgetObject;
import com.android.tools.r8.code.InvokeDirect;
+import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.code.NewArray;
import com.android.tools.r8.code.NewInstance;
import com.android.tools.r8.code.ReturnVoid;
import com.android.tools.r8.graph.DexCode;
@@ -28,6 +37,7 @@
public void exactMatch() throws Exception {
JasminBuilder builder = new JasminBuilder();
ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
+ classBuilder.addDefaultConstructor();
MethodSignature main = classBuilder.addMainMethod(
".limit stack 3",
".limit locals 1",
@@ -39,7 +49,6 @@
List<String> pgConfigs = ImmutableList.of(
"-keep class " + CLASS_NAME + " { *; }",
- "-dontoptimize",
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null);
@@ -50,15 +59,20 @@
NewInstance.class,
InvokeDirect.class,
ReturnVoid.class));
+
+ checkRuntime(builder, app, CLASS_NAME);
}
@Test
public void upCasts() throws Exception {
JasminBuilder builder = new JasminBuilder();
// A < B < C
- builder.addClass("C");
- builder.addClass("B", "C");
- builder.addClass("A", "B");
+ ClassBuilder c = builder.addClass("C");
+ c.addDefaultConstructor();
+ ClassBuilder b = builder.addClass("B", "C");
+ b.addDefaultConstructor();
+ ClassBuilder a = builder.addClass("A", "B");
+ a.addDefaultConstructor();
ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
MethodSignature main = classBuilder.addMainMethod(
".limit stack 3",
@@ -75,7 +89,6 @@
"-keep class A { *; }",
"-keep class B { *; }",
"-keep class C { *; }",
- "-dontoptimize",
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null);
@@ -86,15 +99,20 @@
NewInstance.class,
InvokeDirect.class,
ReturnVoid.class));
+
+ checkRuntime(builder, app, CLASS_NAME);
}
@Test
public void downCasts() throws Exception {
JasminBuilder builder = new JasminBuilder();
// C < B < A
- builder.addClass("A");
- builder.addClass("B", "A");
- builder.addClass("C", "B");
+ ClassBuilder a = builder.addClass("A");
+ a.addDefaultConstructor();
+ ClassBuilder b = builder.addClass("B", "A");
+ b.addDefaultConstructor();
+ ClassBuilder c = builder.addClass("C", "B");
+ c.addDefaultConstructor();
ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
MethodSignature main = classBuilder.addMainMethod(
".limit stack 3",
@@ -126,5 +144,99 @@
ReturnVoid.class));
CheckCast cast = (CheckCast) code.instructions[2];
assertEquals("C", cast.getType().toString());
+
+ checkRuntimeException(builder, app, CLASS_NAME, "ClassCastException");
+ }
+
+ @Test
+ public void bothUpAndDowncast() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
+ MethodSignature main = classBuilder.addMainMethod(
+ ".limit stack 4",
+ ".limit locals 1",
+ "iconst_1",
+ "anewarray java/lang/String", // args parameter
+ "dup",
+ "iconst_0",
+ "ldc \"a string\"",
+ "aastore",
+ "checkcast [Ljava/lang/Object;", // This upcast can be removed.
+ "iconst_0",
+ "aaload",
+ "checkcast java/lang/String", // Then, this downcast can be removed, too.
+ "invokevirtual java/lang/String/length()I",
+ "return");
+ // That is, both checkcasts should be removed together or kept together.
+
+ List<String> pgConfigs = ImmutableList.of(
+ "-keep class " + CLASS_NAME + " { *; }",
+ "-dontshrink");
+ AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+ DexEncodedMethod method = getMethod(app, CLASS_NAME, main);
+ assertNotNull(method);
+
+ DexCode code = method.getCode().asDexCode();
+ checkInstructions(code, ImmutableList.of(
+ Const4.class,
+ NewArray.class,
+ ConstString.class,
+ Const4.class,
+ AputObject.class,
+ AgetObject.class,
+ InvokeVirtual.class,
+ ReturnVoid.class));
+
+ checkRuntime(builder, app, CLASS_NAME);
+ }
+
+ @Test
+ public void nullCast() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
+ classBuilder.addField("public", "fld", "Ljava/lang/String;", null);
+ MethodSignature main = classBuilder.addMainMethod(
+ ".limit stack 3",
+ ".limit locals 1",
+ "aconst_null",
+ "checkcast Example", // Should be kept
+ "getfield Example.fld Ljava/lang/String;",
+ "return");
+
+ List<String> pgConfigs = ImmutableList.of(
+ "-keep class " + CLASS_NAME + " { *; }",
+ "-dontshrink");
+ AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+ DexEncodedMethod method = getMethod(app, CLASS_NAME, main);
+ assertNotNull(method);
+
+ checkInstructions(method.getCode().asDexCode(), ImmutableList.of(
+ Const4.class,
+ CheckCast.class,
+ IgetObject.class,
+ ReturnVoid.class));
+
+ checkRuntimeException(builder, app, CLASS_NAME, "NullPointerException");
+ }
+
+ private void checkRuntime(JasminBuilder builder, AndroidApp app, String className)
+ throws Exception {
+ String normalOutput = runOnJava(builder, className);
+ String dexOptimizedOutput = runOnArt(app, className);
+ assertEquals(normalOutput, dexOptimizedOutput);
+ }
+
+ private void checkRuntimeException(
+ JasminBuilder builder, AndroidApp app, String className, String exceptionName)
+ throws Exception {
+ ProcessResult javaOutput = runOnJavaRaw(builder, className);
+ assertEquals(1, javaOutput.exitCode);
+ assertTrue(javaOutput.stderr.contains(exceptionName));
+
+ ProcessResult artOutput = runOnArtRaw(app, className);
+ assertEquals(1, artOutput.exitCode);
+ assertTrue(artOutput.stderr.contains(exceptionName));
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullMarkerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullMarkerTest.java
deleted file mode 100644
index 036c6da..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullMarkerTest.java
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.optimize;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.AsmTestBase;
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InstancePut;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
-import com.android.tools.r8.ir.code.NonNull;
-import com.android.tools.r8.ir.nonnull.NonNullAfterArrayAccess;
-import com.android.tools.r8.ir.nonnull.NonNullAfterFieldAccess;
-import com.android.tools.r8.ir.nonnull.NonNullAfterInvoke;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.Timing;
-import java.util.function.Consumer;
-import org.junit.Test;
-
-public class NonNullMarkerTest extends AsmTestBase {
- private static final InternalOptions TEST_OPTIONS = new InternalOptions();
-
- private void buildAndTest(
- Class<?> testClass,
- MethodSignature signature,
- int expectedNumberOfNonNull,
- Consumer<IRCode> testAugmentedIRCode)
- throws Exception {
- AndroidApp app = buildAndroidApp(asBytes(testClass));
- DexApplication dexApplication =
- new ApplicationReader(app, TEST_OPTIONS, new Timing("NonNullMarkerTest.appReader"))
- .read().toDirect();
- AppInfo appInfo = new AppInfo(dexApplication);
- DexInspector dexInspector = new DexInspector(appInfo.app);
- DexEncodedMethod foo = dexInspector.clazz(testClass.getName()).method(signature).getMethod();
- IRCode irCode = foo.buildIR(TEST_OPTIONS);
- checkCountOfNonNull(irCode, 0);
-
- NonNullMarker nonNullMarker = new NonNullMarker();
-
- nonNullMarker.addNonNull(irCode);
- assertTrue(irCode.isConsistentSSA());
- checkCountOfNonNull(irCode, expectedNumberOfNonNull);
-
- if (testAugmentedIRCode != null) {
- testAugmentedIRCode.accept(irCode);
- }
-
- nonNullMarker.cleanupNonNull(irCode);
- assertTrue(irCode.isConsistentSSA());
- checkCountOfNonNull(irCode, 0);
- }
-
- private static void checkCountOfNonNull(IRCode code, int expectedOccurrences) {
- int count = 0;
- Instruction prev = null, curr = null;
- InstructionIterator it = code.instructionIterator();
- while (it.hasNext()) {
- prev = curr != null && !curr.isGoto() ? curr : prev;
- curr = it.next();
- if (curr.isNonNull()) {
- // Make sure non-null is added to the right place.
- assertTrue(prev == null || NonNullMarker.throwsOnNullInput(prev));
- count++;
- }
- }
- assertEquals(count, expectedOccurrences);
- }
-
- @Test
- public void nonNullAfterSafeInvokes() throws Exception {
- MethodSignature signature =
- new MethodSignature("foo", "int", new String[]{"java.lang.String"});
- buildAndTest(NonNullAfterInvoke.class, signature, 1, null);
- }
-
- @Test
- public void nonNullAfterSafeArrayAccess() throws Exception {
- MethodSignature signature =
- new MethodSignature("foo", "int", new String[]{"java.lang.String[]"});
- buildAndTest(NonNullAfterArrayAccess.class, signature, 1, null);
- }
-
- @Test
- public void nonNullAfterSafeFieldAccess() throws Exception {
- MethodSignature signature = new MethodSignature("foo", "int",
- new String[]{"com.android.tools.r8.ir.nonnull.FieldAccessTest"});
- buildAndTest(NonNullAfterFieldAccess.class, signature, 1, null);
- }
-
- @Test
- public void avoidRedundantNonNull() throws Exception {
- MethodSignature signature = new MethodSignature("foo2", "int",
- new String[]{"com.android.tools.r8.ir.nonnull.FieldAccessTest"});
- buildAndTest(NonNullAfterFieldAccess.class, signature, 1, ircode -> {
- // There are two InstancePut instructions of interest.
- int count = 0;
- InstructionIterator it = ircode.instructionIterator();
- while (it.hasNext()) {
- Instruction instruction = it.nextUntil(Instruction::isInstancePut);
- if (instruction == null) {
- break;
- }
- InstancePut iput = instruction.asInstancePut();
- if (count == 0) {
- // First one in the very first line: its value should not be replaced by NonNullMarker
- // because this instruction will happen _before_ non-null.
- assertFalse(iput.value().definition.isNonNull());
- } else if (count == 1) {
- // Second one after a safe invocation, which should use the value added by NonNullMarker.
- assertTrue(iput.object().definition.isNonNull());
- }
- count++;
- }
- assertEquals(2, count);
- });
- }
-
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
new file mode 100644
index 0000000..646cfaf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -0,0 +1,190 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.optimize.nonnull.FieldAccessTest;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterArrayAccess;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterFieldAccess;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterInvoke;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterNullCheck;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+import java.util.function.Consumer;
+import org.junit.Test;
+
+public class NonNullTrackerTest extends TestBase {
+ private static final InternalOptions TEST_OPTIONS = new InternalOptions();
+
+ private void buildAndTest(
+ Class<?> testClass,
+ MethodSignature signature,
+ int expectedNumberOfNonNull,
+ Consumer<IRCode> testAugmentedIRCode)
+ throws Exception {
+ AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(testClass));
+ DexApplication dexApplication =
+ new ApplicationReader(app, TEST_OPTIONS, new Timing("NonNullMarkerTest.appReader"))
+ .read().toDirect();
+ AppInfo appInfo = new AppInfo(dexApplication);
+ DexInspector dexInspector = new DexInspector(appInfo.app);
+ DexEncodedMethod foo = dexInspector.clazz(testClass.getName()).method(signature).getMethod();
+ IRCode irCode = foo.buildIR(TEST_OPTIONS);
+ checkCountOfNonNull(irCode, 0);
+
+ NonNullTracker nonNullTracker = new NonNullTracker();
+
+ nonNullTracker.addNonNull(irCode);
+ assertTrue(irCode.isConsistentSSA());
+ checkCountOfNonNull(irCode, expectedNumberOfNonNull);
+
+ if (testAugmentedIRCode != null) {
+ testAugmentedIRCode.accept(irCode);
+ }
+
+ nonNullTracker.cleanupNonNull(irCode);
+ assertTrue(irCode.isConsistentSSA());
+ checkCountOfNonNull(irCode, 0);
+ }
+
+ private static void checkCountOfNonNull(IRCode code, int expectedOccurrences) {
+ int count = 0;
+ Instruction prev = null, curr = null;
+ InstructionIterator it = code.instructionIterator();
+ while (it.hasNext()) {
+ prev = curr != null && !curr.isGoto() ? curr : prev;
+ curr = it.next();
+ if (curr.isNonNull()) {
+ // Make sure non-null is added to the right place.
+ assertTrue(prev == null
+ || NonNullTracker.throwsOnNullInput(prev)
+ || (prev.isIf() && prev.asIf().isZeroTest())
+ || !curr.getBlock().getPredecessors().contains(prev.getBlock()));
+ count++;
+ }
+ }
+ assertEquals(expectedOccurrences, count);
+ }
+
+ private void checkInvokeGetsNonNullReceiver(IRCode irCode) {
+ checkInvokeReceiver(irCode, true);
+ }
+
+ private void checkInvokeGetsNullReceiver(IRCode irCode) {
+ checkInvokeReceiver(irCode, false);
+ }
+
+ private void checkInvokeReceiver(IRCode irCode, boolean isNotNull) {
+ InstructionIterator it = irCode.instructionIterator();
+ boolean metInvokeWithReceiver = false;
+ while (it.hasNext()) {
+ Instruction instruction = it.nextUntil(Instruction::isInvokeMethodWithReceiver);
+ if (instruction == null) {
+ break;
+ }
+ InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
+ if (invoke.isInvokeDirect()
+ || !invoke.getInvokedMethod().name.toString().contains("hashCode")) {
+ continue;
+ }
+ metInvokeWithReceiver = true;
+ if (isNotNull) {
+ assertTrue(invoke.getReceiver().isNeverNull()
+ || invoke.getReceiver().definition.isArgument());
+ } else {
+ assertFalse(invoke.getReceiver().isNeverNull());
+ }
+ }
+ assertTrue(metInvokeWithReceiver);
+ }
+
+ @Test
+ public void nonNullAfterSafeInvokes() throws Exception {
+ MethodSignature foo =
+ new MethodSignature("foo", "int", new String[]{"java.lang.String"});
+ buildAndTest(NonNullAfterInvoke.class, foo, 1, this::checkInvokeGetsNonNullReceiver);
+ MethodSignature bar =
+ new MethodSignature("bar", "int", new String[]{"java.lang.String"});
+ buildAndTest(NonNullAfterInvoke.class, bar, 2, this::checkInvokeGetsNullReceiver);
+ }
+
+ @Test
+ public void nonNullAfterSafeArrayAccess() throws Exception {
+ MethodSignature foo =
+ new MethodSignature("foo", "int", new String[]{"java.lang.String[]"});
+ buildAndTest(NonNullAfterArrayAccess.class, foo, 1, null);
+ }
+
+ @Test
+ public void nonNullAfterSafeArrayLength() throws Exception {
+ MethodSignature signature =
+ new MethodSignature("arrayLength", "int", new String[]{"java.lang.String[]"});
+ buildAndTest(NonNullAfterArrayAccess.class, signature, 1, null);
+ }
+
+ @Test
+ public void nonNullAfterSafeFieldAccess() throws Exception {
+ MethodSignature foo = new MethodSignature("foo", "int",
+ new String[]{FieldAccessTest.class.getCanonicalName()});
+ buildAndTest(NonNullAfterFieldAccess.class, foo, 1, null);
+ }
+
+ @Test
+ public void avoidRedundantNonNull() throws Exception {
+ MethodSignature signature = new MethodSignature("foo2", "int",
+ new String[]{FieldAccessTest.class.getCanonicalName()});
+ buildAndTest(NonNullAfterFieldAccess.class, signature, 1, ircode -> {
+ // There are two InstancePut instructions of interest.
+ int count = 0;
+ InstructionIterator it = ircode.instructionIterator();
+ while (it.hasNext()) {
+ Instruction instruction = it.nextUntil(Instruction::isInstancePut);
+ if (instruction == null) {
+ break;
+ }
+ InstancePut iput = instruction.asInstancePut();
+ if (count == 0) {
+ // First one in the very first line: its value should not be replaced by NonNullMarker
+ // because this instruction will happen _before_ non-null.
+ assertFalse(iput.value().definition.isNonNull());
+ } else if (count == 1) {
+ // Second one after a safe invocation, which should use the value added by NonNullMarker.
+ assertTrue(iput.object().definition.isNonNull());
+ }
+ count++;
+ }
+ assertEquals(2, count);
+ });
+ }
+
+ @Test
+ public void nonNullAfterNullCheck() throws Exception {
+ MethodSignature foo =
+ new MethodSignature("foo", "int", new String[]{"java.lang.String"});
+ buildAndTest(NonNullAfterNullCheck.class, foo, 1, this::checkInvokeGetsNonNullReceiver);
+ MethodSignature bar =
+ new MethodSignature("bar", "int", new String[]{"java.lang.String"});
+ buildAndTest(NonNullAfterNullCheck.class, bar, 1, this::checkInvokeGetsNonNullReceiver);
+ MethodSignature baz =
+ new MethodSignature("baz", "int", new String[]{"java.lang.String"});
+ buildAndTest(NonNullAfterNullCheck.class, baz, 1, this::checkInvokeGetsNullReceiver);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index f282b42..9c03ee7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -100,7 +100,7 @@
.build();
// TODO(62048823): Enable minification.
ToolHelper.runR8(command, o -> {
- o.skipMinification = true;
+ o.enableMinification = false;
});
String artOutput = ToolHelper.runArtNoVerificationErrors(out + "/classes.dex",
"inlining.Inlining");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
index c72cdcb..92e43d0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
@@ -5,14 +5,16 @@
import static org.junit.Assert.assertEquals;
-import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.code.Format21t;
import com.android.tools.r8.code.Format22t;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.nonnull.NonNullAfterArrayAccess;
-import com.android.tools.r8.ir.nonnull.NonNullAfterFieldAccess;
-import com.android.tools.r8.ir.nonnull.NonNullAfterInvoke;
+import com.android.tools.r8.ir.optimize.nonnull.FieldAccessTest;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterArrayAccess;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterFieldAccess;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterInvoke;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
@@ -21,15 +23,15 @@
import java.util.List;
import org.junit.Test;
-public class SimplifyIfNotNullTest extends AsmTestBase {
+public class SimplifyIfNotNullTest extends TestBase {
private static boolean isIf(Instruction instruction) {
return instruction instanceof Format21t || instruction instanceof Format22t;
}
private void buildAndTest(Class<?> testClass, List<MethodSignature> signatures) throws Exception {
- AndroidApp app = buildAndroidApp(asBytes(testClass));
+ AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(testClass));
AndroidApp r8Result = compileWithR8(
- app, keepMainProguardConfiguration(testClass), o -> o.inlineAccessors = false);
+ app, keepMainProguardConfiguration(testClass), o -> o.enableInlining = false);
DexInspector dexInspector = new DexInspector(r8Result);
for (MethodSignature signature : signatures) {
DexEncodedMethod method =
@@ -61,11 +63,11 @@
@Test
public void nonNullAfterSafeFieldAccess() throws Exception {
MethodSignature foo = new MethodSignature("foo", "int",
- new String[]{"com.android.tools.r8.ir.nonnull.FieldAccessTest"});
+ new String[]{FieldAccessTest.class.getCanonicalName()});
MethodSignature bar = new MethodSignature("bar", "int",
- new String[]{"com.android.tools.r8.ir.nonnull.FieldAccessTest"});
+ new String[]{FieldAccessTest.class.getCanonicalName()});
MethodSignature foo2 = new MethodSignature("foo2", "int",
- new String[]{"com.android.tools.r8.ir.nonnull.FieldAccessTest"});
+ new String[]{FieldAccessTest.class.getCanonicalName()});
buildAndTest(NonNullAfterFieldAccess.class, ImmutableList.of(foo, bar, foo2));
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 8697917..699ebb4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -3,9 +3,16 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.Goto;
import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.If.Type;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Return;
@@ -57,9 +64,76 @@
// throw.
IRCode code = new IRCode(null, blocks, new ValueNumberGenerator(), false);
CodeRewriter.collapsTrivialGotos(null, code);
- assert code.blocks.get(0).isTrivialGoto();
- assert blocks.contains(block0);
- assert blocks.contains(block1);
- assert blocks.contains(block2);
+ assertTrue(code.blocks.get(0).isTrivialGoto());
+ assertTrue(blocks.contains(block0));
+ assertTrue(blocks.contains(block1));
+ assertTrue(blocks.contains(block2));
+ }
+
+ @Test
+ public void trivialGotoLoopAsFallthrough() {
+ // Setup block structure:
+ // block0:
+ // v0 <- argument
+ // if ne v0 block2
+ //
+ // block1:
+ // goto block3
+ //
+ // block2:
+ // return
+ //
+ // block3:
+ // goto block3
+ BasicBlock block2 = new BasicBlock();
+ block2.setNumber(2);
+ Instruction ret = new Return();
+ ret.setPosition(Position.none());
+ block2.add(ret);
+ block2.setFilledForTesting();
+
+ BasicBlock block3 = new BasicBlock();
+ block3.setNumber(3);
+ Instruction instruction = new Goto();
+ instruction.setPosition(Position.none());
+ block3.add(instruction);
+ block3.setFilledForTesting();
+ block3.getSuccessors().add(block3);
+
+ BasicBlock block1 = BasicBlock.createGotoBlock(1);
+ block1.getSuccessors().add(block3);
+ block1.setFilledForTesting();
+
+ BasicBlock block0 = new BasicBlock();
+ block0.setNumber(0);
+ Value value = new Value(0, ValueType.OBJECT, null);
+ instruction = new Argument(value);
+ instruction.setPosition(Position.none());
+ block0.add(instruction);
+ instruction = new If(Type.EQ, value);
+ instruction.setPosition(Position.none());
+ block0.add(instruction);
+ block0.getSuccessors().add(block2);
+ block0.getSuccessors().add(block1);
+ block0.setFilledForTesting();
+
+ block1.getPredecessors().add(block0);
+ block2.getPredecessors().add(block0);
+ block3.getPredecessors().add(block1);
+ block3.getPredecessors().add(block3);
+
+ LinkedList<BasicBlock> blocks = new LinkedList<>();
+ blocks.add(block0);
+ blocks.add(block1);
+ blocks.add(block2);
+ blocks.add(block3);
+ // Check that the goto in block0 remains. There was a bug in the trivial goto elimination
+ // that ended up removing that goto changing the code to start with the unreachable
+ // throw.
+ IRCode code = new IRCode(null, blocks, new ValueNumberGenerator(), false);
+ CodeRewriter.collapsTrivialGotos(null, code);
+ assertTrue(block0.getInstructions().get(1).isIf());
+ assertEquals(block1, block0.getInstructions().get(1).asIf().fallthroughBlock());
+ assertTrue(blocks.containsAll(ImmutableList.of(block0, block1, block2, block3)));
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/FieldAccessTest.java
similarity index 83%
rename from src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/nonnull/FieldAccessTest.java
index 09e94f7..dec12d5 100644
--- a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/FieldAccessTest.java
@@ -1,7 +1,7 @@
// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.nonnull;
+package com.android.tools.r8.ir.optimize.nonnull;
public class FieldAccessTest {
String fld;
diff --git a/src/test/java/com/android/tools/r8/ir/nonnull/NonNullAfterArrayAccess.java b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterArrayAccess.java
similarity index 76%
rename from src/test/java/com/android/tools/r8/ir/nonnull/NonNullAfterArrayAccess.java
rename to src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterArrayAccess.java
index 902d577..92cedea 100644
--- a/src/test/java/com/android/tools/r8/ir/nonnull/NonNullAfterArrayAccess.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterArrayAccess.java
@@ -1,7 +1,7 @@
// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.nonnull;
+package com.android.tools.r8.ir.optimize.nonnull;
public class NonNullAfterArrayAccess {
@@ -26,10 +26,19 @@
return arg.hashCode();
}
+ public static int arrayLength(String[] arg) {
+ int length = arg.length;
+ if (arg == null) {
+ throw new AssertionError("arg is not null.");
+ }
+ return arg.hashCode() + length;
+ }
+
public static void main(String[] args) {
String[] nonNullArgs = new String[1];
nonNullArgs[0] = "non-null";
foo(nonNullArgs);
bar(nonNullArgs);
+ arrayLength(nonNullArgs);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/nonnull/NonNullAfterFieldAccess.java b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterFieldAccess.java
similarity index 96%
rename from src/test/java/com/android/tools/r8/ir/nonnull/NonNullAfterFieldAccess.java
rename to src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterFieldAccess.java
index 73ee6ea..3b7e950 100644
--- a/src/test/java/com/android/tools/r8/ir/nonnull/NonNullAfterFieldAccess.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterFieldAccess.java
@@ -1,7 +1,7 @@
// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.nonnull;
+package com.android.tools.r8.ir.optimize.nonnull;
public class NonNullAfterFieldAccess {
diff --git a/src/test/java/com/android/tools/r8/ir/nonnull/NonNullAfterInvoke.java b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterInvoke.java
similarity index 94%
rename from src/test/java/com/android/tools/r8/ir/nonnull/NonNullAfterInvoke.java
rename to src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterInvoke.java
index a2c472a..8aab7fe 100644
--- a/src/test/java/com/android/tools/r8/ir/nonnull/NonNullAfterInvoke.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterInvoke.java
@@ -1,7 +1,7 @@
// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.nonnull;
+package com.android.tools.r8.ir.optimize.nonnull;
public class NonNullAfterInvoke {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterNullCheck.java b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterNullCheck.java
new file mode 100644
index 0000000..d943339
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterNullCheck.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.nonnull;
+
+public class NonNullAfterNullCheck {
+
+ public static int foo(String arg) {
+ if (arg != null) {
+ return arg.contains("null") ? arg.hashCode() : 0;
+ }
+ return -1;
+ }
+
+ public static int bar(String arg) {
+ if (arg == null) {
+ return -1;
+ } else {
+ return arg.contains("null") ? arg.hashCode() : 0;
+ }
+ }
+
+ public static int baz(String arg) {
+ if (arg != null) {
+ if (arg == null) {
+ throw new AssertionError("Unreachable.");
+ }
+ }
+ // not dominated by null check.
+ return arg.hashCode();
+ }
+
+ public static void main(String[] args) {
+ foo("non-null");
+ bar("non-null");
+ baz("non-null");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress72758525.java b/src/test/java/com/android/tools/r8/jasmin/Regress72758525.java
new file mode 100644
index 0000000..97331c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress72758525.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.jasmin;
+
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
+import org.junit.Test;
+
+public class Regress72758525 extends JasminTestBase {
+
+ private JasminBuilder buildClass() {
+ JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+ clazz.addDefaultConstructor();
+
+ clazz.addMainMethod(
+ ".limit stack 25",
+ ".limit locals 1",
+ "aload 0",
+ "dup",
+ "lconst_0",
+ "dconst_1",
+ "fconst_0",
+ "lconst_1",
+ "iconst_5",
+ "fconst_1",
+ "dconst_1",
+ "new Test",
+ "dup",
+ "invokespecial Test/<init>()V",
+ "lconst_0",
+ "new java/lang/Object",
+ "dup",
+ "invokespecial java/lang/Object/<init>()V",
+ "iconst_m1",
+ "dup2",
+ "dup2_x2",
+ "L0:",
+ "ineg",
+ "new java/lang/Object",
+ "dup",
+ "invokespecial java/lang/Object/<init>()V",
+ "dup2_x2",
+ "pop2",
+ "pop",
+ "aload 0",
+ "ifnull L0",
+ "i2f",
+ "invokestatic java/lang/Float/isNaN(F)Z",
+ "return");
+ return builder;
+ }
+
+ @Test
+ public void test() throws Exception {
+ JasminBuilder builder = buildClass();
+ runOnArtD8(builder, "Test");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
index 1349830..af8382e 100644
--- a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
+++ b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.io.File;
@@ -270,7 +271,7 @@
@BeforeClass
public static void compileLibraries() throws Exception {
// Selects appropriate jar according to min api level for the selected runtime.
- int minApi = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm());
+ AndroidApiLevel minApi = ToolHelper.getMinApiLevelForDexVm();
Path jdwpTestsJar = ToolHelper.getJdwpTestsCfJarPath(minApi);
Path classPath = ToolHelper.getClassPathForTests();
Path testPath = classPath.resolve(Paths.get("com","android", "tools", "r8", "jdwp"));
@@ -285,7 +286,7 @@
.addProgramFiles(jdwpTestsJar)
.addProgramFiles(extraTestResources)
.setOutput(d8Out.toPath(), OutputMode.DexIndexed)
- .setMinApiLevel(minApi)
+ .setMinApiLevel(minApi.getLevel())
.setMode(CompilationMode.DEBUG)
.build());
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index d79d099..9f520a2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -21,25 +22,35 @@
import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
-import java.util.List;
+import java.util.Collection;
import java.util.Map;
import org.junit.Assume;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
-// TODO(shertz) also run with backend 1.8
+@RunWith(Parameterized.class)
public abstract class AbstractR8KotlinTestBase extends TestBase {
- public static final String KOTLIN_R8_TEST_RESOURCES_BUILD_DIR =
- ToolHelper.TESTS_BUILD_DIR + "/kotlinR8TestResources";
-
- protected final boolean allowAccessModification;
-
- protected AbstractR8KotlinTestBase(boolean allowAccessModification) {
- this.allowAccessModification = allowAccessModification;
+ @Parameters(name = "{0}_{1}")
+ public static Collection<Object[]> data() {
+ ImmutableList.Builder<Object[]> builder = new Builder<>();
+ for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+ builder.add(new Object[]{Boolean.TRUE, targetVersion});
+ builder.add(new Object[]{Boolean.FALSE, targetVersion});
+ }
+ return builder.build();
}
+ @Parameter(0) public boolean allowAccessModification;
+ @Parameter(1) public KotlinTargetVersion targetVersion;
+
protected static void checkMethodIsInvokedAtLeastOnce(DexCode dexCode,
MethodSignature... methodSignatures) {
for (MethodSignature methodSignature : methodSignatures) {
@@ -90,17 +101,16 @@
return classSubject;
}
- private static MethodSubject checkMethod(ClassSubject classSubject, String methodName,
- String methodReturnType, List<String> methodParameterTypes, boolean isPresent) {
- return checkMethod(classSubject,
- new MethodSignature(methodName, methodReturnType, methodParameterTypes), isPresent);
- }
-
protected static MethodSubject checkMethodIsPresent(ClassSubject classSubject,
MethodSignature methodSignature) {
return checkMethod(classSubject, methodSignature, true);
}
+ protected static MethodSubject checkMethodIsAbsent(ClassSubject classSubject,
+ MethodSignature methodSignature) {
+ return checkMethod(classSubject, methodSignature, false);
+ }
+
protected static MethodSubject checkMethod(ClassSubject classSubject,
MethodSignature methodSignature, boolean isPresent) {
MethodSubject methodSubject = classSubject.method(methodSignature);
@@ -121,21 +131,6 @@
return code.asDexCode();
}
- private static DexCode extractCodeFor(DexInspector dexInspector, String className,
- String methodName,
- String methodReturnType, List<String> methodParameterTypes) {
- ClassSubject classSubject = checkClassExists(dexInspector, className);
- MethodSubject methodSubject = checkMethodIsPresent(classSubject, methodName, methodReturnType,
- methodParameterTypes);
- return getDexCode(methodSubject);
- }
-
- protected static MethodSubject checkMethodIsPresent(ClassSubject classSubject, String methodName,
- String methodReturnType,
- List<String> methodParameterTypes) {
- return checkMethod(classSubject, methodName, methodReturnType, methodParameterTypes, true);
- }
-
private String buildProguardRules(String mainClass) {
ProguardRulesBuilder proguardRules = new ProguardRulesBuilder();
proguardRules.appendWithLineSeparator(keepMainProguardConfiguration(mainClass));
@@ -160,8 +155,7 @@
AndroidAppInspector inspector) throws Exception {
Assume.assumeTrue(ToolHelper.artSupported());
- Path jarFile =
- Paths.get(KOTLIN_R8_TEST_RESOURCES_BUILD_DIR, folder + FileUtils.JAR_EXTENSION);
+ Path jarFile = getJarFile(folder);
String proguardRules = buildProguardRules(mainClass);
if (extraProguardRules != null) {
@@ -193,6 +187,11 @@
inspector.inspectApp(app);
}
+ private Path getJarFile(String folder) {
+ return Paths.get(ToolHelper.TESTS_BUILD_DIR, "kotlinR8TestResources",
+ targetVersion.getFolderName(), folder + FileUtils.JAR_EXTENSION);
+ }
+
@FunctionalInterface
interface AndroidAppInspector {
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClass.java
index 602c992..87ffcef 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -63,7 +63,15 @@
public MemberNaming.MethodSignature getGetterForProperty(String name) {
String type = getProperty(name).type;
- String getterName = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
+ String getterName;
+ if (name.length() > 2 && name.startsWith("is")
+ && (name.charAt(2) == '_' || Character.isUpperCase(name.charAt(2)))) {
+ // Getter for property "isAbc" is "isAbc".
+ getterName = name;
+ } else {
+ // Getter for property "abc" is "getAbc".
+ getterName = "get" + Character.toUpperCase(name.charAt(0)) + name.substring(1);
+ }
return new MemberNaming.MethodSignature(getterName, type, Collections.emptyList());
}
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
index 4bd61e1..379579f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -9,17 +9,9 @@
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.DexInspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import java.util.Collection;
import java.util.Collections;
-import java.util.Map;
import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-@RunWith(Parameterized.class)
public class R8KotlinDataClassTest extends AbstractR8KotlinTestBase {
private static final KotlinDataClass TEST_DATA_CLASS = new KotlinDataClass("dataclass.Person")
@@ -39,15 +31,6 @@
private static final MethodSignature COPY_DEFAULT_METHOD =
TEST_DATA_CLASS.getCopyDefaultSignature();
- public R8KotlinDataClassTest(boolean allowAccessModification) {
- super(allowAccessModification);
- }
-
- @Parameters(name = "{0}")
- public static Collection<Object> data() {
- return ImmutableList.of(Boolean.TRUE, Boolean.FALSE);
- }
-
@Test
public void test_dataclass_gettersOnly() throws Exception {
final String mainClassName = "dataclass.MainGettersOnlyKt";
@@ -60,17 +43,16 @@
// Getters should be removed after inlining, which is possible only if access is relaxed.
final boolean areGetterPresent = !allowAccessModification;
+ checkMethod(dataClass, NAME_GETTER_METHOD, areGetterPresent);
+ checkMethod(dataClass, AGE_GETTER_METHOD, areGetterPresent);
- Map<MethodSignature, Boolean> presenceMap = ImmutableMap.<MethodSignature, Boolean>builder()
- .put(NAME_GETTER_METHOD, areGetterPresent)
- .put(AGE_GETTER_METHOD, areGetterPresent)
- // ComponentN and copy methods are not used.
- .put(COMPONENT1_METHOD, false)
- .put(COMPONENT2_METHOD, false)
- .put(COPY_METHOD, false)
- .put(COPY_DEFAULT_METHOD, false)
- .build();
- checkMethodsPresence(dataClass, presenceMap);
+ // No use of componentN functions.
+ checkMethodIsAbsent(dataClass, COMPONENT1_METHOD);
+ checkMethodIsAbsent(dataClass, COMPONENT2_METHOD);
+
+ // No use of copy functions.
+ checkMethodIsAbsent(dataClass, COPY_METHOD);
+ checkMethodIsAbsent(dataClass, COPY_DEFAULT_METHOD);
ClassSubject classSubject = checkClassExists(dexInspector, mainClassName);
MethodSubject testMethod = checkMethodIsPresent(classSubject, testMethodSignature);
@@ -97,17 +79,16 @@
// ComponentN functions should be removed after inlining, which is possible only if access
// is relaxed.
final boolean areComponentMethodsPresent = !allowAccessModification;
+ checkMethod(dataClass, COMPONENT1_METHOD, areComponentMethodsPresent);
+ checkMethod(dataClass, COMPONENT2_METHOD, areComponentMethodsPresent);
- Map<MethodSignature, Boolean> presenceMap = ImmutableMap.<MethodSignature, Boolean>builder()
- .put(NAME_GETTER_METHOD, false)
- .put(AGE_GETTER_METHOD, false)
- // ComponentN and copy methods are not used.
- .put(COMPONENT1_METHOD, areComponentMethodsPresent)
- .put(COMPONENT2_METHOD, areComponentMethodsPresent)
- .put(COPY_METHOD, false)
- .put(COPY_DEFAULT_METHOD, false)
- .build();
- checkMethodsPresence(dataClass, presenceMap);
+ // No use of getter.
+ checkMethodIsAbsent(dataClass, NAME_GETTER_METHOD);
+ checkMethodIsAbsent(dataClass, AGE_GETTER_METHOD);
+
+ // No use of copy functions.
+ checkMethodIsAbsent(dataClass, COPY_METHOD);
+ checkMethodIsAbsent(dataClass, COPY_DEFAULT_METHOD);
ClassSubject classSubject = checkClassExists(dexInspector, mainClassName);
MethodSubject testMethod = checkMethodIsPresent(classSubject, testMethodSignature);
@@ -131,17 +112,18 @@
ClassSubject dataClass = checkClassExists(dexInspector, TEST_DATA_CLASS.getClassName());
boolean component2IsPresent = !allowAccessModification;
+ checkMethod(dataClass, COMPONENT2_METHOD, component2IsPresent);
- Map<MethodSignature, Boolean> presenceMap = ImmutableMap.<MethodSignature, Boolean>builder()
- .put(NAME_GETTER_METHOD, false)
- .put(AGE_GETTER_METHOD, false)
- // ComponentN and copy methods are not used.
- .put(COMPONENT1_METHOD, false)
- .put(COMPONENT2_METHOD, component2IsPresent)
- .put(COPY_METHOD, false)
- .put(COPY_DEFAULT_METHOD, false)
- .build();
- checkMethodsPresence(dataClass, presenceMap);
+ // Function component1 is not used.
+ checkMethodIsAbsent(dataClass, COMPONENT1_METHOD);
+
+ // No use of getter.
+ checkMethodIsAbsent(dataClass, NAME_GETTER_METHOD);
+ checkMethodIsAbsent(dataClass, AGE_GETTER_METHOD);
+
+ // No use of copy functions.
+ checkMethodIsAbsent(dataClass, COPY_METHOD);
+ checkMethodIsAbsent(dataClass, COPY_DEFAULT_METHOD);
ClassSubject classSubject = checkClassExists(dexInspector, mainClassName);
MethodSubject testMethod = checkMethodIsPresent(classSubject, testMethodSignature);
@@ -155,26 +137,23 @@
}
@Test
- public void test_dataclass_copy() throws Exception {
- final String mainClassName = "dataclass.MainCopyKt";
- runTest("dataclass", mainClassName, (app) -> {
+ public void test_dataclass_copyIsRemovedIfNotUsed() throws Exception {
+ final String mainClassName = "dataclass.MainComponentOnlyKt";
+ final MethodSignature testMethodSignature =
+ new MethodSignature("testDataClassCopy", "void", Collections.emptyList());
+ final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
+ runTest("dataclass", mainClassName, extraRules, (app) -> {
DexInspector dexInspector = new DexInspector(app);
ClassSubject dataClass = checkClassExists(dexInspector, TEST_DATA_CLASS.getClassName());
- // Copy method is small enough that it is always inlined.
- final boolean copyMethodIsPresent = false;
-
- Map<MethodSignature, Boolean> presenceMap = ImmutableMap.<MethodSignature, Boolean>builder()
- .put(COPY_METHOD, copyMethodIsPresent)
- .put(COPY_DEFAULT_METHOD, false)
- .build();
- checkMethodsPresence(dataClass, presenceMap);
+ checkMethodIsAbsent(dataClass, COPY_METHOD);
+ checkMethodIsAbsent(dataClass, COPY_DEFAULT_METHOD);
});
}
@Test
- public void test_dataclass_copyDefault() throws Exception {
- final String mainClassName = "dataclass.MainCopyWithDefaultKt";
+ public void test_dataclass_copyDefaultIsRemovedIfNotUsed() throws Exception {
+ final String mainClassName = "dataclass.MainCopyKt";
final MethodSignature testMethodSignature =
new MethodSignature("testDataClassCopyWithDefault", "void", Collections.emptyList());
final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
@@ -182,30 +161,7 @@
DexInspector dexInspector = new DexInspector(app);
ClassSubject dataClass = checkClassExists(dexInspector, TEST_DATA_CLASS.getClassName());
- // Copy$default method is inlined if access is relaxed.
- final boolean copyDefaultMethodIsPresent = !allowAccessModification;
-
- // copy$default is a wrapper around copy to deal with default values. If it's inlined thus
- // copy is inlined as well.
- final boolean copyMethodIsPresent = copyDefaultMethodIsPresent;
-
- Map<MethodSignature, Boolean> presenceMap = ImmutableMap.<MethodSignature, Boolean>builder()
- .put(COPY_DEFAULT_METHOD, copyDefaultMethodIsPresent)
- .put(COPY_METHOD, copyMethodIsPresent)
- .build();
- checkMethodsPresence(dataClass, presenceMap);
-
- if (copyDefaultMethodIsPresent) {
- ClassSubject classSubject = checkClassExists(dexInspector, mainClassName);
- MethodSubject testMethod = checkMethodIsPresent(classSubject, testMethodSignature);
- DexCode dexCode = getDexCode(testMethod);
- checkMethodIsInvokedAtLeastOnce(dexCode, COPY_DEFAULT_METHOD);
- }
- if (copyMethodIsPresent) {
- MethodSubject testMethod = checkMethod(dataClass, COPY_DEFAULT_METHOD, true);
- DexCode dexCode = getDexCode(testMethod);
- checkMethodIsInvokedAtLeastOnce(dexCode, COPY_METHOD);
- }
+ checkMethodIsAbsent(dataClass, COPY_DEFAULT_METHOD);
});
}
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
new file mode 100644
index 0000000..b059382
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class R8KotlinIntrinsicsTest extends AbstractR8KotlinTestBase {
+
+ private static final KotlinDataClass KOTLIN_INTRINSICS_CLASS =
+ new KotlinDataClass("kotlin.jvm.internal.Intrinsics");
+
+ @Test
+ public void testParameterNullCheckIsInlined() throws Exception {
+ final String extraRules = keepClassMethod("intrinsics.IntrinsicsKt",
+ new MethodSignature("expectsNonNullParameters",
+ "java.lang.String", Lists.newArrayList("java.lang.String", "java.lang.String")));
+
+ runTest("intrinsics", "intrinsics.IntrinsicsKt", extraRules, (app) -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject intrinsicsClass = checkClassExists(
+ dexInspector, KOTLIN_INTRINSICS_CLASS.getClassName());
+
+ checkMethodsPresence(intrinsicsClass,
+ ImmutableMap.<MethodSignature, Boolean>builder()
+ .put(new MethodSignature("throwParameterIsNullException",
+ "void", Collections.singletonList("java.lang.String")),
+ true)
+ .put(new MethodSignature("checkParameterIsNotNull",
+ "void", Lists.newArrayList("java.lang.Object", "java.lang.String")),
+ allowAccessModification ? false /* should be inlined*/ : true)
+ .build());
+ });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index e33d785..36ad4c9 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -47,7 +47,7 @@
EXAMPLE_BUILD_DIR,
Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules.txt"),
Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-1.txt"),
- AndroidApiLevel.I.getLevel());
+ AndroidApiLevel.I);
}
@Test
@@ -58,7 +58,7 @@
EXAMPLE_BUILD_DIR,
Paths.get(EXAMPLE_SRC_DIR, "multidex001", "main-dex-rules-2.txt"),
Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-2.txt"),
- AndroidApiLevel.I.getLevel());
+ AndroidApiLevel.I);
}
@Test
@@ -69,7 +69,7 @@
EXAMPLE_BUILD_DIR,
Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules.txt"),
Paths.get(EXAMPLE_SRC_DIR, "multidex002", "ref-list-1.txt"),
- AndroidApiLevel.I.getLevel());
+ AndroidApiLevel.I);
}
@Test
@@ -80,7 +80,7 @@
EXAMPLE_BUILD_DIR,
Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules.txt"),
Paths.get(EXAMPLE_SRC_DIR, "multidex003", "ref-list-1.txt"),
- AndroidApiLevel.I.getLevel());
+ AndroidApiLevel.I);
}
@Test
@@ -91,7 +91,7 @@
EXAMPLE_O_BUILD_DIR,
Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules.txt"),
Paths.get(EXAMPLE_O_SRC_DIR, "multidex004", "ref-list-1.txt"),
- AndroidApiLevel.I.getLevel());
+ AndroidApiLevel.I);
}
@Test
@@ -131,7 +131,7 @@
EXAMPLE_BUILD_DIR,
Paths.get(EXAMPLE_SRC_DIR, "multidex005", "main-dex-rules-" + variant + ".txt"),
Paths.get(EXAMPLE_SRC_DIR, "multidex005", "ref-list-" + variant + ".txt"),
- AndroidApiLevel.I.getLevel());
+ AndroidApiLevel.I);
}
private void doTest(
@@ -140,7 +140,7 @@
String buildDir,
Path mainDexRules,
Path expectedMainDexList,
- int minSdk)
+ AndroidApiLevel minSdk)
throws Throwable {
doTest(
testName,
@@ -150,7 +150,7 @@
expectedMainDexList,
minSdk,
(options) -> {
- options.inlineAccessors = false;
+ options.enableInlining = false;
});
}
@@ -160,7 +160,7 @@
String buildDir,
Path mainDexRules,
Path expectedMainDexList,
- int minSdk,
+ AndroidApiLevel minSdk,
Consumer<InternalOptions> optionsConsumer)
throws Throwable {
Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
@@ -170,7 +170,7 @@
// Build main-dex list using GenerateMainDexList and test the output from run.
GenerateMainDexListCommand.Builder mdlCommandBuilder = GenerateMainDexListCommand.builder();
GenerateMainDexListCommand mdlCommand = mdlCommandBuilder
- .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K.getLevel()))
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K))
.addProgramFiles(inputJar)
.addProgramFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
.addMainDexRulesFiles(mainDexRules)
@@ -189,7 +189,7 @@
final Box mainDexListOutput = new Box();
mdlCommandBuilder = GenerateMainDexListCommand.builder();
mdlCommand = mdlCommandBuilder
- .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K.getLevel()))
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K))
.addProgramFiles(inputJar)
.addProgramFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
.addMainDexRulesFiles(mainDexRules)
@@ -207,7 +207,7 @@
R8Command.Builder r8CommandBuilder = R8Command.builder();
R8Command command =
r8CommandBuilder
- .setMinApiLevel(minSdk)
+ .setMinApiLevel(minSdk.getLevel())
.addProgramFiles(inputJar)
.addProgramFiles(
Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
diff --git a/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java b/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java
new file mode 100644
index 0000000..0f1359e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java
@@ -0,0 +1,153 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.maindexlist.b72312389;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.BaseCommand;
+import com.android.tools.r8.CompatProguardCommandBuilder;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.GenerateMainDexList;
+import com.android.tools.r8.GenerateMainDexListCommand;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class B72312389 extends TestBase {
+ // Build a app with a class extending InstrumentationTestCase and including both the junit
+ // and the Android library.
+ private void buildInstrumentationTestCaseApplication(BaseCommand.Builder builder) {
+ builder
+ .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.O))
+ .addProgramFiles(
+ Paths.get("build", "test", "examplesAndroidApi",
+ "classes", "instrumentationtest", "InstrumentationTest.class"))
+ .addProgramFiles(ToolHelper.getFrameworkJunitJarPath(DexVm.ART_7_0_0_HOST));
+ }
+
+ private List<String> keepInstrumentationTestCaseRules = ImmutableList.of(
+ "-keep class instrumentationtest.InstrumentationTest {",
+ " *;",
+ "}");
+
+ @Test
+ public void testGenerateMainDexList() throws Exception {
+ CollectingDiagnosticHandler diagnostics = new CollectingDiagnosticHandler();
+ GenerateMainDexListCommand.Builder builder = GenerateMainDexListCommand.builder(diagnostics);
+ buildInstrumentationTestCaseApplication(builder);
+ GenerateMainDexListCommand command = builder
+ .addMainDexRules(keepInstrumentationTestCaseRules, Origin.unknown())
+ .build();
+ List<String> mainDexList = GenerateMainDexList.run(command);
+ assertTrue(mainDexList.contains("junit/framework/TestCase.class"));
+ diagnostics.assertEmpty();
+ }
+
+ private static class StringBox {
+ String content;
+ }
+
+ @Test
+ public void testR8ForceProguardCompatibility() throws Exception {
+ StringBox mainDexList = new StringBox();
+ // Build a app with a class extending InstrumentationTestCase and including both the junit
+ // and the Android library.
+ CollectingDiagnosticHandler diagnostics = new CollectingDiagnosticHandler();
+ R8Command.Builder builder = new CompatProguardCommandBuilder(true, diagnostics);
+ buildInstrumentationTestCaseApplication(builder);
+ R8Command command = builder
+ .setMinApiLevel(AndroidApiLevel.K.getLevel())
+ // TODO(72793900): This should not be required.
+ .addProguardConfiguration(ImmutableList.of("-keep class ** { *; }"), Origin.unknown())
+ .addProguardConfiguration(ImmutableList.of("-dontobfuscate"), Origin.unknown())
+ .addMainDexRules(keepInstrumentationTestCaseRules, Origin.unknown())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .setMainDexListConsumer(
+ (string, handler) -> mainDexList.content = string)
+ .build();
+ DexInspector inspector = new DexInspector(ToolHelper.runR8(command));
+ assertTrue(inspector.clazz("instrumentationtest.InstrumentationTest").isPresent());
+ assertTrue(mainDexList.content.contains("junit/framework/TestCase.class"));
+ // TODO(72794301): Two copies of this message is a bit over the top.
+ assertEquals(2,
+ diagnostics.countLibraryClassExtensdProgramClassWarnings(
+ "android.test.InstrumentationTestCase", "junit.framework.TestCase"));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ CollectingDiagnosticHandler diagnostics = new CollectingDiagnosticHandler();
+ R8Command.Builder builder = R8Command.builder(diagnostics);
+ buildInstrumentationTestCaseApplication(builder);
+ R8Command command = builder
+ .addMainDexRules(keepInstrumentationTestCaseRules, Origin.unknown())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .build();
+ try {
+ R8.run(command);
+ fail();
+ } catch (CompilationFailedException e) {
+ // Expected, as library class extending program class is an error for R8.
+ }
+ }
+
+ private static class CollectingDiagnosticHandler implements DiagnosticsHandler {
+ private final List<Diagnostic> infos = new ArrayList<>();
+ private final List<Diagnostic> warnings = new ArrayList<>();
+ private final List<Diagnostic> errors = new ArrayList<>();
+
+ @Override
+ public void info(Diagnostic info) {
+ infos.add(info);
+ }
+
+ @Override
+ public void warning(Diagnostic warning) {
+ warnings.add(warning);
+ }
+
+ @Override
+ public void error(Diagnostic error) {
+ errors.add(error);
+ }
+
+ public void assertEmpty() {
+ assertEquals(0, errors.size());
+ assertEquals(0, warnings.size());
+ assertEquals(0, infos.size());
+ }
+
+ private boolean isLibraryClassExtensdProgramClassWarnings(
+ String libraryClass, String programClass, Diagnostic diagnostic) {
+ return diagnostic.getDiagnosticMessage().equals(
+ "Library class "+ libraryClass + " extends program class " + programClass);
+ }
+
+ public long countLibraryClassExtensdProgramClassWarnings(
+ String libraryClass, String programClass) {
+ return warnings.stream()
+ .filter(diagnostics ->
+ isLibraryClassExtensdProgramClassWarnings(libraryClass, programClass, diagnostics))
+ .count();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
index 9b0b566..e2dfb6d 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
@@ -84,7 +84,7 @@
.addLibraryFiles(JAR_LIBRARIES)
.setMinApiLevel(minApiLevel);
ToolHelper.getAppBuilder(builder).addProgramFiles(programFile);
- ToolHelper.runR8(builder.build(), options -> options.inlineAccessors = false);
+ ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
}
private static boolean coolInvokes(InstructionSubject instruction) {
diff --git a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
index 9dbfb13..490162a 100644
--- a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
+++ b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
@@ -52,21 +52,39 @@
// Check the instruction used for testInlinedIntoVoidMethod
MethodSubject methodThrowToBeInlined =
- clazz.method("void", "foo", ImmutableList.of("java.lang.String"));
+ clazz.method("void", "foo", ImmutableList.of(
+ "java.lang.String", "java.lang.String", "java.lang.String", "java.lang.String"));
assertTrue(methodThrowToBeInlined.isPresent());
validateSequence(methodThrowToBeInlined.iterateInstructions(),
- InstructionSubject::isIfNez,
+ // 'if' with "foo#1" is flipped.
+ InstructionSubject::isIfEqz,
+
+ // 'if' with "foo#2" is removed along with the constant.
+
+ // 'if' with "foo#3" is removed so now we have unconditional call.
+ insn -> insn.isConstString("StringConstants::foo#3"),
+ InstructionSubject::isInvokeStatic,
+ InstructionSubject::isThrow,
+
+ // 'if's with "foo#4" and "foo#5" are flipped, but their throwing branches
+ // are not moved to the end of the code (area for improvement?).
+ insn -> insn.isConstString("StringConstants::foo#4"),
+ InstructionSubject::isIfEqz, // Flipped if
+ InstructionSubject::isGoto, // Jump around throwing branch.
+ InstructionSubject::isInvokeStatic, // Throwing branch.
+ InstructionSubject::isThrow,
+
+ insn -> insn.isConstString("StringConstants::foo#5"),
+ InstructionSubject::isIfEqz, // Flipped if
+ InstructionSubject::isReturnVoid, // Final return statement.
+ InstructionSubject::isInvokeStatic, // Throwing branch.
+ InstructionSubject::isThrow,
+
+ // After 'if' with "foo#1" flipped, always throwing branch
+ // moved here along with the constant.
insn -> insn.isConstString("StringConstants::foo#1"),
- // Below two are removed by optimization: non-null argument "".
- // InstructionSubject::isIfNez,
- // insn -> insn.isConstString("StringConstants::foo#2"),
- // InstructionSubject::isIfNez, 'removed by optimization'
- insn -> insn.isConstString("StringConstants::foo#3")
- // Below four are removed, since a safe call of arg.length() indicates arg is not null.
- // insn -> insn.isConstString("StringConstants::foo#4"),
- // InstructionSubject::isIfNez,
- // insn -> insn.isConstString("StringConstants::foo#5"),
- // InstructionSubject::isIfNez
+ InstructionSubject::isInvokeStatic,
+ InstructionSubject::isThrow
);
}
diff --git a/src/test/java/com/android/tools/r8/movestringconstants/TestClass.java b/src/test/java/com/android/tools/r8/movestringconstants/TestClass.java
index b801804..bf13fc3 100644
--- a/src/test/java/com/android/tools/r8/movestringconstants/TestClass.java
+++ b/src/test/java/com/android/tools/r8/movestringconstants/TestClass.java
@@ -5,19 +5,19 @@
package com.android.tools.r8.movestringconstants;
public class TestClass {
- static void foo(String arg) {
- Utils.check(arg, "StringConstants::foo#1");
+ static void foo(String arg1, String arg2, String arg3, String arg4) {
+ Utils.check(arg1, "StringConstants::foo#1");
Utils.check("", "StringConstants::foo#2");
- if (arg.length() == 12345) {
+ if (arg2.length() == 12345) {
Utils.check(null, "StringConstants::foo#3");
}
try {
- Utils.check(arg, "StringConstants::foo#4");
+ Utils.check(arg3, "StringConstants::foo#4");
} catch (Exception e) {
System.out.println(e.getMessage());
}
try {
- Utils.check(arg, "StringConstants::foo#5");
+ Utils.check(arg4, "StringConstants::foo#5");
} finally {
System.out.println("finally");
}
diff --git a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
index 1631a52..deb90e4 100644
--- a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
@@ -15,6 +15,8 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.NewInstance;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.ProguardRuleParserException;
@@ -219,13 +221,40 @@
main.iterateInstructions(InstructionSubject::isInvoke);
// mapping-105 simply includes: naming001.D#keep -> peek
// naming001.E extends D, hence its keep() should be renamed to peek as well.
- // Skip E#<init>
- iterator.next();
+ // Check E#<init> is not renamed.
+ InvokeInstructionSubject init = iterator.next();
+ assertEquals("Lnaming001/E;-><init>()V", init.invokedMethod().toSmaliString());
// E#keep() should be replaced with peek by applying the map.
InvokeInstructionSubject m = iterator.next();
assertEquals("peek", m.invokedMethod().name.toSourceString());
- // E could be renamed randomly, though.
- assertNotEquals("naming001.E", m.holder().toString());
+ // D must not be renamed
+ assertEquals("naming001.D", m.holder().toString());
+ }
+
+ @Test
+ public void test_naming001_rule106() throws Exception {
+ // keep rules just to rename E
+ Path flag = Paths.get(ToolHelper.EXAMPLES_DIR, "naming001", "keep-rules-106.txt");
+ Path proguardMap = out.resolve(MAPPING);
+ AndroidApp outputApp =
+ runR8(
+ ToolHelper.addProguardConfigurationConsumer(
+ getCommandForApps(out, flag, NAMING001_JAR).setDisableMinification(true),
+ pgConfig -> {
+ pgConfig.setPrintMapping(true);
+ pgConfig.setPrintMappingFile(proguardMap);
+ })
+ .build());
+
+ // Make sure the given proguard map is indeed applied.
+ DexInspector inspector = new DexInspector(outputApp);
+ MethodSubject main = inspector.clazz("naming001.D").method(DexInspector.MAIN);
+
+ // naming001.E is renamed to a.a, so first instruction must be: new-instance La/a;
+ Instruction[] instructions = main.getMethod().getCode().asDexCode().instructions;
+ assertTrue(instructions[0] instanceof NewInstance);
+ NewInstance newInstance = (NewInstance) instructions[0];
+ assertEquals( "La/a;", newInstance.getType().toSmaliString());
}
@Test
@@ -264,7 +293,7 @@
throws ProguardRuleParserException, ExecutionException, CompilationException, IOException {
return ToolHelper.runR8(command, options -> {
// Disable inlining to make this test not depend on inlining decisions.
- options.inlineAccessors = false;
+ options.enableInlining = false;
});
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index 0d1ca64..fa39257 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.code.ConstString;
import com.android.tools.r8.code.ConstStringJumbo;
@@ -18,6 +19,7 @@
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.DexInspector.MethodSubject;
@@ -43,15 +45,12 @@
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-public class IdentifierMinifierTest {
+public class IdentifierMinifierTest extends TestBase {
private final String appFileName;
private final List<String> keepRulesFiles;
private final Consumer<DexInspector> inspection;
- @Rule
- public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
-
public IdentifierMinifierTest(
String test,
List<String> keepRulesFiles,
@@ -61,30 +60,28 @@
this.inspection = inspection;
}
+ private AndroidApp processedApp;
+
@Before
public void generateR8ProcessedApp() throws Exception {
Path out = temp.getRoot().toPath();
R8Command.Builder builder =
ToolHelper.addProguardConfigurationConsumer(
- R8Command.builder(),
- pgConfig -> {
- pgConfig.setPrintMapping(true);
- pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
- })
+ R8Command.builder(),
+ pgConfig -> {
+ pgConfig.setPrintMapping(true);
+ pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
+ })
.setOutput(out, OutputMode.DexIndexed)
.addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
.addLibraryFiles(ToolHelper.getDefaultAndroidJar());
ToolHelper.getAppBuilder(builder).addProgramFiles(Paths.get(appFileName));
- ToolHelper.runR8(builder.build());
+ processedApp = ToolHelper.runR8(builder.build(), o -> o.debug = false);
}
@Test
public void identiferMinifierTest() throws Exception {
- Path out = temp.getRoot().toPath();
- DexInspector dexInspector =
- new DexInspector(
- out.resolve("classes.dex"),
- out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE).toString());
+ DexInspector dexInspector = new DexInspector(processedApp);
inspection.accept(dexInspector);
}
@@ -189,6 +186,13 @@
Set<Instruction> constStringInstructions =
getRenamedMemberIdentifierConstStrings(a, mainCode.asDexCode().instructions);
assertEquals(2, constStringInstructions.size());
+
+ ClassSubject b = inspector.clazz("getmembers.B");
+ MethodSubject inliner = b.method("java.lang.String", "inliner", ImmutableList.of());
+ Code inlinerCode = inliner.getMethod().getCode();
+ constStringInstructions =
+ getRenamedMemberIdentifierConstStrings(a, inlinerCode.asDexCode().instructions);
+ assertEquals(1, constStringInstructions.size());
}
// Without -identifiernamestring
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
index 62c1bec..b0e1131 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -60,12 +60,13 @@
assertNotNull(method);
DexCode code = method.getCode().asDexCode();
- assertTrue(code.instructions[0] instanceof InvokeDirect);
- assertTrue(code.instructions[1] instanceof ConstString);
+ checkInstructions(code, ImmutableList.of(
+ InvokeDirect.class,
+ ConstString.class,
+ IputObject.class,
+ ReturnVoid.class));
ConstString constString = (ConstString) code.instructions[1];
assertEquals(BOO, constString.getString().toString());
- assertTrue(code.instructions[2] instanceof IputObject);
- assertTrue(code.instructions[3] instanceof ReturnVoid);
}
@Test
@@ -92,17 +93,18 @@
assertNotNull(method);
DexCode code = method.getCode().asDexCode();
- assertTrue(code.instructions[0] instanceof InvokeDirect);
- assertTrue(code.instructions[1] instanceof SgetObject);
- assertTrue(code.instructions[2] instanceof ConstString);
+ checkInstructions(code, ImmutableList.of(
+ InvokeDirect.class,
+ SgetObject.class,
+ ConstString.class,
+ InvokeVirtual.class,
+ ConstString.class,
+ IputObject.class,
+ ReturnVoid.class));
ConstString constString = (ConstString) code.instructions[2];
assertEquals(BOO, constString.getString().toString());
- assertTrue(code.instructions[3] instanceof InvokeVirtual);
- assertTrue(code.instructions[4] instanceof ConstString);
constString = (ConstString) code.instructions[4];
assertEquals(BOO, constString.getString().toString());
- assertTrue(code.instructions[5] instanceof IputObject);
- assertTrue(code.instructions[6] instanceof ReturnVoid);
}
@Test
@@ -131,17 +133,18 @@
assertNotNull(method);
DexCode code = method.getCode().asDexCode();
- assertTrue(code.instructions[0] instanceof InvokeDirect);
- assertTrue(code.instructions[1] instanceof SgetObject);
- assertTrue(code.instructions[2] instanceof ConstString);
+ checkInstructions(code, ImmutableList.of(
+ InvokeDirect.class,
+ SgetObject.class,
+ ConstString.class,
+ InvokeVirtual.class,
+ ConstString.class,
+ IputObject.class,
+ ReturnVoid.class));
ConstString constString = (ConstString) code.instructions[2];
assertEquals(BOO, constString.getString().toString());
- assertTrue(code.instructions[3] instanceof InvokeVirtual);
- assertTrue(code.instructions[4] instanceof ConstString);
constString = (ConstString) code.instructions[4];
assertNotEquals(BOO, constString.getString().toString());
- assertTrue(code.instructions[5] instanceof IputObject);
- assertTrue(code.instructions[6] instanceof ReturnVoid);
}
@Test
@@ -165,11 +168,12 @@
assertNotNull(method);
DexCode code = method.getCode().asDexCode();
- assertTrue(code.instructions[0] instanceof ConstString);
+ checkInstructions(code, ImmutableList.of(
+ ConstString.class,
+ SputObject.class,
+ ReturnVoid.class));
ConstString constString = (ConstString) code.instructions[0];
assertEquals(BOO, constString.getString().toString());
- assertTrue(code.instructions[1] instanceof SputObject);
- assertTrue(code.instructions[2] instanceof ReturnVoid);
}
@Test
@@ -195,16 +199,17 @@
assertNotNull(method);
DexCode code = method.getCode().asDexCode();
- assertTrue(code.instructions[0] instanceof SgetObject);
- assertTrue(code.instructions[1] instanceof ConstString);
+ checkInstructions(code, ImmutableList.of(
+ SgetObject.class,
+ ConstString.class,
+ InvokeVirtual.class,
+ ConstString.class,
+ SputObject.class,
+ ReturnVoid.class));
ConstString constString = (ConstString) code.instructions[1];
assertEquals(BOO, constString.getString().toString());
- assertTrue(code.instructions[2] instanceof InvokeVirtual);
- assertTrue(code.instructions[3] instanceof ConstString);
constString = (ConstString) code.instructions[3];
assertEquals(BOO, constString.getString().toString());
- assertTrue(code.instructions[4] instanceof SputObject);
- assertTrue(code.instructions[5] instanceof ReturnVoid);
}
@Test
@@ -232,16 +237,17 @@
assertNotNull(method);
DexCode code = method.getCode().asDexCode();
- assertTrue(code.instructions[0] instanceof SgetObject);
- assertTrue(code.instructions[1] instanceof ConstString);
+ checkInstructions(code, ImmutableList.of(
+ SgetObject.class,
+ ConstString.class,
+ InvokeVirtual.class,
+ ConstString.class,
+ SputObject.class,
+ ReturnVoid.class));
ConstString constString = (ConstString) code.instructions[1];
assertEquals(BOO, constString.getString().toString());
- assertTrue(code.instructions[2] instanceof InvokeVirtual);
- assertTrue(code.instructions[3] instanceof ConstString);
constString = (ConstString) code.instructions[3];
assertNotEquals(BOO, constString.getString().toString());
- assertTrue(code.instructions[4] instanceof SputObject);
- assertTrue(code.instructions[5] instanceof ReturnVoid);
}
@Test
@@ -252,8 +258,7 @@
List<String> pgConfigs = ImmutableList.of(
"-identifiernamestring class " + CLASS_NAME + " { static java.lang.String sClassName; }",
"-keep class " + CLASS_NAME + " { static java.lang.String sClassName; }",
- "-dontshrink",
- "-dontoptimize");
+ "-dontshrink");
DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -275,8 +280,7 @@
"-identifiernamestring class " + CLASS_NAME + " { static java.lang.String sClassName; }",
"-keep class " + CLASS_NAME + " { static java.lang.String sClassName; }",
"-keep,allowobfuscation class " + BOO,
- "-dontshrink",
- "-dontoptimize");
+ "-dontshrink");
DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -300,8 +304,7 @@
"-identifiernamestring class " + CLASS_NAME + " { static java.lang.String sFieldName; }",
"-keep class " + CLASS_NAME + " { static java.lang.String sFieldName; }",
"-keep,allowobfuscation class " + BOO + " { <fields>; }",
- "-dontshrink",
- "-dontoptimize");
+ "-dontshrink");
DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -325,8 +328,7 @@
"-identifiernamestring class " + CLASS_NAME + " { static java.lang.String sMethodName; }",
"-keep class " + CLASS_NAME + " { static java.lang.String sMethodName; }",
"-keep,allowobfuscation class " + BOO + " { <methods>; }",
- "-dontshrink",
- "-dontoptimize");
+ "-dontshrink");
DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -356,8 +358,7 @@
List<String> pgConfigs = ImmutableList.of(
"-identifiernamestring class " + CLASS_NAME + " { static void foo(...); }",
- "-keep class " + CLASS_NAME,
- "-dontoptimize");
+ "-keep class " + CLASS_NAME);
DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -366,15 +367,16 @@
assertNotNull(method);
DexCode code = method.getCode().asDexCode();
- assertTrue(code.instructions[0] instanceof InvokeDirect);
- assertTrue(code.instructions[1] instanceof ConstString);
+ checkInstructions(code, ImmutableList.of(
+ InvokeDirect.class,
+ ConstString.class,
+ ConstString.class,
+ InvokeStatic.class,
+ ReturnVoid.class));
ConstString constString = (ConstString) code.instructions[1];
assertEquals("Mixed/form.Boo", constString.getString().toString());
- assertTrue(code.instructions[2] instanceof ConstString);
constString = (ConstString) code.instructions[2];
assertEquals(BOO, constString.getString().toString());
- assertTrue(code.instructions[3] instanceof InvokeStatic);
- assertTrue(code.instructions[4] instanceof ReturnVoid);
}
@Test
@@ -396,8 +398,7 @@
List<String> pgConfigs = ImmutableList.of(
"-identifiernamestring class " + CLASS_NAME + " { static void foo(...); }",
- "-keep class " + CLASS_NAME,
- "-dontoptimize");
+ "-keep class " + CLASS_NAME);
DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -406,17 +407,18 @@
assertNotNull(method);
DexCode code = method.getCode().asDexCode();
- assertTrue(code.instructions[0] instanceof InvokeDirect);
- assertTrue(code.instructions[1] instanceof SgetObject);
- assertTrue(code.instructions[2] instanceof ConstString);
+ checkInstructions(code, ImmutableList.of(
+ InvokeDirect.class,
+ SgetObject.class,
+ ConstString.class,
+ InvokeVirtual.class,
+ ConstString.class,
+ InvokeStatic.class,
+ ReturnVoid.class));
ConstString constString = (ConstString) code.instructions[2];
assertEquals(BOO, constString.getString().toString());
- assertTrue(code.instructions[3] instanceof InvokeVirtual);
- assertTrue(code.instructions[4] instanceof ConstString);
constString = (ConstString) code.instructions[4];
assertEquals(BOO, constString.getString().toString());
- assertTrue(code.instructions[5] instanceof InvokeStatic);
- assertTrue(code.instructions[6] instanceof ReturnVoid);
}
@Test
@@ -440,8 +442,7 @@
List<String> pgConfigs = ImmutableList.of(
"-identifiernamestring class " + CLASS_NAME + " { static void foo(...); }",
"-keep class " + CLASS_NAME,
- "-keep,allowobfuscation class " + BOO,
- "-dontoptimize");
+ "-keep,allowobfuscation class " + BOO);
DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -450,17 +451,18 @@
assertNotNull(method);
DexCode code = method.getCode().asDexCode();
- assertTrue(code.instructions[0] instanceof InvokeDirect);
- assertTrue(code.instructions[1] instanceof SgetObject);
- assertTrue(code.instructions[2] instanceof ConstString);
+ checkInstructions(code, ImmutableList.of(
+ InvokeDirect.class,
+ SgetObject.class,
+ ConstString.class,
+ InvokeVirtual.class,
+ ConstString.class,
+ InvokeStatic.class,
+ ReturnVoid.class));
ConstString constString = (ConstString) code.instructions[2];
assertEquals(BOO, constString.getString().toString());
- assertTrue(code.instructions[3] instanceof InvokeVirtual);
- assertTrue(code.instructions[4] instanceof ConstString);
constString = (ConstString) code.instructions[4];
assertNotEquals(BOO, constString.getString().toString());
- assertTrue(code.instructions[5] instanceof InvokeStatic);
- assertTrue(code.instructions[6] instanceof ReturnVoid);
}
@Test
@@ -492,8 +494,7 @@
+ " static java.lang.reflect.Field *(java.lang.Class,java.lang.String);\n"
+ "}",
"-keep class " + CLASS_NAME,
- "-keep class R { *; }",
- "-dontoptimize");
+ "-keep class R { *; }");
DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -502,13 +503,14 @@
assertNotNull(method);
DexCode code = method.getCode().asDexCode();
- assertTrue(code.instructions[0] instanceof InvokeDirect);
- assertTrue(code.instructions[1] instanceof ConstClass);
- assertTrue(code.instructions[2] instanceof ConstString);
+ checkInstructions(code, ImmutableList.of(
+ InvokeDirect.class,
+ ConstClass.class,
+ ConstString.class,
+ InvokeStatic.class,
+ ReturnVoid.class));
ConstString constString = (ConstString) code.instructions[2];
assertEquals("foo", constString.getString().toString());
- assertTrue(code.instructions[3] instanceof InvokeStatic);
- assertTrue(code.instructions[4] instanceof ReturnVoid);
}
@Test
@@ -540,8 +542,7 @@
+ " static java.lang.reflect.Field *(java.lang.Class,java.lang.String);\n"
+ "}",
"-keep class " + CLASS_NAME,
- "-keep,allowobfuscation class R { *; }",
- "-dontoptimize");
+ "-keep,allowobfuscation class R { *; }");
DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -550,13 +551,14 @@
assertNotNull(method);
DexCode code = method.getCode().asDexCode();
- assertTrue(code.instructions[0] instanceof InvokeDirect);
- assertTrue(code.instructions[1] instanceof ConstClass);
- assertTrue(code.instructions[2] instanceof ConstString);
+ checkInstructions(code, ImmutableList.of(
+ InvokeDirect.class,
+ ConstClass.class,
+ ConstString.class,
+ InvokeStatic.class,
+ ReturnVoid.class));
ConstString constString = (ConstString) code.instructions[2];
assertNotEquals("foo", constString.getString().toString());
- assertTrue(code.instructions[3] instanceof InvokeStatic);
- assertTrue(code.instructions[4] instanceof ReturnVoid);
}
@Test
@@ -595,8 +597,7 @@
+ " *(java.lang.Class,java.lang.String,java.lang.Class[]);\n"
+ "}",
"-keep class " + CLASS_NAME,
- "-keep class R { *; }",
- "-dontoptimize");
+ "-keep class R { *; }");
DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -605,17 +606,18 @@
assertNotNull(method);
DexCode code = method.getCode().asDexCode();
- assertTrue(code.instructions[0] instanceof InvokeDirect);
- assertTrue(code.instructions[1] instanceof ConstClass);
- assertTrue(code.instructions[2] instanceof Const4);
- assertTrue(code.instructions[3] instanceof NewArray);
- assertTrue(code.instructions[4] instanceof Const4);
- assertTrue(code.instructions[5] instanceof AputObject);
- assertTrue(code.instructions[6] instanceof ConstString);
+ checkInstructions(code, ImmutableList.of(
+ InvokeDirect.class,
+ ConstClass.class,
+ Const4.class,
+ NewArray.class,
+ Const4.class,
+ AputObject.class,
+ ConstString.class,
+ InvokeStatic.class,
+ ReturnVoid.class));
ConstString constString = (ConstString) code.instructions[6];
assertEquals("foo", constString.getString().toString());
- assertTrue(code.instructions[7] instanceof InvokeStatic);
- assertTrue(code.instructions[8] instanceof ReturnVoid);
}
@Test
@@ -654,8 +656,7 @@
+ " *(java.lang.Class,java.lang.String,java.lang.Class[]);\n"
+ "}",
"-keep class " + CLASS_NAME,
- "-keep,allowobfuscation class R { *; }",
- "-dontoptimize");
+ "-keep,allowobfuscation class R { *; }");
DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -664,17 +665,18 @@
assertNotNull(method);
DexCode code = method.getCode().asDexCode();
- assertTrue(code.instructions[0] instanceof InvokeDirect);
- assertTrue(code.instructions[1] instanceof ConstClass);
- assertTrue(code.instructions[2] instanceof Const4);
- assertTrue(code.instructions[3] instanceof NewArray);
- assertTrue(code.instructions[4] instanceof Const4);
- assertTrue(code.instructions[5] instanceof AputObject);
- assertTrue(code.instructions[6] instanceof ConstString);
+ checkInstructions(code, ImmutableList.of(
+ InvokeDirect.class,
+ ConstClass.class,
+ Const4.class,
+ NewArray.class,
+ Const4.class,
+ AputObject.class,
+ ConstString.class,
+ InvokeStatic.class,
+ ReturnVoid.class));
ConstString constString = (ConstString) code.instructions[6];
assertNotEquals("foo", constString.getString().toString());
- assertTrue(code.instructions[7] instanceof InvokeStatic);
- assertTrue(code.instructions[8] instanceof ReturnVoid);
}
private DexInspector getInspectorAfterRunR8(
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
index 84dd3b7..ca86a53 100644
--- a/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.debug.DebugTestBase;
import com.android.tools.r8.debug.DebugTestConfig;
import com.android.tools.r8.debug.DexDebugTestConfig;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import org.junit.BeforeClass;
@@ -26,7 +27,7 @@
@BeforeClass
public static void initDebuggeePath() throws Exception {
- int minSdk = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm());
+ AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
Path outdir = temp.newFolder().toPath();
Path outjar = outdir.resolve("r8_compiled.jar");
Path proguardMapPath = outdir.resolve("proguard.map");
@@ -39,7 +40,7 @@
ImmutableList.of("SourceFile", "LineNumberTable"));
})
.addProgramFiles(DEBUGGEE_JAR)
- .setMinApiLevel(minSdk)
+ .setMinApiLevel(minSdk.getLevel())
.addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
.setMode(CompilationMode.DEBUG)
.setOutput(outjar, OutputMode.DexIndexed)
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
new file mode 100644
index 0000000..b96c17a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.b72391662;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
+import com.android.tools.r8.naming.b72391662.subpackage.OtherPackageSuper;
+import com.android.tools.r8.naming.b72391662.subpackage.OtherPackageTestClass;
+import com.android.tools.r8.utils.AndroidApp;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import joptsimple.internal.Strings;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class B72391662 extends TestBase {
+
+ private void doTest(boolean allowAccessModification, boolean minify) throws Exception {
+ Class mainClass = TestMain.class;
+ List<String> config = ImmutableList.of(
+ allowAccessModification ?"-allowaccessmodification" : "",
+ !minify ? "-dontobfuscate" : "",
+ "-keep class " + mainClass.getCanonicalName() + " {",
+ " public void main(java.lang.String[]);",
+ "}",
+ "-keep class " + TestClass.class.getCanonicalName() + " {",
+ " *;",
+ "}",
+ "-keep class " + OtherPackageTestClass.class.getCanonicalName() + " {",
+ " *;",
+ "}"
+ );
+
+ AndroidApp app = readClassesAndAndriodJar(ImmutableList.of(
+ mainClass, Interface.class, Super.class, TestClass.class,
+ OtherPackageSuper.class, OtherPackageTestClass.class));
+ app = compileWithR8(app, Strings.join(config, System.lineSeparator()));
+ assertEquals("123451234567\nABC\n", runOnArt(app, mainClass.getCanonicalName()));
+ }
+
+ @Test
+ @IgnoreIfVmOlderThan(Version.V7_0_0)
+ public void test() throws Exception {
+ doTest(true, true);
+ doTest(true, false);
+ doTest(false, true);
+ doTest(false, false);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java b/src/test/java/com/android/tools/r8/naming/b72391662/Interface.java
similarity index 69%
copy from src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
copy to src/test/java/com/android/tools/r8/naming/b72391662/Interface.java
index 09e94f7..803466e 100644
--- a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/Interface.java
@@ -1,8 +1,9 @@
// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.nonnull;
-public class FieldAccessTest {
- String fld;
+package com.android.tools.r8.naming.b72391662;
+
+public interface Interface {
+ int getValue();
}
diff --git a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java b/src/test/java/com/android/tools/r8/naming/b72391662/Super.java
similarity index 66%
copy from src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
copy to src/test/java/com/android/tools/r8/naming/b72391662/Super.java
index 09e94f7..acb891d 100644
--- a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/Super.java
@@ -1,8 +1,11 @@
// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.nonnull;
-public class FieldAccessTest {
- String fld;
+package com.android.tools.r8.naming.b72391662;
+
+public class Super {
+ int returnFive() {
+ return 5;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/TestClass.java b/src/test/java/com/android/tools/r8/naming/b72391662/TestClass.java
new file mode 100644
index 0000000..88efc09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/TestClass.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.b72391662;
+
+import java.util.function.IntSupplier;
+import java.util.function.Supplier;
+
+public class TestClass extends Super implements Interface {
+ private final int value;
+
+ TestClass() {
+ this.value = 3;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ static String staticMethod() {
+ return "1";
+ }
+
+ String instanceMethod() {
+ return "2";
+ }
+
+ private int y(IntSupplier x) {
+ return x.getAsInt();
+ }
+ int x() {
+ return y(super::returnFive);
+ }
+
+ Object supplyNull() {
+ System.out.print("A");
+ return null;
+ }
+
+ Object useSupplier(Supplier<Object> a) {
+ return a.get();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/TestMain.java b/src/test/java/com/android/tools/r8/naming/b72391662/TestMain.java
new file mode 100644
index 0000000..4da4b87
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/TestMain.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.b72391662;
+
+import com.android.tools.r8.naming.b72391662.subpackage.OtherPackageTestClass;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
+
+public class TestMain extends Super {
+ private Object supplyNull() {
+ System.out.print("C");
+ return null;
+ }
+
+ private Object useSupplier(Supplier<Object> a) {
+ return a.get();
+ }
+
+ private static void printString(Supplier<String> stringSupplier) {
+ System.out.print(stringSupplier.get());
+ }
+
+ private static void printInteger(Integer i) {
+ System.out.print(i);
+ }
+
+ private static void printValue(Supplier<Interface> s) {
+ System.out.print(s.get().getValue());
+ }
+
+ private static void printNewArrayLength(IntFunction<Interface[]> s) {
+ System.out.print(s.apply(4).length);
+ }
+
+ public static void main(String[] args) {
+ // Test with an instance in this package.
+ TestClass instanceInThisPackage = new TestClass();
+ printString(TestClass::staticMethod);
+ printString(instanceInThisPackage::instanceMethod);
+ printValue(TestClass::new);
+ printNewArrayLength(TestClass[]::new);
+ printInteger(instanceInThisPackage.x());
+
+ // Test with an instance in another package.
+ OtherPackageTestClass instanceInOtherPackage = new OtherPackageTestClass();
+ printString(OtherPackageTestClass::staticMethod);
+ printString(instanceInOtherPackage::instanceMethod);
+ printValue(OtherPackageTestClass::new);
+ printNewArrayLength(OtherPackageTestClass[]::new);
+ printInteger(instanceInOtherPackage.x());
+
+ Function<Integer, Integer> lambda = x -> x + 2;
+ printInteger(lambda.apply(4));
+
+ List<Integer> list = new ArrayList<>();
+ list.add(5);
+ list.forEach(e -> { System.out.println(e + 2);});
+
+ instanceInThisPackage.useSupplier(instanceInThisPackage::supplyNull);
+ instanceInOtherPackage.useSupplier(instanceInOtherPackage::supplyNull);
+ TestMain instanceOfThisClass = new TestMain();
+ instanceOfThisClass.useSupplier(instanceOfThisClass::supplyNull);
+
+ System.out.println("");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java b/src/test/java/com/android/tools/r8/naming/b72391662/subpackage/OtherPackageSuper.java
similarity index 61%
copy from src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
copy to src/test/java/com/android/tools/r8/naming/b72391662/subpackage/OtherPackageSuper.java
index 09e94f7..01aee8a 100644
--- a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/subpackage/OtherPackageSuper.java
@@ -1,8 +1,11 @@
// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.nonnull;
-public class FieldAccessTest {
- String fld;
+package com.android.tools.r8.naming.b72391662.subpackage;
+
+public class OtherPackageSuper {
+ int returnFive() {
+ return 5;
+ }
}
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/subpackage/OtherPackageTestClass.java b/src/test/java/com/android/tools/r8/naming/b72391662/subpackage/OtherPackageTestClass.java
new file mode 100644
index 0000000..0476a6a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/subpackage/OtherPackageTestClass.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.b72391662.subpackage;
+
+import com.android.tools.r8.naming.b72391662.Interface;
+import java.util.function.IntSupplier;
+import java.util.function.Supplier;
+
+public class OtherPackageTestClass extends OtherPackageSuper implements Interface{
+ public final int value;
+
+ public OtherPackageTestClass() {
+ this.value = 3;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public static String staticMethod() {
+ return "1";
+ }
+
+ public String instanceMethod() {
+ return "2";
+ }
+
+ private int y(IntSupplier x) {
+ return x.getAsInt();
+ }
+
+ public int x() {
+ return y(super::returnFive);
+ }
+
+ public Object supplyNull() {
+ System.out.print("B");
+ return null;
+ }
+
+ public Object useSupplier(Supplier<Object> a) {
+ return a.get();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/A.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/A.java
new file mode 100644
index 0000000..96b8a06
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/A.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.overloadaggressively;
+
+public class A {
+ public volatile int f1;
+ volatile Object f2;
+ public volatile B f3;
+}
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/B.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/B.java
new file mode 100644
index 0000000..6660196
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/B.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.overloadaggressively;
+
+public class B {
+ volatile int f1 = 8;
+ volatile Object f2 = "d8";
+ volatile String f3 = "r8";
+
+ public int getF1() {
+ return f1;
+ }
+
+ public Object getF2() {
+ return f2;
+ }
+
+ public String getF3() {
+ return f3;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/FieldResolution.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/FieldResolution.java
new file mode 100644
index 0000000..26f5df6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/FieldResolution.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.overloadaggressively;
+
+import java.lang.reflect.Field;
+import java.util.Random;
+
+public class FieldResolution {
+ public static void main(String[] args) throws Exception {
+ A a = new A();
+ B b = new B();
+
+ Field f3 = A.class.getField("f3");
+ f3.set(a, b);
+ assert a.f3 != null;
+ assert a.f3 == b;
+
+ Field f1 = A.class.getField("f1");
+ Random random = new Random();
+ int next = random.nextInt();
+ f1.set(a, next);
+ a.f3.f1 = next;
+ int diff = a.f1 - b.f1;
+ System.out.println("diff: " + diff);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/FieldUpdater.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/FieldUpdater.java
new file mode 100644
index 0000000..8f5011c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/FieldUpdater.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.overloadaggressively;
+
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+
+public class FieldUpdater {
+ @SuppressWarnings("unchecked")
+ public static void main(String[] args) throws Exception {
+ A a = new A();
+ B b = new B();
+ AtomicReferenceFieldUpdater f3Updater =
+ AtomicReferenceFieldUpdater.newUpdater(A.class, B.class, "f3");
+ f3Updater.set(a, b);
+ AtomicReferenceFieldUpdater f2Updater =
+ AtomicReferenceFieldUpdater.newUpdater(A.class, Object.class, "f2");
+ f2Updater.set(a, b);
+ assert a.f2 instanceof B;
+ assert a.f2 == a.f3;
+ ((B) a.f2).f2 = a;
+ assert b.f2 instanceof A;
+ assert ((A) b.f2).f2 == b;
+
+ Random random = new Random();
+ int next = random.nextInt();
+ AtomicIntegerFieldUpdater f1Updater =
+ AtomicIntegerFieldUpdater.newUpdater(A.class, "f1");
+ f1Updater.set(a, next);
+ B viaF3 = a.f3;
+ viaF3.f1 = next;
+ int diff = viaF3.f1 - a.f1;
+ System.out.println("diff: " + diff);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/MethodResolution.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/MethodResolution.java
new file mode 100644
index 0000000..c791031
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/MethodResolution.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.overloadaggressively;
+
+import java.lang.reflect.Method;
+
+public class MethodResolution {
+ public static void main(String[] args) throws Exception {
+ B b = new B();
+
+ int originalF1 = b.getF1();
+ Method getF1 = B.class.getMethod("getF1", (Class[]) null);
+ int diff = ((Integer) getF1.invoke(b)) - originalF1;
+ System.out.println("diff: " + diff);
+
+ Object originalF2 = b.getF2();
+ Method getF2 = B.class.getMethod("getF2", (Class[]) null);
+ System.out.println(originalF2 + " v.s. " + getF2.invoke(b));
+
+ String originalF3 = b.getF3();
+ Method getF3 = B.class.getMethod("getF3", (Class[]) null);
+ System.out.println(originalF3 + " v.s. " + getF3.invoke(b));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
new file mode 100644
index 0000000..dce8880
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
@@ -0,0 +1,203 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.overloadaggressively;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.CompatProguardCommandBuilder;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Kind;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class OverloadAggressivelyTest extends TestBase {
+ private final DexVm dexVm;
+ private final boolean overloadaggressively;
+
+ public OverloadAggressivelyTest(DexVm dexVm, boolean overloadaggressively) {
+ this.dexVm = dexVm;
+ this.overloadaggressively = overloadaggressively;
+ }
+
+ @Parameters(name = "vm: {0}, overloadaggressively: {1}")
+ public static Collection<Object[]> data() {
+ List<Object[]> testCases = new ArrayList<>();
+ for (DexVm version : DexVm.values()) {
+ if (version.getKind() == Kind.HOST) {
+ testCases.add(new Object[]{version, true});
+ testCases.add(new Object[]{version, false});
+ }
+ }
+ return testCases;
+ }
+
+ private AndroidApp runR8(AndroidApp app, Class main, Path out) throws Exception {
+ R8Command command =
+ ToolHelper.addProguardConfigurationConsumer(
+ ToolHelper.prepareR8CommandBuilder(app),
+ pgConfig -> {
+ pgConfig.setPrintMapping(true);
+ pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
+ })
+ .addProguardConfiguration(
+ ImmutableList.copyOf(Iterables.concat(ImmutableList.of(
+ keepMainProguardConfiguration(main),
+ overloadaggressively ? "-overloadaggressively" : ""),
+ CompatProguardCommandBuilder.REFLECTIONS)),
+ Origin.unknown())
+ .setOutput(out, OutputMode.DexIndexed)
+ .build();
+ return ToolHelper.runR8(command, o -> o.enableInlining = false);
+ }
+
+ @Test
+ public void fieldUpdater() throws Exception {
+ Assume.assumeTrue(ToolHelper.artSupported());
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(FieldUpdater.class),
+ ToolHelper.getClassAsBytes(A.class),
+ ToolHelper.getClassAsBytes(B.class)
+ };
+ AndroidApp originalApp = buildAndroidApp(classes);
+ Path out = temp.getRoot().toPath();
+ AndroidApp processedApp = runR8(originalApp, FieldUpdater.class, out);
+
+ DexInspector dexInspector = new DexInspector(processedApp);
+ ClassSubject a = dexInspector.clazz(A.class.getCanonicalName());
+ DexEncodedField f1 = a.field("int", "f1").getField();
+ assertNotNull(f1);
+ DexEncodedField f2 = a.field("java.lang.Object", "f2").getField();
+ assertNotNull(f2);
+ // TODO(b/72858955): due to the potential reflective access, they should have different names
+ // by R8's improved reflective access detection or via keep rules.
+ assertEquals(overloadaggressively, f1.field.name == f2.field.name);
+ DexEncodedField f3 = a.field(B.class.getCanonicalName(), "f3").getField();
+ assertNotNull(f3);
+ // TODO(b/72858955): ditto
+ assertEquals(overloadaggressively, f1.field.name == f3.field.name);
+ // TODO(b/72858955): ditto
+ assertEquals(overloadaggressively, f2.field.name == f3.field.name);
+
+ String main = FieldUpdater.class.getCanonicalName();
+ ProcessResult javaOutput = runOnJava(main, classes);
+ assertEquals(0, javaOutput.exitCode);
+ ProcessResult artOutput = runOnArtRaw(processedApp, main, dexVm);
+ // TODO(b/72858955): eventually, R8 should avoid this field resolution conflict.
+ if (overloadaggressively) {
+ assertNotEquals(0, artOutput.exitCode);
+ assertTrue(artOutput.stderr.contains("ClassCastException"));
+ } else {
+ assertEquals(0, artOutput.exitCode);
+ assertEquals(javaOutput.stdout.trim(), artOutput.stdout.trim());
+ // ART may dump its own debugging info through stderr.
+ // assertEquals(javaOutput.stderr.trim(), artOutput.stderr.trim());
+ }
+ }
+
+ @Test
+ public void fieldResolution() throws Exception {
+ Assume.assumeTrue(ToolHelper.artSupported());
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(FieldResolution.class),
+ ToolHelper.getClassAsBytes(A.class),
+ ToolHelper.getClassAsBytes(B.class)
+ };
+ AndroidApp originalApp = buildAndroidApp(classes);
+ Path out = temp.getRoot().toPath();
+ AndroidApp processedApp = runR8(originalApp, FieldResolution.class, out);
+
+ DexInspector dexInspector = new DexInspector(processedApp);
+ ClassSubject a = dexInspector.clazz(A.class.getCanonicalName());
+ DexEncodedField f1 = a.field("int", "f1").getField();
+ assertNotNull(f1);
+ DexEncodedField f3 = a.field(B.class.getCanonicalName(), "f3").getField();
+ assertNotNull(f3);
+ // TODO(b/72858955): due to the potential reflective access, they should have different names
+ // by R8's improved reflective access detection or via keep rules.
+ assertEquals(overloadaggressively, f1.field.name == f3.field.name);
+
+ String main = FieldResolution.class.getCanonicalName();
+ ProcessResult javaOutput = runOnJava(main, classes);
+ assertEquals(0, javaOutput.exitCode);
+ ProcessResult artOutput = runOnArtRaw(processedApp, main, dexVm);
+ // TODO(b/72858955): R8 should avoid field resolution conflict even w/ -overloadaggressively.
+ if (overloadaggressively) {
+ assertNotEquals(0, artOutput.exitCode);
+ assertTrue(artOutput.stderr.contains("IllegalArgumentException"));
+ } else {
+ assertEquals(0, artOutput.exitCode);
+ assertEquals(javaOutput.stdout.trim(), artOutput.stdout.trim());
+ // ART may dump its own debugging info through stderr.
+ // assertEquals(javaOutput.stderr.trim(), artOutput.stderr.trim());
+ }
+ }
+
+ @Test
+ public void methodResolution() throws Exception {
+ Assume.assumeTrue(ToolHelper.artSupported());
+ byte[][] classes = {
+ ToolHelper.getClassAsBytes(MethodResolution.class),
+ ToolHelper.getClassAsBytes(B.class)
+ };
+ AndroidApp originalApp = buildAndroidApp(classes);
+ Path out = temp.getRoot().toPath();
+ AndroidApp processedApp = runR8(originalApp, MethodResolution.class, out);
+
+ DexInspector dexInspector = new DexInspector(processedApp);
+ ClassSubject b = dexInspector.clazz(B.class.getCanonicalName());
+ DexEncodedMethod m1 =
+ b.method("int", "getF1", ImmutableList.of()).getMethod();
+ assertNotNull(m1);
+ DexEncodedMethod m2 =
+ b.method("java.lang.Object", "getF2", ImmutableList.of()).getMethod();
+ // TODO(b/72858955): due to the potential reflective access, they should have different names.
+ assertEquals(overloadaggressively, m1.method.name == m2.method.name);
+ DexEncodedMethod m3 =
+ b.method("java.lang.String", "getF3", ImmutableList.of()).getMethod();
+ assertNotNull(m3);
+ // TODO(b/72858955): ditto
+ assertEquals(overloadaggressively, m1.method.name == m3.method.name);
+ // TODO(b/72858955): ditto
+ assertEquals(overloadaggressively, m2.method.name == m3.method.name);
+
+ String main = MethodResolution.class.getCanonicalName();
+ ProcessResult javaOutput = runOnJava(main, classes);
+ assertEquals(0, javaOutput.exitCode);
+ ProcessResult artOutput = runOnArtRaw(processedApp, main, dexVm);
+ // TODO(b/72858955): R8 should avoid method resolution conflict even w/ -overloadaggressively.
+ if (overloadaggressively) {
+ assertEquals(0, artOutput.exitCode);
+ assertNotEquals(javaOutput.stdout.trim(), artOutput.stdout.trim());
+ } else {
+ assertEquals(0, artOutput.exitCode);
+ assertEquals(javaOutput.stdout.trim(), artOutput.stdout.trim());
+ // ART may dump its own debugging info through stderr.
+ // assertEquals(javaOutput.stderr.trim(), artOutput.stderr.trim());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
new file mode 100644
index 0000000..66d76b7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
@@ -0,0 +1,370 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.overloadaggressively;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Kind;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+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.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ValidNameConflictTest extends JasminTestBase {
+ private final String CLASS_NAME = "Example";
+ private final String MSG = "You are seeing undefined behavior.";
+
+ private final String REFLECTIONS =
+ "-identifiernamestring public class java.lang.Class {\n"
+ + " public java.lang.reflect.Field getField(java.lang.String);\n"
+ + " public java.lang.reflect.Method getMethod(java.lang.String, java.lang.Class[]);"
+ + "}";
+
+ private final DexVm dexVm;
+
+ public ValidNameConflictTest(DexVm dexVm) {
+ this.dexVm = dexVm;
+ }
+
+ @Parameters(name = "vm: {0}")
+ public static Collection<Object> data() {
+ List<Object> testCases = new ArrayList<>();
+ for (DexVm version : DexVm.values()) {
+ if (version.getKind() == Kind.HOST) {
+ testCases.add(version);
+ }
+ }
+ return testCases;
+ }
+
+ private JasminBuilder buildFieldNameConflictClassFile() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
+ classBuilder.addStaticField("same", "Ljava/lang/String;", "\"" + MSG + "\"");
+ classBuilder.addStaticField("same", "Ljava/lang/Object;", null);
+ classBuilder.addMainMethod(
+ ".limit stack 3",
+ ".limit locals 1",
+ "ldc Example",
+ "ldc \"same\"",
+ "invokevirtual java/lang/Class/getField(Ljava/lang/String;)Ljava/lang/reflect/Field;",
+ "astore_0",
+ "getstatic java/lang/System/out Ljava/io/PrintStream;",
+ "aload_0",
+ "aconst_null",
+ "invokevirtual java/lang/reflect/Field/get(Ljava/lang/Object;)Ljava/lang/Object;",
+ "invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V",
+ "return");
+ return builder;
+ }
+
+ @Test
+ public void remainFieldNameConflictDueToKeepRules() throws Exception {
+ Assume.assumeTrue(ToolHelper.artSupported());
+ JasminBuilder builder = buildFieldNameConflictClassFile();
+ ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
+ assertEquals(0, javaOutput.exitCode);
+
+ List<String> pgConfigs = ImmutableList.of(
+ "-keep public class " + CLASS_NAME + " {\n"
+ + " public static void main(java.lang.String[]);\n"
+ + " static <fields>;"
+ + "}\n"
+ + "-printmapping\n",
+ REFLECTIONS,
+ "-dontshrink");
+ AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+ assertTrue(clazz.isPresent());
+ FieldSubject f1 = clazz.field("java.lang.String", "same");
+ assertTrue(f1.isPresent());
+ assertFalse(f1.isRenamed());
+ FieldSubject f2 = clazz.field("java.lang.Object", "same");
+ assertTrue(f2.isPresent());
+ assertFalse(f2.isRenamed());
+ assertEquals(f1.getField().field.name, f2.getField().field.name);
+
+ ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+ assertEquals(0, artOutput.exitCode);
+ // With reserved *same* names, it is not guaranteed to have same output.
+ // assertEquals(javaOutput.stdout, artOutput.stdout);
+ }
+
+
+ @Test
+ public void remainFieldNameConflictWithUseUniqueClassMemberNames() throws Exception {
+ Assume.assumeTrue(ToolHelper.artSupported());
+ JasminBuilder builder = buildFieldNameConflictClassFile();
+ ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
+ assertEquals(0, javaOutput.exitCode);
+
+ List<String> pgConfigs = ImmutableList.of(
+ keepMainProguardConfiguration(CLASS_NAME),
+ REFLECTIONS,
+ "-useuniqueclassmembernames",
+ "-dontshrink");
+ AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+ assertTrue(clazz.isPresent());
+ FieldSubject f1 = clazz.field("java.lang.String", "same");
+ assertTrue(f1.isPresent());
+ assertTrue(f1.isRenamed());
+ FieldSubject f2 = clazz.field("java.lang.Object", "same");
+ assertTrue(f2.isPresent());
+ assertTrue(f2.isRenamed());
+ // TODO(b/73149686): -useuniqueclassmembernames for field minification is buggy.
+ // assertEquals(f1.getField().field.name, f2.getField().field.name);
+
+ ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+ assertEquals(0, artOutput.exitCode);
+ // TODO(b/73149686): with reserved *same* names, it is not guaranteed to have same output.
+ assertEquals(javaOutput.stdout, artOutput.stdout);
+ }
+
+ @Test
+ public void resolveFieldNameConflictWithoutAnyOption() throws Exception {
+ Assume.assumeTrue(ToolHelper.artSupported());
+ JasminBuilder builder = buildFieldNameConflictClassFile();
+ ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
+ assertEquals(0, javaOutput.exitCode);
+
+ List<String> pgConfigs = ImmutableList.of(
+ keepMainProguardConfiguration(CLASS_NAME),
+ REFLECTIONS,
+ "-dontshrink");
+ AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+ assertTrue(clazz.isPresent());
+ FieldSubject f1 = clazz.field("java.lang.String", "same");
+ assertTrue(f1.isPresent());
+ assertTrue(f1.isRenamed());
+ FieldSubject f2 = clazz.field("java.lang.Object", "same");
+ assertTrue(f2.isPresent());
+ assertTrue(f2.isRenamed());
+ assertNotEquals(f1.getField().field.name, f2.getField().field.name);
+
+ ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+ assertEquals(0, artOutput.exitCode);
+ assertEquals(javaOutput.stdout, artOutput.stdout);
+ }
+
+ @Test
+ public void resolveFieldNameConflictEvenWithOverloadAggressively() throws Exception {
+ Assume.assumeTrue(ToolHelper.artSupported());
+ JasminBuilder builder = buildFieldNameConflictClassFile();
+ ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
+ assertEquals(0, javaOutput.exitCode);
+
+ List<String> pgConfigs = ImmutableList.of(
+ keepMainProguardConfiguration(CLASS_NAME),
+ REFLECTIONS,
+ "-overloadaggressively",
+ "-dontshrink");
+ AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+ assertTrue(clazz.isPresent());
+ FieldSubject f1 = clazz.field("java.lang.String", "same");
+ assertTrue(f1.isPresent());
+ assertTrue(f1.isRenamed());
+ FieldSubject f2 = clazz.field("java.lang.Object", "same");
+ assertTrue(f2.isPresent());
+ assertTrue(f2.isRenamed());
+ // TODO(b/72858955): R8 should resolve this field name conflict.
+ assertEquals(f1.getField().field.name, f2.getField().field.name);
+
+ ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+ assertEquals(0, artOutput.exitCode);
+ // TODO(b/72858955): distinct names will make the output be same.
+ // assertEquals(javaOutput.stdout, artOutput.stdout);
+ }
+
+ private JasminBuilder buildMethodNameConflictClassFile() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
+ classBuilder.addStaticMethod("same", ImmutableList.of(), "Ljava/lang/String;",
+ "ldc \"" + MSG + "\"",
+ "areturn");
+ classBuilder.addStaticMethod("same", ImmutableList.of(), "Ljava/lang/Object;",
+ "aconst_null",
+ "areturn");
+ classBuilder.addMainMethod(
+ ".limit stack 3",
+ ".limit locals 1",
+ "ldc Example",
+ "ldc \"same\"",
+ "aconst_null",
+ "checkcast [Ljava/lang/Class;",
+ "invokevirtual java/lang/Class/getMethod"
+ + "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;",
+ "astore_0",
+ "getstatic java/lang/System/out Ljava/io/PrintStream;",
+ "aload_0",
+ "aconst_null",
+ "aconst_null",
+ "checkcast [Ljava/lang/Object;",
+ "invokevirtual java/lang/reflect/Method/invoke"
+ + "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;",
+ "invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V",
+ "return");
+ return builder;
+ }
+
+ @Test
+ public void remainMethodNameConflictDueToKeepRules() throws Exception {
+ Assume.assumeTrue(ToolHelper.artSupported());
+ JasminBuilder builder = buildMethodNameConflictClassFile();
+ ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
+ assertEquals(0, javaOutput.exitCode);
+
+ List<String> pgConfigs = ImmutableList.of(
+ "-keep public class " + CLASS_NAME + " {\n"
+ + " public static void main(java.lang.String[]);\n"
+ + " static <methods>;"
+ + "}\n"
+ + "-printmapping\n",
+ REFLECTIONS,
+ "-useuniqueclassmembernames",
+ "-dontshrink");
+ AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+ assertTrue(clazz.isPresent());
+ MethodSubject m1 = clazz.method("java.lang.String", "same", ImmutableList.of());
+ assertTrue(m1.isPresent());
+ assertFalse(m1.isRenamed());
+ MethodSubject m2 = clazz.method("java.lang.Object", "same", ImmutableList.of());
+ assertTrue(m2.isPresent());
+ assertFalse(m2.isRenamed());
+ assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+
+ ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+ assertEquals(0, artOutput.exitCode);
+ // With name conflict, it is not guaranteed to get the same output.
+ // assertEquals(javaOutput.stdout, artOutput.stdout);
+ }
+
+ @Test
+ public void remainMethodNameConflictWithUseUniqueClassMemberNames() throws Exception {
+ Assume.assumeTrue(ToolHelper.artSupported());
+ JasminBuilder builder = buildMethodNameConflictClassFile();
+ ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
+ assertEquals(0, javaOutput.exitCode);
+
+ List<String> pgConfigs = ImmutableList.of(
+ keepMainProguardConfiguration(CLASS_NAME),
+ REFLECTIONS,
+ "-useuniqueclassmembernames",
+ "-dontshrink");
+ AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+ assertTrue(clazz.isPresent());
+ MethodSubject m1 = clazz.method("java.lang.String", "same", ImmutableList.of());
+ assertTrue(m1.isPresent());
+ assertTrue(m1.isRenamed());
+ MethodSubject m2 = clazz.method("java.lang.Object", "same", ImmutableList.of());
+ assertTrue(m2.isPresent());
+ assertTrue(m2.isRenamed());
+ assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+
+ ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+ assertEquals(0, artOutput.exitCode);
+ // With name conflict, it is not guaranteed to get the same output.
+ // assertEquals(javaOutput.stdout, artOutput.stdout);
+ }
+
+ @Test
+ public void resolveMethodNameConflictWithoutAnyOption() throws Exception {
+ Assume.assumeTrue(ToolHelper.artSupported());
+ JasminBuilder builder = buildMethodNameConflictClassFile();
+ ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
+ assertEquals(0, javaOutput.exitCode);
+
+ List<String> pgConfigs = ImmutableList.of(
+ keepMainProguardConfiguration(CLASS_NAME),
+ REFLECTIONS,
+ "-dontshrink");
+ AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+ assertTrue(clazz.isPresent());
+ MethodSubject m1 = clazz.method("java.lang.String", "same", ImmutableList.of());
+ assertTrue(m1.isPresent());
+ assertTrue(m1.isRenamed());
+ MethodSubject m2 = clazz.method("java.lang.Object", "same", ImmutableList.of());
+ assertTrue(m2.isPresent());
+ assertTrue(m2.isRenamed());
+ // TODO(b/73149686): R8 should be able to fix this conflict w/o -overloadaggressively.
+ // assertNotEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+
+ ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+ assertEquals(0, artOutput.exitCode);
+ // TODO(b/73149686): distinct names will output the same results.
+ // assertEquals(javaOutput.stdout, artOutput.stdout);
+ }
+
+ @Test
+ public void resolveMethodNameConflictEvenWithOverloadAggressively() throws Exception {
+ Assume.assumeTrue(ToolHelper.artSupported());
+ JasminBuilder builder = buildMethodNameConflictClassFile();
+ ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
+ assertEquals(0, javaOutput.exitCode);
+
+ List<String> pgConfigs = ImmutableList.of(
+ keepMainProguardConfiguration(CLASS_NAME),
+ REFLECTIONS,
+ "-overloadaggressively",
+ "-dontshrink");
+ AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+ assertTrue(clazz.isPresent());
+ MethodSubject m1 = clazz.method("java.lang.String", "same", ImmutableList.of());
+ assertTrue(m1.isPresent());
+ assertTrue(m1.isRenamed());
+ MethodSubject m2 = clazz.method("java.lang.Object", "same", ImmutableList.of());
+ assertTrue(m2.isPresent());
+ assertTrue(m2.isRenamed());
+ // TODO(b/73149686): R8 should be able to fix this conflict even w/ -overloadaggressively.
+ assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+
+ ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+ assertEquals(0, artOutput.exitCode);
+ // TODO(b/73149686): distinct names will output the same results.
+ // assertEquals(javaOutput.stdout, artOutput.stdout);
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b69906048/Regress69906048Test.java b/src/test/java/com/android/tools/r8/regress/b69906048/Regress69906048Test.java
index 0553fe4..f1d846e 100644
--- a/src/test/java/com/android/tools/r8/regress/b69906048/Regress69906048Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b69906048/Regress69906048Test.java
@@ -16,7 +16,7 @@
public void buildWithD8AndRunWithDalvikOrArt() throws Exception {
AndroidApp androidApp = compileWithR8(
ImmutableList.of(ClassWithAnnotations.class, AnAnnotation.class),
- options -> options.minApiLevel = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm()));
+ options -> options.minApiLevel = ToolHelper.getMinApiLevelForDexVm().getLevel());
String result = runOnArt(androidApp, ClassWithAnnotations.class);
Assert.assertEquals("@" + AnAnnotation.class.getCanonicalName() + "()", result);
}
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
index 3725a24..cf2be59 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
@@ -46,10 +46,10 @@
List<byte[]> allBytes = new ArrayList<>();
allBytes.addAll(ASM_CLASSES);
for (Class clazz : CLASSES) {
- allBytes.add(getBytesFromJavaClass(clazz));
+ allBytes.add(ToolHelper.getClassAsBytes(clazz));
}
ensureSameOutput(Main.class.getCanonicalName(),
- ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm()),
+ ToolHelper.getMinApiLevelForDexVm(),
allBytes.toArray(new byte[allBytes.size()][]));
}
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
index 3c79bd3..3c724c0 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
@@ -26,7 +26,7 @@
AndroidApp app = compileWithR8(
ImmutableList.of(testClass),
keepMainProguardConfiguration(testClass, true, false),
- options -> options.inlineAccessors = false);
+ options -> options.enableInlining = false);
DexInspector x = new DexInspector(app);
ClassSubject clazz = x.clazz(ClassWithAssertions.class);
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index 94e7669..cce5a96 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -79,7 +79,7 @@
.build();
ToolHelper.runR8(command, options -> {
// Disable inlining to make this test not depend on inlining decisions.
- options.inlineAccessors = false;
+ options.enableInlining = false;
});
}
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index fdb35fb..cba8686 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -94,7 +94,7 @@
.addProguardConfigurationFiles(keepRules, printMapping);
ToolHelper.getAppBuilder(builder).addProgramFiles(originalDex);
// Turn off inlining, as we want the mapping that is printed to be stable.
- ToolHelper.runR8(builder.build(), options -> options.inlineAccessors = false);
+ ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
Path outputmapping = out.resolve("mapping.txt");
// Remove comments.
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 732f397..fe1f1eb 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -145,7 +145,7 @@
.addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
.addLibraryFiles(JAR_LIBRARIES);
ToolHelper.getAppBuilder(builder).addProgramFiles(Paths.get(programFile));
- ToolHelper.runR8(builder.build(), options -> options.inlineAccessors = inline);
+ ToolHelper.runR8(builder.build(), options -> options.enableInlining = inline);
}
public static void shaking1HasNoClassUnused(DexInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 402e639..64ea9b4 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -49,7 +49,7 @@
private Consumer<InternalOptions> configureOptions(Consumer<OutlineOptions> optionsConsumer) {
return options -> {
// Disable inlining to make sure that code looks as expected.
- options.inlineAccessors = false;
+ options.enableInlining = false;
// Also apply outline options.
optionsConsumer.accept(options.outline);
};
diff --git a/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
index 2dec26c..dae717e 100644
--- a/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
@@ -59,7 +59,7 @@
compileWithR8(
AndroidApp.builder().addDexProgramData(builder.compile(), Origin.unknown()).build(),
keepMainProguardConfiguration("Test"),
- options -> options.inlineAccessors = false);
+ options -> options.enableInlining = false);
DexInspector inspector = new DexInspector(app);
MethodSubject method = inspector.clazz("Test").method("void", "test", ImmutableList.of());
@@ -111,7 +111,7 @@
compileWithR8(
AndroidApp.builder().addDexProgramData(builder.compile(), Origin.unknown()).build(),
keepMainProguardConfiguration("Test"),
- options -> options.inlineAccessors = false);
+ options -> options.enableInlining = false);
DexInspector inspector = new DexInspector(app);
MethodSubject method = inspector.clazz("Test").method("void", "test", ImmutableList.of());
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 7db0d6c..95ecd4f 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.code.Const4;
import com.android.tools.r8.code.ConstString;
import com.android.tools.r8.code.Goto;
+import com.android.tools.r8.code.IfEqz;
import com.android.tools.r8.code.IfNez;
import com.android.tools.r8.code.Iget;
import com.android.tools.r8.code.IgetBoolean;
@@ -755,9 +756,10 @@
@Override
public MethodSignature getOriginalSignature() {
MethodSignature signature = getFinalSignature();
- return clazz.naming != null ?
- (MethodSignature) clazz.naming.lookup(signature).getOriginalSignature() :
- signature;
+ MemberNaming memberNaming = clazz.naming != null ? clazz.naming.lookup(signature) : null;
+ return memberNaming != null
+ ? (MethodSignature) memberNaming.getOriginalSignature()
+ : signature;
}
@Override
@@ -878,9 +880,10 @@
@Override
public FieldSignature getOriginalSignature() {
FieldSignature signature = getFinalSignature();
- return clazz.naming != null ?
- (FieldSignature) clazz.naming.lookup(signature).getOriginalSignature() :
- signature;
+ MemberNaming memberNaming = clazz.naming != null ? clazz.naming.lookup(signature) : null;
+ return memberNaming != null
+ ? (FieldSignature) memberNaming.getOriginalSignature()
+ : signature;
}
@Override
@@ -999,6 +1002,10 @@
return instruction instanceof IfNez;
}
+ boolean isIfEqz(Instruction instruction) {
+ return instruction instanceof IfEqz;
+ }
+
boolean isFieldAccess(Instruction instruction) {
return isInstanceGet(instruction)
|| isInstancePut(instruction)
@@ -1109,6 +1116,10 @@
return factory.isIfNez(instruction);
}
+ public boolean isIfEqz() {
+ return factory.isIfEqz(instruction);
+ }
+
public boolean isReturnVoid() {
return factory.isReturnVoid(instruction);
}
diff --git a/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java b/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
new file mode 100644
index 0000000..3e759d9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
@@ -0,0 +1,83 @@
+// 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.
+package com.android.tools.r8.utils;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+
+import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+
+public class FeatureClassMappingTest {
+
+ @Test
+ public void testSimpleParse() throws Exception {
+
+ List<String> lines =
+ ImmutableList.of(
+ "# Comment, don't care about contents: even more ::::",
+ "com.google.base:base",
+ "", // Empty lines allowed
+ "com.google.feature1:feature1",
+ "com.google.feature1:feature1", // Multiple definitions of the same predicate allowed.
+ "com.google$:feature1",
+ "_com.google:feature21",
+ "com.google.*:feature32");
+ FeatureClassMapping mapping = new FeatureClassMapping(lines);
+ }
+
+ private void ensureThrowsMappingException(List<String> lines) {
+ try {
+ new FeatureClassMapping(lines);
+ assertFalse(true);
+ } catch (FeatureMappingException e) {
+ // Expected
+ }
+ }
+
+ private void ensureThrowsMappingException(String string) {
+ ensureThrowsMappingException(ImmutableList.of(string));
+ }
+
+ @Test
+ public void testLookup() throws Exception {
+ List<String> lines =
+ ImmutableList.of(
+ "com.google.Base:base",
+ "",
+ "com.google.Feature1:feature1",
+ "com.google.Feature1:feature1", // Multiple definitions of the same predicate allowed.
+ "com.google.different.*:feature1",
+ "_com.Google:feature21",
+ "com.google.bas42.*:feature42");
+ FeatureClassMapping mapping = new FeatureClassMapping(lines);
+ assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1");
+ assertEquals(mapping.featureForClass("com.google.different.Feature1"), "feature1");
+ assertEquals(mapping.featureForClass("com.google.different.Foobar"), "feature1");
+ assertEquals(mapping.featureForClass("com.google.Base"), "base");
+ assertEquals(mapping.featureForClass("com.google.bas42.foo.bar.bar.Foo"), "feature42");
+ assertEquals(mapping.featureForClass("com.google.bas42.f$o$o$.bar43.bar.Foo"), "feature42");
+ assertEquals(mapping.featureForClass("_com.Google"), "feature21");
+ }
+
+ @Test
+ public void testWrongLines() throws Exception {
+ // No colon.
+ ensureThrowsMappingException("foo");
+ ensureThrowsMappingException("com.google.base");
+ // Two colons.
+ ensureThrowsMappingException(ImmutableList.of("a:b:c"));
+
+ // Empty identifier.
+ ensureThrowsMappingException("com..google:feature1");
+
+ // Ambiguous redefinition
+ ensureThrowsMappingException(
+ ImmutableList.of("com.google.foo:feature1", "com.google.foo:feature2"));
+ ensureThrowsMappingException(
+ ImmutableList.of("com.google.foo.*:feature1", "com.google.foo.*:feature2"));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
index b14ef8a..fb4fe70 100644
--- a/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
+++ b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
@@ -61,7 +61,7 @@
private void addAndroidJarsToCommandLine(List<String> args) {
args.add("--lib");
- args.add(ToolHelper.getAndroidJar(AndroidApiLevel.K.getLevel()).toAbsolutePath().toString());
+ args.add(ToolHelper.getAndroidJar(AndroidApiLevel.K).toAbsolutePath().toString());
}
// Add the jars used in the com.android.tools.r8.maindexlist.MainDexTracingTest test.
diff --git a/src/test/kotlinR8TestResources/dataclass/MainCopy.kt b/src/test/kotlinR8TestResources/dataclass/MainCopy.kt
index 2e4f047..2644302 100644
--- a/src/test/kotlinR8TestResources/dataclass/MainCopy.kt
+++ b/src/test/kotlinR8TestResources/dataclass/MainCopy.kt
@@ -16,7 +16,8 @@
fun testDataClassCopy() {
val albert = Person("Albert", 28)
- val olderJonas = albert.copy("Jonas", albert.age + 10)
+ val youngerJonas = albert.copy("Jonas", albert.age - 10)
+ val olderJonas = youngerJonas.copy("Jonas", albert.age + 20)
println("Name: ${olderJonas.name}")
println("Age: ${olderJonas.age}")
}
\ No newline at end of file
diff --git a/src/test/kotlinR8TestResources/dataclass/MainCopyWithDefault.kt b/src/test/kotlinR8TestResources/dataclass/MainCopyWithDefault.kt
deleted file mode 100644
index 2df19f7..0000000
--- a/src/test/kotlinR8TestResources/dataclass/MainCopyWithDefault.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package dataclass
-
-/**
- * This is an example of copying an instance of a data class Person using its copy method and
- * relying on default values for some of its properties. Therefore the compiler will generate an
- * invoke to copy$default method which is a wrapper around copy and deals with default values.
- *
- * See https://kotlinlang.org/docs/reference/data-classes.html#copying.
- */
-fun main(args: Array<String>) {
- testDataClassCopyWithDefault()
-}
-
-fun testDataClassCopyWithDefault() {
- val albert = Person("Albert", 28)
- // We don't pass a 'name', thus we copy the property value of the receiver. This will result
- // in calling the copy$default method instead of the copy method.
- val olderAlbert = albert.copy(age = albert.age + 10)
- println("Name: ${olderAlbert.name}")
- println("Age: ${olderAlbert.age}")
-}
\ No newline at end of file
diff --git a/src/test/kotlinR8TestResources/intrinsics/Intrinsics.kt b/src/test/kotlinR8TestResources/intrinsics/Intrinsics.kt
new file mode 100644
index 0000000..90d26bf
--- /dev/null
+++ b/src/test/kotlinR8TestResources/intrinsics/Intrinsics.kt
@@ -0,0 +1,32 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package intrinsics
+
+import java.lang.reflect.InvocationTargetException
+
+fun main(args: Array<String>) {
+ testParameterNullCheck()
+}
+
+fun expectsNonNullParameters(a: String, b: String): String = a + b
+
+fun testParameterNullCheck() {
+ println("> ${expectsNonNullParameters("pre", "post")} <")
+
+ val intrinsics = Class.forName("intrinsics.IntrinsicsKt")
+ val method = intrinsics.getMethod(
+ "expectsNonNullParameters", String::class.java, String::class.java)
+
+ println("> ${method.invoke(null, "pre", "post")} <")
+
+ try {
+ println("> ${method.invoke(null, "pre", null)} <")
+ } catch (e: InvocationTargetException) {
+ println("> exception: ${e.targetException::javaClass} <")
+ return
+ }
+ throw AssertionError()
+}
+
diff --git a/third_party/core-lambda-stubs.tar.gz.sha1 b/third_party/core-lambda-stubs.tar.gz.sha1
new file mode 100644
index 0000000..3b8961a
--- /dev/null
+++ b/third_party/core-lambda-stubs.tar.gz.sha1
@@ -0,0 +1 @@
+23dd799b1df85a68110bf06f672177d553b0682c
\ No newline at end of file
diff --git a/tools/archive.py b/tools/archive.py
index 360a0ee..815e416 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -113,6 +113,14 @@
print('Uploading %s to %s' % (tagged_jar, destination))
utils.upload_file_to_cloud_storage(tagged_jar, destination)
print('File available at: %s' % GetUrl(version, file_name, is_master))
+ # Upload extracted maven directory for easy testing in studio.
+ zip_ref = zipfile.ZipFile(utils.MAVEN_ZIP, 'r')
+ zip_ref.extractall(temp)
+ zip_ref.close()
+ utils.upload_dir_to_cloud_storage(
+ os.path.join(temp, 'com'),
+ GetUploadDestination(version, 'com', is_master))
+ print('Maven repo root available at: %s' % GetUrl(version, '', is_master))
if __name__ == '__main__':
sys.exit(Main())
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index cc045a2..47bf7d2 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -210,8 +210,7 @@
# Create directory structure for this version.
version = determine_version()
with utils.TempDir() as tmp_dir:
- version_dir = join(
- tmp_dir, 'com', 'google', 'android', 'tools', 'r8', version, 'r8')
+ version_dir = join(tmp_dir, 'com', 'android', 'tools', 'r8', version)
makedirs(version_dir)
# Write the pom file.
pom_file = join(version_dir, 'r8-' + version + '.pom')
diff --git a/tools/test.py b/tools/test.py
index 58fc4eb..ed99c1d 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -85,7 +85,7 @@
upload_dir = os.path.join(utils.REPO_ROOT, 'build', 'reports', 'tests')
u_dir = uuid.uuid4()
destination = 'gs://%s/%s' % (BUCKET, u_dir)
- utils.upload_dir_to_cloud_storage(upload_dir, destination)
+ utils.upload_dir_to_cloud_storage(upload_dir, destination, is_html=True)
url = 'http://storage.googleapis.com/%s/%s/test/index.html' % (BUCKET, u_dir)
print 'Test results available at: %s' % url
print '@@@STEP_LINK@Test failures@%s@@@' % url
@@ -166,4 +166,3 @@
else:
notify.notify("Tests passed.")
sys.exit(return_code)
-
diff --git a/tools/update_prebuilds_in_android.py b/tools/update_prebuilds_in_android.py
index d6e96a3..7635ba6 100755
--- a/tools/update_prebuilds_in_android.py
+++ b/tools/update_prebuilds_in_android.py
@@ -53,7 +53,9 @@
def download_target(root, url, target):
download_path = os.path.join(root, target)
print 'Downloading: ' + url + ' -> ' + download_path
- urllib.urlretrieve(url, download_path)
+ result = urllib.urlretrieve(url, download_path)
+ if 'X-GUploader-Request-Result: success' not in str(result[1]):
+ raise IOError('Failed to download ' + url)
def Main():
args = parse_arguments()
diff --git a/tools/utils.py b/tools/utils.py
index 90e084b..8716f65 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -73,10 +73,12 @@
if not os.path.isdir(path):
raise
-def upload_dir_to_cloud_storage(directory, destination):
+def upload_dir_to_cloud_storage(directory, destination, is_html=False):
# Upload and make the content encoding right for viewing directly
- cmd = ['gsutil.py', 'cp', '-z', 'html', '-a',
- 'public-read', '-R', directory, destination]
+ cmd = ['gsutil.py', 'cp']
+ if is_html:
+ cmd += ['-z', 'html']
+ cmd += ['-a', 'public-read', '-R', directory, destination]
PrintCmd(cmd)
subprocess.check_call(cmd)
@@ -204,8 +206,7 @@
print('{}-{}(CodeSize): {}'
.format(prefix, segment_name, size))
-# ensure that java version is 1.8.*-internal,
-# as opposed to e.g. 1.7* or 1.8.*-google-v7
+# Ensure that we are not benchmarking with a google jvm.
def check_java_version():
cmd= ['java', '-version']
output = subprocess.check_output(cmd, stderr = subprocess.STDOUT)
@@ -214,10 +215,9 @@
raise Exception("Can't check java version: no version string in output"
" of 'java -version': '{}'".format(output))
version = m.groups(0)[0]
- m = re.search('1[.]8[.].*-internal', version)
- if m is None:
- raise Exception("Incorrect java version, expected: '1.8.*-internal',"
- " actual: {}".format(version))
+ m = re.search('google', version)
+ if m is not None:
+ raise Exception("Do not use google JVM for benchmarking: " + version)
def verify_with_dex2oat(dex_file):