Merge "Keep synthesized '$r8$twr$utility' utility class source method."
diff --git a/build.gradle b/build.gradle
index 5631d94..41f4390 100644
--- a/build.gradle
+++ b/build.gradle
@@ -254,8 +254,10 @@
apt "com.google.auto.value:auto-value:$autoValueVersion"
}
-def r8LibPath = "build/libs/r8lib.jar"
-def r8LibClassesPath = "build/classes/r8lib"
+def r8LibPath = "$buildDir/libs/r8lib.jar"
+def r8LibClassesPath = "$buildDir/classes/r8lib"
+def r8LibGeneratedKeepRulesPath = "$buildDir/generated/keep.txt"
+def r8LibTestPath = "$buildDir/classes/r8libtest"
def osString = OperatingSystem.current().isLinux() ? "linux" :
OperatingSystem.current().isMacOsX() ? "mac" : "windows"
@@ -276,6 +278,8 @@
"android_jar/lib-v24",
"android_jar/lib-v25",
"android_jar/lib-v26",
+ "android_jar/lib-v27",
+ "android_jar/lib-v28",
"core-lambda-stubs",
"dart-sdk",
"ddmlib",
@@ -635,21 +639,24 @@
}
}
+def baseR8CommandLine(args = []) {
+ return ["java", "-ea", "-jar", R8.outputs.files[0]] + args
+}
+
def r8CfCommandLine(input, output, pgconf, args = [], libs = []) {
- return [
- "java", "-ea", "-jar", R8.outputs.files[0],
- "--classfile", "--release",
- input,
- "--output", output,
- "--pg-conf", pgconf,
- "--pg-map-output", output + ".map",
- "--lib", "third_party/openjdk/openjdk-rt-1.8/rt.jar"
- ] + args + libs.collectMany { ["--lib", it] }
+ return baseR8CommandLine([
+ "--classfile", "--release",
+ input,
+ "--output", output,
+ "--pg-conf", pgconf,
+ "--pg-map-output", output + ".map",
+ "--lib", "third_party/openjdk/openjdk-rt-1.8/rt.jar"
+ ] + args + libs.collectMany { ["--lib", it] })
}
def r8LibCreateTask(name, pgConf, r8Task, output, args = [], libs = []) {
return tasks.create("r8Lib${name}", Exec) {
- inputs.files ([pgConf, R8.outputs] + [r8Task.outputs])
+ inputs.files ([pgConf, R8.outputs, r8Task.outputs])
outputs.file output
dependsOn downloadOpenJDKrt
commandLine r8CfCommandLine(r8Task.outputs.files[0], output, pgConf, args, libs)
@@ -657,19 +664,48 @@
}
}
+task testJar(type: Jar, dependsOn: testClasses) {
+ baseName = "r8tests"
+ from sourceSets.test.output
+}
+
+task generateR8LibKeepRules(type: Exec) {
+ doFirst {
+ standardOutput new FileOutputStream(r8LibGeneratedKeepRulesPath)
+ }
+ dependsOn R8
+ dependsOn r8WithoutDeps
+ dependsOn testJar
+ dependsOn downloadOpenJDKrt
+ inputs.files ([R8.outputs, r8WithoutDeps.outputs, testJar.outputs])
+ outputs.file r8LibGeneratedKeepRulesPath
+ commandLine baseR8CommandLine([
+ "printuses",
+ "--keeprules",
+ "third_party/openjdk/openjdk-rt-1.8/rt.jar",
+ r8WithoutDeps.outputs.files[0],
+ testJar.outputs.files[0]])
+ workingDir = projectDir
+ // TODO(b/121350534) Keep this until we have proper generation of keep rules for r8lib.
+ doLast {
+ new File(r8LibGeneratedKeepRulesPath).append("\n-keep class ** { *; }\n")
+ }
+}
+
task R8LibNoDeps {
dependsOn r8LibCreateTask(
"NoDeps",
"src/main/keep.txt",
r8WithoutDeps,
r8LibPath,
- ["--no-tree-shaking", "--no-minification"],
+ ["--pg-conf", generateR8LibKeepRules.outputs.files[0]],
repackageDepsNoRelocate.outputs.files
- ).dependsOn(repackageDepsNoRelocate, r8WithoutDeps)
+ ).dependsOn(repackageDepsNoRelocate, r8WithoutDeps, generateR8LibKeepRules)
}
task R8Lib {
dependsOn r8LibCreateTask("Main", "src/main/keep.txt", R8NoManifest, r8LibPath)
+ outputs.file r8LibPath
}
task CompatDxLib {
@@ -1344,24 +1380,37 @@
workingDir = projectDir
}
-task configureTestForR8Lib(type: Copy) {
- def r8LibTask;
+def getR8LibTask() {
if (project.hasProperty('r8lib')) {
- r8LibTask = R8Lib
+ return R8Lib
} else if (project.hasProperty('r8lib_no_deps')) {
- r8LibTask = R8LibNoDeps
+ return R8LibNoDeps
}
+ return null
+}
+
+task copyR8LibForTests(type: Copy) {
+ def r8LibTask = getR8LibTask()
if (r8LibTask != null) {
dependsOn r8LibTask
from(zipTree(r8LibPath))
into(r8LibClassesPath)
- outputs.file r8LibClassesPath
- doLast {
- test {
- classpath = (classpath - sourceSets.main.output) + files(r8LibClassesPath)
- }
- }
}
+ outputs.dir r8LibClassesPath
+}
+
+task configureTestForR8Lib(type: Copy) {
+ dependsOn testClasses
+ dependsOn copyR8LibForTests
+ // Setting classpath triggers a scan for test files in $buildDir/classes/test that finds all
+ // tests and not just the ones under $test/com/android/tools/r8. That is generally not
+ // something we want so we just copy the desired test files to $r8LibTestPath.
+ if (getR8LibTask() != null) {
+ // Cannot use sourceSets.test.output here since it will copy all tests.
+ from("$buildDir/classes/test/com/android/tools/r8")
+ into(r8LibTestPath + "/com/android/tools/r8")
+ }
+ outputs.dir r8LibTestPath
}
test {
@@ -1459,7 +1508,13 @@
if (project.hasProperty('aosp_jar')) {
dependsOn AospJarTest
}
-
+ if (project.hasProperty('r8lib') || project.hasProperty('r8lib_no_deps')) {
+ dependsOn configureTestForR8Lib
+ classpath =
+ (sourceSets.test.runtimeClasspath - sourceSets.main.output) +
+ files(r8LibClassesPath)
+ testClassesDir = new File(r8LibTestPath)
+ }
if (OperatingSystem.current().isLinux()
|| OperatingSystem.current().isMacOsX()
|| OperatingSystem.current().isWindows()) {
@@ -1486,9 +1541,6 @@
logger.lifecycle("WARNING: Testing in not supported on your platform. Testing is only fully supported on " +
"Linux and partially supported on Mac OS and Windows. Art does not run on other platforms.")
}
- if (project.hasProperty('r8lib') || project.hasProperty('r8lib_no_deps')) {
- dependsOn configureTestForR8Lib
- }
}
// The Art tests we use for R8 are pre-build and downloaded from Google Cloud Storage.
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 48c67fd..48c2cde 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -4,12 +4,12 @@
package com.android.tools.r8;
import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.graphinfo.GraphConsumer;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.MainDexClasses;
@@ -44,7 +44,8 @@
DexApplication application =
new ApplicationReader(app, options, timing).read(executor).toDirect();
AppView<? extends AppInfoWithSubtyping> appView =
- new AppView<>(new AppInfoWithSubtyping(application), GraphLense.getIdentityLense());
+ new AppView<>(
+ new AppInfoWithSubtyping(application), GraphLense.getIdentityLense(), options);
RootSet mainDexRootSet =
new RootSetBuilder(appView, application, options.mainDexKeepRules, options).run(executor);
@@ -73,7 +74,6 @@
// Print -whyareyoukeeping results if any.
if (whyAreYouKeepingConsumer != null) {
- // TODO(b/120959039): This should be ordered!
for (DexDefinition definition : mainDexRootSet.reasonAsked) {
whyAreYouKeepingConsumer.printWhyAreYouKeeping(
enqueuer.getGraphNode(definition), System.out);
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
index a1a2837..a55033a 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -3,8 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graphinfo.GraphConsumer;
import com.android.tools.r8.origin.CommandLineOrigin;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardConfigurationParser;
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index 0d8524b..475ca70 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -84,15 +84,16 @@
DexApplication application =
new ApplicationReader(command.getInputApp(), options, timing).read(executor).toDirect();
AppView<? extends AppInfoWithSubtyping> appView =
- new AppView<>(new AppInfoWithSubtyping(application), GraphLense.getIdentityLense());
+ new AppView<>(
+ new AppInfoWithSubtyping(application), GraphLense.getIdentityLense(), options);
RootSet rootSet =
new RootSetBuilder(
- appView, application, options.proguardConfiguration.getRules(), options)
+ appView, application, options.getProguardConfiguration().getRules(), options)
.run(executor);
Enqueuer enqueuer = new Enqueuer(appView, options, null);
AppInfoWithLiveness appInfo =
enqueuer.traceApplication(
- rootSet, options.proguardConfiguration.getDontWarnPatterns(), executor, timing);
+ rootSet, options.getProguardConfiguration().getDontWarnPatterns(), executor, timing);
RootSetBuilder.writeSeeds(
appInfo, System.out, type -> descriptors.contains(type.toDescriptorString()));
} catch (ExecutionException e) {
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 42fed08..d5f0156 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.AppInfo.ResolutionResult;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
@@ -15,6 +16,9 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueType;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
@@ -22,7 +26,6 @@
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.io.IOException;
-import java.io.PrintStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
@@ -46,19 +49,20 @@
public class PrintUses {
private static final String USAGE =
- "Arguments: <rt.jar> <r8.jar> <sample.jar>\n"
+ "Arguments: [--keeprules] <rt.jar> <r8.jar> <sample.jar>\n"
+ "\n"
+ "PrintUses prints the classes, interfaces, methods and fields used by <sample.jar>,\n"
+ "restricted to classes and interfaces in <r8.jar> that are not in <sample.jar>.\n"
+ "<rt.jar> and <r8.jar> should point to libraries used by <sample.jar>.\n"
+ "\n"
+ "The output is in the same format as what is printed when specifying -printseeds in\n"
- + "a ProGuard configuration file. See also the "
+ + "a ProGuard configuration file. Use --keeprules for outputting proguard keep rules. "
+ + "See also the "
+ PrintSeeds.class.getSimpleName()
+ " program in R8.";
private final Set<String> descriptors;
- private final PrintStream out;
+ private final Printer printer;
private Set<DexType> types = Sets.newIdentityHashSet();
private Map<DexType, Set<DexMethod>> methods = Maps.newIdentityHashMap();
private Map<DexType, Set<DexField>> fields = Maps.newIdentityHashMap();
@@ -115,13 +119,13 @@
@Override
public boolean registerInstanceFieldWrite(DexField field) {
- addField(field);
+ addField(field, false);
return false;
}
@Override
public boolean registerInstanceFieldRead(DexField field) {
- addField(field);
+ addField(field, false);
return false;
}
@@ -133,13 +137,13 @@
@Override
public boolean registerStaticFieldRead(DexField field) {
- addField(field);
+ addField(field, true);
return false;
}
@Override
public boolean registerStaticFieldWrite(DexField field) {
- addField(field);
+ addField(field, true);
return false;
}
@@ -160,8 +164,15 @@
return descriptors.contains(type.toDescriptorString());
}
- private void addField(DexField field) {
+ private void addField(DexField field, boolean isStatic) {
addType(field.type);
+ DexEncodedField baseField =
+ isStatic
+ ? appInfo.lookupStaticTarget(field.clazz, field)
+ : appInfo.lookupInstanceTarget(field.clazz, field);
+ if (baseField != null && baseField.field.clazz != field.clazz) {
+ field = baseField.field;
+ }
addType(field.clazz);
Set<DexField> typeFields = fields.get(field.clazz);
if (typeFields != null) {
@@ -193,6 +204,14 @@
for (DexType type : method.method.proto.parameters.values) {
registerTypeReference(type);
}
+ for (DexAnnotation annotation : method.annotations.annotations) {
+ if (annotation.annotation.type == appInfo.dexItemFactory.annotationThrows) {
+ DexValueArray dexValues = (DexValueArray) annotation.annotation.elements[0].value;
+ for (DexValue dexValType : dexValues.getValues()) {
+ registerTypeReference(((DexValueType) dexValType).value);
+ }
+ }
+ }
registerTypeReference(method.method.proto.returnType);
method.registerCodeReferences(this);
}
@@ -210,21 +229,28 @@
}
}
- public static void main(String[] args) throws Exception {
- if (args.length != 3) {
+ public static void main(String... args) throws Exception {
+ if (args.length != 3 && args.length != 4) {
System.out.println(USAGE.replace("\n", System.lineSeparator()));
return;
}
+ int argumentIndex = 0;
+ boolean printKeep = false;
+ if (args[0].equals("--keeprules")) {
+ printKeep = true;
+ argumentIndex++;
+ }
AndroidApp.Builder builder = AndroidApp.builder();
- Path rtJar = Paths.get(args[0]);
+ Path rtJar = Paths.get(args[argumentIndex++]);
builder.addLibraryFile(rtJar);
- Path r8Jar = Paths.get(args[1]);
+ Path r8Jar = Paths.get(args[argumentIndex++]);
builder.addLibraryFile(r8Jar);
- Path sampleJar = Paths.get(args[2]);
+ Path sampleJar = Paths.get(args[argumentIndex++]);
builder.addProgramFile(sampleJar);
Set<String> descriptors = new HashSet<>(getDescriptors(r8Jar));
descriptors.removeAll(getDescriptors(sampleJar));
- PrintUses printUses = new PrintUses(descriptors, builder.build(), System.out);
+ Printer printer = printKeep ? new KeepPrinter() : new DefaultPrinter();
+ PrintUses printUses = new PrintUses(descriptors, builder.build(), printer);
printUses.analyze();
printUses.print();
if (printUses.errors > 0) {
@@ -237,10 +263,10 @@
return new ArchiveClassFileProvider(path).getClassDescriptors();
}
- private PrintUses(Set<String> descriptors, AndroidApp inputApp, PrintStream out)
+ private PrintUses(Set<String> descriptors, AndroidApp inputApp, Printer printer)
throws Exception {
this.descriptors = descriptors;
- this.out = out;
+ this.printer = printer;
InternalOptions options = new InternalOptions();
application =
new ApplicationReader(inputApp, options, new Timing("PrintUses")).read().toDirect();
@@ -260,69 +286,182 @@
}
private void print() {
- List<DexType> types = new ArrayList<>(this.types);
- types.sort(Comparator.comparing(DexType::toSourceString));
- for (DexType type : types) {
- String typeName = type.toSourceString();
- DexClass dexClass = application.definitionFor(type);
- if (dexClass == null) {
- error("Could not find definition for type " + type.toSourceString());
- continue;
+ errors = printer.print(application, types, methods, fields);
+ }
+
+ private abstract static class Printer {
+
+ void append(String string) {
+ System.out.print(string);
+ }
+
+ void appendLine(String string) {
+ System.out.println(string);
+ }
+
+ void printArguments(DexMethod method) {
+ append("(");
+ for (int i = 0; i < method.getArity(); i++) {
+ if (i != 0) {
+ append(",");
+ }
+ append(method.proto.parameters.values[i].toSourceString());
}
- out.println(typeName);
- List<DexMethod> methods = new ArrayList<>(this.methods.get(type));
- List<String> methodDefinitions = new ArrayList<>(methods.size());
- for (DexMethod method : methods) {
- DexEncodedMethod encodedMethod = dexClass.lookupMethod(method);
- if (encodedMethod == null) {
- error("Could not find definition for method " + method.toSourceString());
+ append(")");
+ }
+
+ abstract void printConstructorName(DexEncodedMethod encodedMethod);
+
+ void printError(String message) {
+ appendLine("# Error: " + message);
+ }
+
+ abstract void printField(DexClass dexClass, DexField field);
+
+ abstract void printMethod(DexEncodedMethod encodedMethod, String typeName);
+
+ void printNameAndReturn(DexEncodedMethod encodedMethod) {
+ if (encodedMethod.accessFlags.isConstructor()) {
+ printConstructorName(encodedMethod);
+ } else {
+ DexMethod method = encodedMethod.method;
+ append(method.proto.returnType.toSourceString());
+ append(" ");
+ append(method.name.toSourceString());
+ }
+ }
+
+ abstract void printTypeHeader(DexClass dexClass);
+
+ abstract void printTypeFooter();
+
+ int print(
+ DexApplication application,
+ Set<DexType> types,
+ Map<DexType, Set<DexMethod>> methods,
+ Map<DexType, Set<DexField>> fields) {
+ int errors = 0;
+ List<DexType> sortedTypes = new ArrayList<>(types);
+ sortedTypes.sort(Comparator.comparing(DexType::toSourceString));
+ for (DexType type : sortedTypes) {
+ DexClass dexClass = application.definitionFor(type);
+ if (dexClass == null) {
+ printError("Could not find definition for type " + type.toSourceString());
+ errors++;
continue;
}
- methodDefinitions.add(getMethodSourceString(encodedMethod));
+ printTypeHeader(dexClass);
+ List<DexEncodedMethod> methodDefinitions = new ArrayList<>(methods.size());
+ for (DexMethod method : methods.get(type)) {
+ DexEncodedMethod encodedMethod = dexClass.lookupMethod(method);
+ if (encodedMethod == null) {
+ printError("Could not find definition for method " + method.toSourceString());
+ errors++;
+ continue;
+ }
+ methodDefinitions.add(encodedMethod);
+ }
+ methodDefinitions.sort(Comparator.comparing(x -> x.method.name.toSourceString()));
+ for (DexEncodedMethod encodedMethod : methodDefinitions) {
+ printMethod(encodedMethod, dexClass.type.toSourceString());
+ }
+ List<DexField> sortedFields = new ArrayList<>(fields.get(type));
+ sortedFields.sort(Comparator.comparing(DexField::toSourceString));
+ for (DexField field : sortedFields) {
+ printField(dexClass, field);
+ }
+ printTypeFooter();
}
- methodDefinitions.sort(Comparator.naturalOrder());
- for (String encodedMethod : methodDefinitions) {
- out.println(typeName + ": " + encodedMethod);
- }
- List<DexField> fields = new ArrayList<>(this.fields.get(type));
- fields.sort(Comparator.comparing(DexField::toSourceString));
- for (DexField field : fields) {
- out.println(
- typeName + ": " + field.type.toSourceString() + " " + field.name.toSourceString());
- }
+ return errors;
}
}
- private void error(String message) {
- out.println("# Error: " + message);
- errors += 1;
- }
+ private static class DefaultPrinter extends Printer {
- private static String getMethodSourceString(DexEncodedMethod encodedMethod) {
- DexMethod method = encodedMethod.method;
- StringBuilder builder = new StringBuilder();
- if (encodedMethod.accessFlags.isConstructor()) {
+ @Override
+ public void printConstructorName(DexEncodedMethod encodedMethod) {
if (encodedMethod.accessFlags.isStatic()) {
- builder.append("<clinit>");
+ append("<clinit>");
} else {
- String holderName = method.holder.toSourceString();
+ String holderName = encodedMethod.method.holder.toSourceString();
String constructorName = holderName.substring(holderName.lastIndexOf('.') + 1);
- builder.append(constructorName);
+ append(constructorName);
}
- } else {
- builder
- .append(method.proto.returnType.toSourceString())
- .append(" ")
- .append(method.name.toSourceString());
}
- builder.append("(");
- for (int i = 0; i < method.getArity(); i++) {
- if (i != 0) {
- builder.append(",");
+
+ @Override
+ void printMethod(DexEncodedMethod encodedMethod, String typeName) {
+ append(typeName + ": ");
+ printNameAndReturn(encodedMethod);
+ printArguments(encodedMethod.method);
+ appendLine("");
+ }
+
+ @Override
+ void printTypeHeader(DexClass dexClass) {
+ appendLine(dexClass.type.toSourceString());
+ }
+
+ @Override
+ void printTypeFooter() {}
+
+ @Override
+ void printField(DexClass dexClass, DexField field) {
+ appendLine(
+ dexClass.type.toSourceString()
+ + ": "
+ + field.type.toSourceString()
+ + " "
+ + field.name.toString());
+ }
+ }
+
+ private static class KeepPrinter extends Printer {
+
+ @Override
+ public void printTypeHeader(DexClass dexClass) {
+ if (dexClass.isInterface()) {
+ append("-keep interface " + dexClass.type.toSourceString() + " {\n");
+ } else if (dexClass.accessFlags.isEnum()) {
+ append("-keep enum " + dexClass.type.toSourceString() + " {\n");
+ } else {
+ append("-keep class " + dexClass.type.toSourceString() + " {\n");
}
- builder.append(method.proto.parameters.values[i].toSourceString());
}
- builder.append(")");
- return builder.toString();
+
+ @Override
+ public void printConstructorName(DexEncodedMethod encodedMethod) {
+ append("<init>");
+ }
+
+ @Override
+ public void printField(DexClass dexClass, DexField field) {
+ append(" " + field.type.toSourceString() + " " + field.name.toString() + ";\n");
+ }
+
+ @Override
+ public void printMethod(DexEncodedMethod encodedMethod, String typeName) {
+ // Static initializers do not require keep rules - it is kept by keeping the class.
+ if (encodedMethod.accessFlags.isConstructor() && encodedMethod.accessFlags.isStatic()) {
+ return;
+ }
+ append(" ");
+ if (encodedMethod.isPublicMethod()) {
+ append("public ");
+ } else if (encodedMethod.isPrivateMethod()) {
+ append("private ");
+ }
+ if (encodedMethod.isStatic()) {
+ append("static ");
+ }
+ printNameAndReturn(encodedMethod);
+ printArguments(encodedMethod.method);
+ appendLine(";");
+ }
+
+ @Override
+ public void printTypeFooter() {
+ appendLine("}");
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index ae166d3..c1664a6 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.dex.Marker.Tool;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
@@ -19,7 +20,6 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.graphinfo.GraphConsumer;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.optimize.EnumOrdinalMapCollector;
import com.android.tools.r8.ir.optimize.MethodPoolCollection;
@@ -250,14 +250,15 @@
inputApp.closeInternalArchiveProviders();
AppView<AppInfoWithSubtyping> appView =
- new AppView<>(new AppInfoWithSubtyping(application), GraphLense.getIdentityLense());
+ new AppView<>(
+ new AppInfoWithSubtyping(application), GraphLense.getIdentityLense(), options);
RootSet rootSet;
String proguardSeedsData = null;
timing.begin("Strip unused code");
try {
Set<DexType> missingClasses = appView.appInfo().getMissingClasses();
missingClasses = filterMissingClasses(
- missingClasses, options.proguardConfiguration.getDontWarnPatterns());
+ missingClasses, options.getProguardConfiguration().getDontWarnPatterns());
if (!missingClasses.isEmpty()) {
missingClasses.forEach(
clazz -> {
@@ -279,18 +280,21 @@
rootSet =
new RootSetBuilder(
- appView, application, options.proguardConfiguration.getRules(), options)
+ appView, application, options.getProguardConfiguration().getRules(), options)
.run(executorService);
Enqueuer enqueuer = new Enqueuer(appView, options, null, compatibility);
appView.setAppInfo(
enqueuer.traceApplication(
rootSet,
- options.proguardConfiguration.getDontWarnPatterns(),
+ options.getProguardConfiguration().getDontWarnPatterns(),
executorService,
timing));
+ assert rootSet.verifyKeptFieldsAreAccessedAndLive(appView.appInfo().withLiveness());
+ assert rootSet.verifyKeptMethodsAreTargetedAndLive(appView.appInfo().withLiveness());
+ assert rootSet.verifyKeptTypesAreLive(appView.appInfo().withLiveness());
- if (options.proguardConfiguration.isPrintSeeds()) {
+ if (options.getProguardConfiguration().isPrintSeeds()) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
PrintStream out = new PrintStream(bytes);
RootSetBuilder.writeSeeds(appView.appInfo().withLiveness(), out, type -> true);
@@ -301,16 +305,17 @@
TreePruner pruner =
new TreePruner(application, appView.appInfo().withLiveness(), options);
application = pruner.run();
+
// Recompute the subtyping information.
appView.setAppInfo(
appView
.appInfo()
.withLiveness()
.prunedCopyFrom(application, pruner.getRemovedClasses()));
- new AbstractMethodRemover(appView.appInfo()).run();
+ new AbstractMethodRemover(appView.appInfo().withLiveness()).run();
}
- new AnnotationRemover(appView.appInfo().withLiveness(), options)
+ new AnnotationRemover(appView.appInfo().withLiveness(), appView.graphLense(), options)
.ensureValid(compatibility)
.run();
@@ -334,7 +339,7 @@
timing.end();
}
- if (options.proguardConfiguration.isAccessModificationAllowed()) {
+ if (options.getProguardConfiguration().isAccessModificationAllowed()) {
appView.setGraphLense(
ClassAndMemberPublicizer.run(executorService, timing, application, appView, rootSet));
// We can now remove visibility bridges. Note that we do not need to update the
@@ -343,12 +348,32 @@
application = new VisibilityBridgeRemover(appView.appInfo(), application).run();
}
+ // Build conservative main dex content before first round of tree shaking. This is used
+ // by certain optimizations to avoid introducing additional class references into main dex
+ // classes, as that can cause the final number of main dex methods to grow.
+ RootSet mainDexRootSet = null;
+ MainDexClasses mainDexClasses = MainDexClasses.NONE;
+ if (!options.mainDexKeepRules.isEmpty()) {
+ // Find classes which may have code executed before secondary dex files installation.
+ mainDexRootSet =
+ new RootSetBuilder(appView, application, options.mainDexKeepRules, options)
+ .run(executorService);
+ Enqueuer enqueuer = new Enqueuer(appView, options, null, true);
+ AppInfoWithLiveness mainDexAppInfo =
+ enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
+ // LiveTypes is the tracing result.
+ Set<DexType> mainDexBaseClasses = new HashSet<>(mainDexAppInfo.liveTypes);
+ // Calculate the automatic main dex list according to legacy multidex constraints.
+ mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
+ }
+
if (appView.appInfo().hasLiveness()) {
AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
- if (options.proguardConfiguration.hasApplyMappingFile()) {
+ if (options.getProguardConfiguration().hasApplyMappingFile()) {
SeedMapper seedMapper =
- SeedMapper.seedMapperFromFile(options.proguardConfiguration.getApplyMappingFile());
+ SeedMapper.seedMapperFromFile(
+ options.getProguardConfiguration().getApplyMappingFile());
timing.begin("apply-mapping");
appView.setGraphLense(
new ProguardMapApplier(appView.withLiveness(), seedMapper).run(timing));
@@ -361,11 +386,9 @@
timing.end();
}
appView.setGraphLense(new MemberRebindingAnalysis(appViewWithLiveness, options).run());
- // TODO(117854943): Pass information on main dex classes to class merging.
- if (options.enableHorizontalClassMerging
- && options.mainDexKeepRules.isEmpty()
- && application.mainDexList.isEmpty()) {
- StaticClassMerger staticClassMerger = new StaticClassMerger(appViewWithLiveness, options);
+ if (options.enableHorizontalClassMerging) {
+ StaticClassMerger staticClassMerger =
+ new StaticClassMerger(appViewWithLiveness, options, mainDexClasses);
appView.setGraphLense(staticClassMerger.run());
appViewWithLiveness.setAppInfo(
appViewWithLiveness
@@ -376,7 +399,12 @@
timing.begin("ClassMerger");
VerticalClassMerger verticalClassMerger =
new VerticalClassMerger(
- application, appViewWithLiveness, executorService, options, timing);
+ application,
+ appViewWithLiveness,
+ executorService,
+ options,
+ timing,
+ mainDexClasses);
appView.setGraphLense(verticalClassMerger.run());
appView.setVerticallyMergedClasses(verticalClassMerger.getMergedClasses());
timing.end();
@@ -425,7 +453,8 @@
Set<DexCallSite> desugaredCallSites;
CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
try {
- IRConverter converter = new IRConverter(appView, options, timing, printer, rootSet);
+ IRConverter converter =
+ new IRConverter(appView, options, timing, printer, mainDexClasses, rootSet);
application = converter.optimize(application, executorService);
desugaredCallSites = converter.getDesugaredCallSites();
} finally {
@@ -449,14 +478,10 @@
new SourceFileRewriter(appView.appInfo(), options).run();
timing.end();
- MainDexClasses mainDexClasses = MainDexClasses.NONE;
if (!options.mainDexKeepRules.isEmpty()) {
appView.setAppInfo(new AppInfoWithSubtyping(application));
- // Lets find classes which may have code executed before secondary dex files installation.
- RootSet mainDexRootSet =
- new RootSetBuilder(appView, application, options.mainDexKeepRules, options)
- .run(executorService);
-
+ // No need to build a new main dex root set
+ assert mainDexRootSet != null;
GraphConsumer mainDexKeptGraphConsumer = options.mainDexKeptGraphConsumer;
WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = null;
if (!mainDexRootSet.reasonAsked.isEmpty()) {
@@ -465,9 +490,9 @@
}
Enqueuer enqueuer = new Enqueuer(appView, options, mainDexKeptGraphConsumer, true);
+ // Find classes which may have code executed before secondary dex files installation.
AppInfoWithLiveness mainDexAppInfo =
enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
-
// LiveTypes is the tracing result.
Set<DexType> mainDexBaseClasses = new HashSet<>(mainDexAppInfo.liveTypes);
// Calculate the automatic main dex list according to legacy multidex constraints.
@@ -478,7 +503,6 @@
.run();
}
if (whyAreYouKeepingConsumer != null) {
- // TODO(b/120959039): Sort the set so the order is always the same print order!
for (DexDefinition dexDefinition : mainDexRootSet.reasonAsked) {
whyAreYouKeepingConsumer.printWhyAreYouKeeping(
enqueuer.getGraphNode(dexDefinition), System.out);
@@ -506,7 +530,7 @@
appView.setAppInfo(
enqueuer.traceApplication(
rootSet,
- options.proguardConfiguration.getDontWarnPatterns(),
+ options.getProguardConfiguration().getDontWarnPatterns(),
executorService,
timing));
@@ -518,16 +542,17 @@
appViewWithLiveness
.appInfo()
.prunedCopyFrom(application, pruner.getRemovedClasses()));
+
// Print reasons on the application after pruning, so that we reflect the actual result.
if (whyAreYouKeepingConsumer != null) {
- // TODO(b/120959039): Sort the set so the order is always the same print order!
for (DexDefinition dexDefinition : rootSet.reasonAsked) {
whyAreYouKeepingConsumer.printWhyAreYouKeeping(
enqueuer.getGraphNode(dexDefinition), System.out);
}
}
// Remove annotations that refer to types that no longer exist.
- new AnnotationRemover(appView.appInfo().withLiveness(), options).run();
+ new AnnotationRemover(appView.appInfo().withLiveness(), appView.graphLense(), options)
+ .run();
if (!mainDexClasses.isEmpty()) {
// Remove types that no longer exists from the computed main dex list.
mainDexClasses = mainDexClasses.prunedCopy(appView.appInfo().withLiveness());
@@ -577,11 +602,13 @@
// If a method filter is present don't produce output since the application is likely partial.
if (options.hasMethodsFilter()) {
System.out.println("Finished compilation with method filter: ");
- options.methodsFilter.forEach((m) -> System.out.println(" - " + m));
+ options.methodsFilter.forEach(m -> System.out.println(" - " + m));
return;
}
+ // Validity checks.
assert application.classes().stream().allMatch(DexClass::isValid);
+ assert rootSet.verifyKeptItemsAreKept(application, appView.appInfo(), options);
// Generate the resulting application resources.
writeApplication(
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 9f25fbc..39630f3 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -5,8 +5,8 @@
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graphinfo.GraphConsumer;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.origin.StandardOutOrigin;
@@ -663,9 +663,9 @@
assert internal.enableHorizontalClassMerging || !proguardConfiguration.isOptimizing();
assert internal.enableVerticalClassMerging || !proguardConfiguration.isOptimizing();
if (internal.debug) {
- internal.proguardConfiguration.getKeepAttributes().lineNumberTable = true;
- internal.proguardConfiguration.getKeepAttributes().localVariableTable = true;
- internal.proguardConfiguration.getKeepAttributes().localVariableTypeTable = true;
+ internal.getProguardConfiguration().getKeepAttributes().lineNumberTable = true;
+ internal.getProguardConfiguration().getKeepAttributes().localVariableTable = true;
+ internal.getProguardConfiguration().getKeepAttributes().localVariableTypeTable = true;
// TODO(zerny): Should we support inlining in debug mode? b/62937285
internal.enableInlining = false;
internal.enableClassInlining = false;
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 4db0374..4288e55 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.4.17-dev";
+ public static final String LABEL = "1.4.19-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 06d9af4..24e82a7 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -306,7 +306,7 @@
if (options.configurationConsumer != null) {
ExceptionUtils.withConsumeResourceHandler(
options.reporter, options.configurationConsumer,
- options.proguardConfiguration.getParsedConfiguration());
+ options.getProguardConfiguration().getParsedConfiguration());
}
if (options.usageInformationConsumer != null && deadCode != null) {
ExceptionUtils.withConsumeResourceHandler(
diff --git a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
index e680f70..ae59576 100644
--- a/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
+++ b/src/main/java/com/android/tools/r8/dex/ResourceAdapter.java
@@ -46,7 +46,7 @@
public DataEntryResource adaptIfNeeded(DataEntryResource file) {
// Adapt name, if needed.
ProguardPathFilter adaptResourceFileNamesFilter =
- options.proguardConfiguration.getAdaptResourceFilenames();
+ options.getProguardConfiguration().getAdaptResourceFilenames();
String name =
adaptResourceFileNamesFilter.isEnabled()
&& !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
@@ -56,7 +56,7 @@
assert name != null;
// Adapt contents, if needed.
ProguardPathFilter adaptResourceFileContentsFilter =
- options.proguardConfiguration.getAdaptResourceFileContents();
+ options.getProguardConfiguration().getAdaptResourceFileContents();
byte[] contents =
adaptResourceFileContentsFilter.isEnabled()
&& !file.getName().toLowerCase().endsWith(FileUtils.CLASS_EXTENSION)
@@ -80,7 +80,8 @@
public DataDirectoryResource adaptIfNeeded(DataDirectoryResource directory) {
// First check if this directory should even be in the output.
- ProguardPathFilter keepDirectoriesFilter = options.proguardConfiguration.getKeepDirectories();
+ ProguardPathFilter keepDirectoriesFilter =
+ options.getProguardConfiguration().getKeepDirectories();
if (!keepDirectoriesFilter.matches(directory.getName())) {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/AnnotationGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/AnnotationGraphNode.java
new file mode 100644
index 0000000..5242510
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/AnnotationGraphNode.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.experimental.graphinfo;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public final class AnnotationGraphNode extends GraphNode {
+
+ private final GraphNode annotatedNode;
+
+ public AnnotationGraphNode(GraphNode annotatedNode) {
+ super(annotatedNode.isLibraryNode());
+ this.annotatedNode = annotatedNode;
+ }
+
+ public GraphNode getAnnotatedNode() {
+ return annotatedNode;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o
+ || (o instanceof AnnotationGraphNode
+ && ((AnnotationGraphNode) o).annotatedNode.equals(annotatedNode));
+ }
+
+ @Override
+ public int hashCode() {
+ return 7 * annotatedNode.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "annotated " + annotatedNode.toString();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/ClassGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/ClassGraphNode.java
new file mode 100644
index 0000000..edbffbe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/ClassGraphNode.java
@@ -0,0 +1,39 @@
+// 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.experimental.graphinfo;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.ClassReference;
+
+@Keep
+public final class ClassGraphNode extends GraphNode {
+
+ private final ClassReference reference;
+
+ public ClassGraphNode(boolean isLibraryNode, ClassReference reference) {
+ super(isLibraryNode);
+ assert reference != null;
+ this.reference = reference;
+ }
+
+ public ClassReference getReference() {
+ return reference;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o
+ || (o instanceof ClassGraphNode && ((ClassGraphNode) o).reference == reference);
+ }
+
+ @Override
+ public int hashCode() {
+ return reference.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return reference.getDescriptor();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/FieldGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/FieldGraphNode.java
new file mode 100644
index 0000000..b091613
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/FieldGraphNode.java
@@ -0,0 +1,39 @@
+// 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.experimental.graphinfo;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.FieldReference;
+
+@Keep
+public final class FieldGraphNode extends GraphNode {
+
+ private final FieldReference reference;
+
+ public FieldGraphNode(boolean isLibraryNode, FieldReference reference) {
+ super(isLibraryNode);
+ assert reference != null;
+ this.reference = reference;
+ }
+
+ public FieldReference getReference() {
+ return reference;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o
+ || (o instanceof FieldGraphNode && ((FieldGraphNode) o).reference == reference);
+ }
+
+ @Override
+ public int hashCode() {
+ return reference.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return reference.toString();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graphinfo/GraphConsumer.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphConsumer.java
similarity index 91%
rename from src/main/java/com/android/tools/r8/graphinfo/GraphConsumer.java
rename to src/main/java/com/android/tools/r8/experimental/graphinfo/GraphConsumer.java
index 8c124e5..3c7bda1 100644
--- a/src/main/java/com/android/tools/r8/graphinfo/GraphConsumer.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphConsumer.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.graphinfo;
+package com.android.tools.r8.experimental.graphinfo;
import com.android.tools.r8.KeepForSubclassing;
diff --git a/src/main/java/com/android/tools/r8/graphinfo/GraphEdgeInfo.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
similarity index 95%
rename from src/main/java/com/android/tools/r8/graphinfo/GraphEdgeInfo.java
rename to src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.java
index c0b3f3b..0c838ac 100644
--- a/src/main/java/com/android/tools/r8/graphinfo/GraphEdgeInfo.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphEdgeInfo.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.graphinfo;
+package com.android.tools.r8.experimental.graphinfo;
public class GraphEdgeInfo {
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphNode.java
new file mode 100644
index 0000000..b99485a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/GraphNode.java
@@ -0,0 +1,29 @@
+// 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.experimental.graphinfo;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public abstract class GraphNode {
+
+ private final boolean isLibraryNode;
+
+ public GraphNode(boolean isLibraryNode) {
+ this.isLibraryNode = isLibraryNode;
+ }
+
+ public boolean isLibraryNode() {
+ return isLibraryNode;
+ }
+
+ @Override
+ public abstract boolean equals(Object o);
+
+ @Override
+ public abstract int hashCode();
+
+ @Override
+ public abstract String toString();
+}
diff --git a/src/main/java/com/android/tools/r8/graphinfo/KeepRuleGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/KeepRuleGraphNode.java
similarity index 94%
rename from src/main/java/com/android/tools/r8/graphinfo/KeepRuleGraphNode.java
rename to src/main/java/com/android/tools/r8/experimental/graphinfo/KeepRuleGraphNode.java
index 2f4d5e1..7eb55a8 100644
--- a/src/main/java/com/android/tools/r8/graphinfo/KeepRuleGraphNode.java
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/KeepRuleGraphNode.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.graphinfo;
+package com.android.tools.r8.experimental.graphinfo;
import com.android.tools.r8.Keep;
import com.android.tools.r8.origin.Origin;
@@ -16,6 +16,7 @@
private final ProguardKeepRule rule;
public KeepRuleGraphNode(ProguardKeepRule rule) {
+ super(false);
assert rule != null;
this.rule = rule;
}
@@ -49,7 +50,7 @@
* {@code <keep-rule-file>:<keep-rule-start-line>:<keep-rule-start-column>}.
*/
@Override
- public String identity() {
+ public String toString() {
return (getOrigin() == Origin.unknown() ? getContent() : getOrigin())
+ ":"
+ shortPositionInfo(getPosition());
diff --git a/src/main/java/com/android/tools/r8/experimental/graphinfo/MethodGraphNode.java b/src/main/java/com/android/tools/r8/experimental/graphinfo/MethodGraphNode.java
new file mode 100644
index 0000000..94fe8e7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/experimental/graphinfo/MethodGraphNode.java
@@ -0,0 +1,39 @@
+// 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.experimental.graphinfo;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.MethodReference;
+
+@Keep
+public final class MethodGraphNode extends GraphNode {
+
+ private final MethodReference reference;
+
+ public MethodGraphNode(boolean isLibraryNode, MethodReference reference) {
+ super(isLibraryNode);
+ assert reference != null;
+ this.reference = reference;
+ }
+
+ public MethodReference getReference() {
+ return reference;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return this == o
+ || (o instanceof MethodGraphNode && ((MethodGraphNode) o).reference == reference);
+ }
+
+ @Override
+ public int hashCode() {
+ return reference.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return reference.toString();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 2354918..f9a9dde 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -6,18 +6,21 @@
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.shaking.VerticalClassMerger.VerticallyMergedClasses;
+import com.android.tools.r8.utils.InternalOptions;
public class AppView<T extends AppInfo> {
private T appInfo;
private final DexItemFactory dexItemFactory;
private GraphLense graphLense;
+ private final InternalOptions options;
private VerticallyMergedClasses verticallyMergedClasses;
- public AppView(T appInfo, GraphLense graphLense) {
+ public AppView(T appInfo, GraphLense graphLense, InternalOptions options) {
this.appInfo = appInfo;
this.dexItemFactory = appInfo != null ? appInfo.dexItemFactory : null;
this.graphLense = graphLense;
+ this.options = options;
}
public T appInfo() {
@@ -46,6 +49,10 @@
this.graphLense = graphLense;
}
+ public InternalOptions options() {
+ return options;
+ }
+
// Get the result of vertical class merging. Returns null if vertical class merging has not been
// run.
public VerticallyMergedClasses verticallyMergedClasses() {
@@ -63,7 +70,7 @@
private class AppViewWithLiveness extends AppView<AppInfoWithLiveness> {
private AppViewWithLiveness() {
- super(null, null);
+ super(null, null, null);
}
@Override
@@ -94,6 +101,11 @@
}
@Override
+ public InternalOptions options() {
+ return AppView.this.options();
+ }
+
+ @Override
public AppView<AppInfoWithLiveness> withLiveness() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 6258e98..755402d 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -157,7 +157,7 @@
if (instruction instanceof CfFrame
&& (classFileVersion <= 49
|| (classFileVersion == 50
- && !options.proguardConfiguration.getKeepAttributes().stackMapTable))) {
+ && !options.getProguardConfiguration().getKeepAttributes().stackMapTable))) {
continue;
}
instruction.write(visitor, namingLens);
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index eda695d..1168c45 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -19,6 +19,7 @@
import java.util.Collections;
import java.util.List;
import java.util.TreeSet;
+import java.util.function.Function;
public class DexAnnotation extends DexItem {
public static final int VISIBILITY_BUILD = 0x00;
@@ -373,4 +374,15 @@
DexItemFactory factory) {
return annotation.annotation.type == factory.annotationSynthesizedClassMap;
}
+
+ public DexAnnotation rewrite(Function<DexEncodedAnnotation, DexEncodedAnnotation> rewriter) {
+ DexEncodedAnnotation rewritten = rewriter.apply(annotation);
+ if (rewritten == annotation) {
+ return this;
+ }
+ if (rewritten == null) {
+ return null;
+ }
+ return new DexAnnotation(visibility, rewritten);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
index d093a20..44b09b0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
@@ -5,8 +5,9 @@
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
-import java.util.ArrayList;
+import com.android.tools.r8.utils.ArrayUtils;
import java.util.Arrays;
+import java.util.function.Function;
import java.util.function.Predicate;
public class DexAnnotationSet extends CachedHashValueDexItem {
@@ -119,28 +120,20 @@
}
public DexAnnotationSet keepIf(Predicate<DexAnnotation> filter) {
- ArrayList<DexAnnotation> filtered = null;
- for (int i = 0; i < annotations.length; i++) {
- DexAnnotation annotation = annotations[i];
- if (filter.test(annotation)) {
- if (filtered != null) {
- filtered.add(annotation);
- }
- } else {
- if (filtered == null) {
- filtered = new ArrayList<>(annotations.length);
- for (int j = 0; j < i; j++) {
- filtered.add(annotations[j]);
- }
- }
- }
- }
- if (filtered == null) {
+ return rewrite(anno -> filter.test(anno) ? anno : null);
+ }
+
+ public DexAnnotationSet rewrite(Function<DexAnnotation, DexAnnotation> rewriter) {
+ if (isEmpty()) {
return this;
- } else if (filtered.isEmpty()) {
- return DexAnnotationSet.empty();
- } else {
- return new DexAnnotationSet(filtered.toArray(new DexAnnotation[filtered.size()]));
}
+ DexAnnotation[] rewritten = ArrayUtils.map(DexAnnotation[].class, annotations, rewriter);
+ if (rewritten == annotations) {
+ return this;
+ }
+ if (rewritten.length == 0) {
+ return DexAnnotationSet.empty();
+ }
+ return new DexAnnotationSet(rewritten);
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 5a4b204..c76865e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -321,6 +321,10 @@
return accessFlags.isInterface();
}
+ public boolean isEnum() {
+ return accessFlags.isEnum();
+ }
+
public abstract void addDependencies(MixedSectionCollection collector);
@Override
@@ -478,6 +482,12 @@
enclosingMethod = null;
}
+ public void removeEnclosingMethod(Predicate<EnclosingMethodAttribute> predicate) {
+ if (enclosingMethod != null && predicate.test(enclosingMethod)) {
+ enclosingMethod = null;
+ }
+ }
+
public void clearInnerClasses() {
innerClasses.clear();
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
index c47e72b..882ffb3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedAnnotation.java
@@ -5,7 +5,9 @@
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.utils.ArrayUtils;
import java.util.Arrays;
+import java.util.function.Function;
public class DexEncodedAnnotation extends DexItem {
@@ -72,4 +74,19 @@
int hashCode = hashCode();
return hashCode == UNSORTED ? 1 : hashCode;
}
+
+ public DexEncodedAnnotation rewrite(
+ Function<DexType, DexType> typeRewriter,
+ Function<DexAnnotationElement, DexAnnotationElement> elementRewriter) {
+ DexType rewrittenType = typeRewriter.apply(type);
+ DexAnnotationElement[] rewrittenElements =
+ ArrayUtils.map(DexAnnotationElement[].class, elements, elementRewriter);
+ if (rewrittenType == type && rewrittenElements == elements) {
+ return this;
+ }
+ if (rewrittenElements.length == 0) {
+ return null;
+ }
+ return new DexEncodedAnnotation(rewrittenType, rewrittenElements);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index e4f1c8c..012c4a4 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -7,6 +7,7 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.graph.LazyLoadedDexApplication.AllClasses;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.utils.ProgramClassCollection;
import com.android.tools.r8.utils.Timing;
@@ -21,9 +22,11 @@
public class DirectMappedDexApplication extends DexApplication {
+ private final AllClasses allClasses;
private final ImmutableMap<DexType, DexLibraryClass> libraryClasses;
private DirectMappedDexApplication(ClassNameMapper proguardMap,
+ AllClasses allClasses,
ProgramClassCollection programClasses,
ImmutableList<ProgramResourceProvider> programResourceProviders,
ImmutableMap<DexType, DexLibraryClass> libraryClasses,
@@ -32,6 +35,7 @@
Timing timing) {
super(proguardMap, programClasses, programResourceProviders, mainDexList, deadCode,
dexItemFactory, highestSortingString, timing);
+ this.allClasses = allClasses;
this.libraryClasses = libraryClasses;
}
@@ -92,17 +96,21 @@
public static class Builder extends DexApplication.Builder<Builder> {
+ private final AllClasses allClasses;
private final List<DexLibraryClass> libraryClasses = new ArrayList<>();
Builder(LazyLoadedDexApplication application) {
super(application);
// As a side-effect, this will force-load all classes.
- Map<DexType, DexClass> allClasses = application.getFullClassMap();
+ this.allClasses = application.loadAllClasses();
+ Map<DexType, DexClass> allClasses = this.allClasses.getClasses();
+ // TODO(120884788): This filter will only add library classes which are not program classes.
Iterables.filter(allClasses.values(), DexLibraryClass.class).forEach(libraryClasses::add);
}
private Builder(DirectMappedDexApplication application) {
super(application);
+ this.allClasses = application.allClasses;
this.libraryClasses.addAll(application.libraryClasses.values());
}
@@ -116,6 +124,7 @@
// Rebuild the map. This will fail if keys are not unique.
return new DirectMappedDexApplication(
proguardMap,
+ allClasses,
ProgramClassCollection.create(
programClasses, ProgramClassCollection::resolveClassConflictImpl),
ImmutableList.copyOf(programResourceProviders),
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 1c74f79..247a5fe 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -424,14 +424,30 @@
return this == getIdentityLense();
}
- public <T extends DexDefinition> boolean assertDefinitionNotModified(Iterable<T> items) {
- for (DexDefinition item : items) {
- DexReference dexReference = item.toReference();
- DexReference lookupedReference = lookupReference(dexReference);
+ public <T extends DexDefinition> boolean assertDefinitionsNotModified(Iterable<T> definitions) {
+ for (DexDefinition definition : definitions) {
+ DexReference reference = definition.toReference();
// We allow changes to bridge methods as these get retargeted even if they are kept.
boolean isBridge =
- item.isDexEncodedMethod() && item.asDexEncodedMethod().accessFlags.isBridge();
- assert isBridge || dexReference == lookupedReference;
+ definition.isDexEncodedMethod() && definition.asDexEncodedMethod().accessFlags.isBridge();
+ assert isBridge || lookupReference(reference) == reference;
+ }
+ return true;
+ }
+
+ public <T extends DexReference> boolean assertReferencesNotModified(Iterable<T> references) {
+ for (DexReference reference : references) {
+ if (reference.isDexField()) {
+ DexField field = reference.asDexField();
+ assert getRenamedFieldSignature(field) == field;
+ } else if (reference.isDexMethod()) {
+ DexMethod method = reference.asDexMethod();
+ assert getRenamedMethodSignature(method) == method;
+ } else {
+ assert reference.isDexType();
+ DexType type = reference.asDexType();
+ assert lookupType(type) == type;
+ }
}
return true;
}
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index a25bae0..55ece50 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -100,9 +100,12 @@
// If the source-file and source-debug-extension attributes are not kept we can skip all debug
// related attributes when parsing the class structure.
- ProguardKeepAttributes keep = application.options.proguardConfiguration.getKeepAttributes();
- if (!keep.sourceFile && !keep.sourceDebugExtension) {
- parsingOptions |= SKIP_DEBUG;
+ if (application.options.getProguardConfiguration() != null) {
+ ProguardKeepAttributes keep =
+ application.options.getProguardConfiguration().getKeepAttributes();
+ if (!keep.sourceFile && !keep.sourceDebugExtension) {
+ parsingOptions |= SKIP_DEBUG;
+ }
}
reader.accept(
new CreateDexClassVisitor(origin, classKind, reader.b, application, classConsumer),
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 2bc029c..04e6516 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -21,6 +21,7 @@
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OffOrAuto;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Iterator;
@@ -171,6 +172,20 @@
}
}
+ private boolean keepLocals(DexEncodedMethod encodedMethod, InternalOptions options) {
+ if (options.testing.noLocalsTableOnInput) {
+ return false;
+ }
+ if (options.debug) {
+ return true;
+ }
+ if (options.getProguardConfiguration() != null
+ && options.getProguardConfiguration().getKeepAttributes().localVariableTable) {
+ return true;
+ }
+ return encodedMethod.getOptimizationInfo().isReachabilitySensitive();
+ }
+
private IRCode internalBuild(
DexEncodedMethod context,
DexEncodedMethod encodedMethod,
@@ -179,9 +194,7 @@
InternalOptions options,
ValueNumberGenerator generator,
Position callerPosition) {
- if (!(encodedMethod.getOptimizationInfo().isReachabilitySensitive()
- || (options.debug
- && options.proguardConfiguration.getKeepAttributes().localVariableTable))) {
+ if (!keepLocals(encodedMethod, options)) {
node.localVariables.clear();
}
JarSourceCode source =
@@ -208,17 +221,29 @@
@Override
public void registerArgumentReferences(ArgumentUse registry) {
+ triggerDelayedParsingIfNeccessary();
node.instructions.accept(new JarArgumentUseVisitor(getOwner(), registry));
}
public ConstraintWithTarget computeInliningConstraint(
DexEncodedMethod encodedMethod,
- AppInfoWithLiveness appInfo,
+ AppView<? extends AppInfoWithLiveness> appView,
GraphLense graphLense,
DexType invocationContext) {
InliningConstraintVisitor visitor =
new InliningConstraintVisitor(
- application, appInfo, graphLense, encodedMethod, invocationContext);
+ application, appView.appInfo(), graphLense, encodedMethod, invocationContext);
+
+ if (appView.options().enableDesugaring
+ && appView.options().interfaceMethodDesugaring == OffOrAuto.Auto
+ && !appView.options().canUseDefaultAndStaticInterfaceMethods()) {
+ // TODO(b/120130831): Conservatively need to say "no" at this point if there are invocations
+ // to static interface methods. This should be fixed by making sure that the desugared
+ // versions of default and static interface methods are present in the application during
+ // IR processing.
+ visitor.disallowStaticInterfaceMethodCalls();
+ }
+
AbstractInsnNode insn = node.instructions.getFirst();
while (insn != null) {
insn.accept(visitor);
@@ -272,13 +297,16 @@
// to get locals information which we need to extend the live ranges of locals for their
// entire scope.
int parsingOptions = ClassReader.SKIP_FRAMES;
- ProguardKeepAttributes keep = application.options.proguardConfiguration.getKeepAttributes();
- if (!keep.localVariableTable
- && !keep.localVariableTypeTable
- && !keep.lineNumberTable
- && !reachabilitySensitive) {
- parsingOptions |= ClassReader.SKIP_DEBUG;
+ if (application.options.getProguardConfiguration() != null) {
+ ProguardKeepAttributes keep =
+ application.options.getProguardConfiguration().getKeepAttributes();
+ if (!keep.localVariableTable
+ && !keep.localVariableTypeTable
+ && !keep.lineNumberTable
+ && !reachabilitySensitive) {
+ parsingOptions |= ClassReader.SKIP_DEBUG;
+ }
}
SecondVisitor classVisitor = new SecondVisitor(createCodeLocator(context), useJsrInliner);
try {
diff --git a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
index b648e41..6dea825 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyLoadedDexApplication.java
@@ -55,31 +55,67 @@
return clazz;
}
- private Map<DexType, DexClass> forceLoadAllClasses() {
- Map<DexType, DexClass> loaded = new IdentityHashMap<>();
+ static class AllClasses {
+ private Map<DexType, DexClass> libraryClasses;
+ private Map<DexType, DexClass> classpathClasses;
+ private Map<DexType, DexClass> programClasses;
+ private Map<DexType, DexClass> classes;
- // Program classes are supposed to be loaded, but force-loading them is no-op.
- programClasses.forceLoad(type -> true);
- programClasses.getAllClasses().forEach(clazz -> loaded.put(clazz.type, clazz));
+ AllClasses(
+ LibraryClassCollection libraryClasses,
+ ClasspathClassCollection classpathClasses,
+ ProgramClassCollection programClasses) {
+ load(libraryClasses, classpathClasses, programClasses);
- if (classpathClasses != null) {
- classpathClasses.forceLoad(type -> !loaded.containsKey(type));
- classpathClasses.getAllClasses().forEach(clazz -> loaded.putIfAbsent(clazz.type, clazz));
+ // Collect loaded classes in the precedence order program classes, class path classes and
+ // library classes.
+ // TODO(b/120884788): Change this.
+ classes = new IdentityHashMap<>();
+ classes.putAll(this.programClasses);
+ if (classpathClasses != null) {
+ classpathClasses.getAllClasses().forEach(clazz -> classes.putIfAbsent(clazz.type, clazz));
+ }
+ if (libraryClasses != null) {
+ libraryClasses.getAllClasses().forEach(clazz -> classes.putIfAbsent(clazz.type, clazz));
+ }
}
- if (libraryClasses != null) {
- libraryClasses.forceLoad(type -> !loaded.containsKey(type));
- libraryClasses.getAllClasses().forEach(clazz -> loaded.putIfAbsent(clazz.type, clazz));
+ public Map<DexType, DexClass> getLibraryClasses() {
+ return libraryClasses;
}
- return loaded;
+ public Map<DexType, DexClass> getClasspathClasses() {
+ return classpathClasses;
+ }
+
+ public Map<DexType, DexClass> getClasses() {
+ return classes;
+ }
+
+ private void load(
+ LibraryClassCollection libraryClasses,
+ ClasspathClassCollection classpathClasses,
+ ProgramClassCollection programClasses) {
+ if (libraryClasses != null) {
+ libraryClasses.forceLoad(type -> true);
+ this.libraryClasses = libraryClasses.getAllClassesInMap();
+ }
+ if (classpathClasses != null) {
+ classpathClasses.forceLoad(type -> true);
+ this.classpathClasses = classpathClasses.getAllClassesInMap();
+ }
+ assert programClasses != null;
+ // Program classes are supposed to be loaded, but force-loading them is no-op.
+ programClasses.forceLoad(type -> true);
+ this.programClasses = programClasses.getAllClassesInMap();
+ }
}
/**
* Force load all classes and return type -> class map containing all the classes.
*/
- public Map<DexType, DexClass> getFullClassMap() {
- return forceLoadAllClasses();
+ public AllClasses loadAllClasses() {
+ return new AllClasses(libraryClasses, classpathClasses, programClasses);
}
public static class Builder extends DexApplication.Builder<Builder> {
diff --git a/src/main/java/com/android/tools/r8/graphinfo/AnnotationGraphNode.java b/src/main/java/com/android/tools/r8/graphinfo/AnnotationGraphNode.java
deleted file mode 100644
index 2c8482f..0000000
--- a/src/main/java/com/android/tools/r8/graphinfo/AnnotationGraphNode.java
+++ /dev/null
@@ -1,44 +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.graphinfo;
-
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.graph.DexItem;
-
-@Keep
-public final class AnnotationGraphNode extends GraphNode {
-
- private final DexItem annotatedItem;
-
- public AnnotationGraphNode(DexItem annotatedItem) {
- assert annotatedItem != null;
- this.annotatedItem = annotatedItem;
- }
-
- @Override
- public boolean equals(Object o) {
- return this == o
- || (o instanceof AnnotationGraphNode
- && ((AnnotationGraphNode) o).annotatedItem == annotatedItem);
- }
-
- @Override
- public int hashCode() {
- return annotatedItem.hashCode();
- }
-
- public String getDescriptor() {
- return annotatedItem.toSourceString();
- }
-
- /**
- * Get a unique identity string determining this annotated-item node.
- *
- * <p>This is the descriptor of the concrete node type.
- */
- @Override
- public String identity() {
- return getDescriptor();
- }
-}
diff --git a/src/main/java/com/android/tools/r8/graphinfo/ClassGraphNode.java b/src/main/java/com/android/tools/r8/graphinfo/ClassGraphNode.java
deleted file mode 100644
index 96bebdb..0000000
--- a/src/main/java/com/android/tools/r8/graphinfo/ClassGraphNode.java
+++ /dev/null
@@ -1,42 +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.graphinfo;
-
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.graph.DexType;
-
-@Keep
-public final class ClassGraphNode extends GraphNode {
-
- private final DexType clazz;
-
- public ClassGraphNode(DexType clazz) {
- assert clazz != null;
- this.clazz = clazz;
- }
-
- @Override
- public boolean equals(Object o) {
- return this == o || (o instanceof ClassGraphNode && ((ClassGraphNode) o).clazz == clazz);
- }
-
- @Override
- public int hashCode() {
- return clazz.hashCode();
- }
-
- public String getDescriptor() {
- return clazz.toDescriptorString();
- }
-
- /**
- * Get a unique identity string determining this clazz node.
- *
- * <p>This is just the class descriptor.
- */
- @Override
- public String identity() {
- return getDescriptor();
- }
-}
diff --git a/src/main/java/com/android/tools/r8/graphinfo/FieldGraphNode.java b/src/main/java/com/android/tools/r8/graphinfo/FieldGraphNode.java
deleted file mode 100644
index bb3b932..0000000
--- a/src/main/java/com/android/tools/r8/graphinfo/FieldGraphNode.java
+++ /dev/null
@@ -1,67 +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.graphinfo;
-
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.graph.DexField;
-
-@Keep
-public final class FieldGraphNode extends GraphNode {
-
- private final DexField field;
-
- public FieldGraphNode(DexField field) {
- assert field != null;
- this.field = field;
- }
-
- @Override
- public boolean equals(Object o) {
- return this == o || (o instanceof FieldGraphNode && ((FieldGraphNode) o).field == field);
- }
-
- @Override
- public int hashCode() {
- return field.hashCode();
- }
-
- /**
- * Get the class descriptor for the field holder as defined by the JVM specification.
- *
- * <p>For the field {@code x.y.Z a.b.C.foo}, this would be {@code La/b/C;}.
- */
- public String getHolderDescriptor() {
- return field.clazz.toDescriptorString();
- }
-
- /**
- * Get the field descriptor as defined by the JVM specification.
- *
- * <p>For the field {@code x.y.Z a.b.C.foo}, this would be {@code Lx/y/Z;}.
- */
- public String getFieldDescriptor() {
- return field.type.toDescriptorString();
- }
-
- /**
- * Get the (unqualified) field name.
- *
- * <p>For the field {@code x.y.Z a.b.C.foo} this would be {@code foo}.
- */
- public String getFieldName() {
- return field.name.toString();
- }
-
- /**
- * Get a unique identity string determining this field node.
- *
- * <p>The identity string follows the CF encoding of a field reference: {@code
- * <holder-descriptor>.<field-name>:<field-descriptor>}, e.g., for {@code x.y.Z a.b.C.foo} this
- * would be {@code La/b/C;foo:Lx/y/Z;}.
- */
- @Override
- public String identity() {
- return getHolderDescriptor() + getFieldName() + "\":" + getFieldDescriptor();
- }
-}
diff --git a/src/main/java/com/android/tools/r8/graphinfo/GraphNode.java b/src/main/java/com/android/tools/r8/graphinfo/GraphNode.java
deleted file mode 100644
index 4f6251b..0000000
--- a/src/main/java/com/android/tools/r8/graphinfo/GraphNode.java
+++ /dev/null
@@ -1,23 +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.graphinfo;
-
-import com.android.tools.r8.Keep;
-
-@Keep
-public abstract class GraphNode {
-
- public abstract String identity();
-
- @Override
- public abstract boolean equals(Object o);
-
- @Override
- public abstract int hashCode();
-
- @Override
- public String toString() {
- return identity();
- }
-}
diff --git a/src/main/java/com/android/tools/r8/graphinfo/MethodGraphNode.java b/src/main/java/com/android/tools/r8/graphinfo/MethodGraphNode.java
deleted file mode 100644
index 2745518..0000000
--- a/src/main/java/com/android/tools/r8/graphinfo/MethodGraphNode.java
+++ /dev/null
@@ -1,67 +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.graphinfo;
-
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.graph.DexMethod;
-
-@Keep
-public final class MethodGraphNode extends GraphNode {
-
- private final DexMethod method;
-
- public MethodGraphNode(DexMethod method) {
- assert method != null;
- this.method = method;
- }
-
- @Override
- public boolean equals(Object o) {
- return this == o || (o instanceof MethodGraphNode && ((MethodGraphNode) o).method == method);
- }
-
- @Override
- public int hashCode() {
- return method.hashCode();
- }
-
- /**
- * Get the class descriptor for the method holder as defined by the JVM specification.
- *
- * <p>For the method {@code void a.b.C.foo(x.y.Z arg)}, this would be {@code La/b/C;}.
- */
- public String getHolderDescriptor() {
- return method.holder.toDescriptorString();
- }
-
- /**
- * Get the method descriptor as defined by the JVM specification.
- *
- * <p>For the method {@code void a.b.C.foo(x.y.Z arg)}, this would be {@code (Lx/y/Z;)V}.
- */
- public String getMethodDescriptor() {
- return method.proto.toDescriptorString();
- }
-
- /**
- * Get the (unqualified) method name.
- *
- * <p>For the method {@code void a.b.C.foo(x.y.Z arg)} this would be {@code foo}.
- */
- public String getMethodName() {
- return method.name.toString();
- }
-
- /**
- * Get a unique identity string determining this method node.
- *
- * <p>The identity string follows the CF encoding of a method reference:
- * {@code <holder-descriptor><method-name><method-descriptor>}, e.g., for
- * {@code void a.b.C.foo(x.y.Z arg)} this will be {@code La/b/C;foo(Lx/y/Z;)V}.
- */
- @Override
- public String identity() {
- return getHolderDescriptor() + getMethodName() + getMethodDescriptor();
- }
-}
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 c6ab4cd..99833f4 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
@@ -40,6 +40,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.WeakHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -48,6 +49,12 @@
*/
public class BasicBlock {
+ public interface BasicBlockChangeListener {
+ void onSuccessorsMayChange(BasicBlock block);
+
+ void onPredecessorsMayChange(BasicBlock block);
+ }
+
private Int2ReferenceMap<DebugLocalInfo> localsAtEntry;
public boolean consistentBlockInstructions(boolean argumentsAllowed, boolean debug) {
@@ -130,6 +137,8 @@
private final List<BasicBlock> successors = new ArrayList<>();
private final List<BasicBlock> predecessors = new ArrayList<>();
+ private Set<BasicBlockChangeListener> onControlFlowEdgesMayChangeListeners = null;
+
// Catch handler information about which successors are catch handlers and what their guards are.
private CatchHandlers<Integer> catchHandlers = CatchHandlers.EMPTY_INDICES;
@@ -159,14 +168,30 @@
// Map of registers to current SSA value. Used during SSA numbering and cleared once filled.
private Map<Integer, Value> currentDefinitions = new HashMap<>();
+ public void addControlFlowEdgesMayChangeListener(BasicBlockChangeListener listener) {
+ if (onControlFlowEdgesMayChangeListeners == null) {
+ // WeakSet to allow the listeners to be garbage collected.
+ onControlFlowEdgesMayChangeListeners = Collections.newSetFromMap(new WeakHashMap<>());
+ }
+ onControlFlowEdgesMayChangeListeners.add(listener);
+ }
+
public List<BasicBlock> getSuccessors() {
return Collections.unmodifiableList(successors);
}
public List<BasicBlock> getMutableSuccessors() {
+ assert notifySuccessorsMayChangeListeners();
return successors;
}
+ private boolean notifySuccessorsMayChangeListeners() {
+ if (onControlFlowEdgesMayChangeListeners != null) {
+ onControlFlowEdgesMayChangeListeners.forEach(l -> l.onSuccessorsMayChange(this));
+ }
+ return true;
+ }
+
public List<BasicBlock> getNormalSuccessors() {
if (!hasCatchHandlers()) {
return successors;
@@ -186,9 +211,17 @@
}
public List<BasicBlock> getMutablePredecessors() {
+ assert notifyPredecessorsMayChangeListeners();
return predecessors;
}
+ private boolean notifyPredecessorsMayChangeListeners() {
+ if (onControlFlowEdgesMayChangeListeners != null) {
+ onControlFlowEdgesMayChangeListeners.forEach(l -> l.onPredecessorsMayChange(this));
+ }
+ return true;
+ }
+
public List<BasicBlock> getNormalPredecessors() {
ImmutableList.Builder<BasicBlock> normals = ImmutableList.builder();
for (BasicBlock predecessor : predecessors) {
@@ -208,7 +241,7 @@
public void removePredecessor(BasicBlock block) {
int index = predecessors.indexOf(block);
assert index >= 0 : "removePredecessor did not find the predecessor to remove";
- predecessors.remove(index);
+ getMutablePredecessors().remove(index);
if (phis != null) {
for (Phi phi : getPhis()) {
phi.removeOperand(index);
@@ -249,6 +282,7 @@
}
catchHandlers = new CatchHandlers<>(catchHandlers.getGuards(), targets);
}
+ List<BasicBlock> successors = getMutableSuccessors();
BasicBlock tmp = successors.get(index1);
successors.set(index1, successors.get(index2));
successors.set(index2, tmp);
@@ -335,14 +369,14 @@
}
// Remove the replaced successor.
- boolean removed = successors.remove(block);
+ boolean removed = getMutableSuccessors().remove(block);
assert removed;
} else {
// If the new block is not a successor we don't have to rewrite indices or instructions
// and we can just replace the old successor with the new one.
for (int i = 0; i < successors.size(); i++) {
if (successors.get(i) == block) {
- successors.set(i, newBlock);
+ getMutableSuccessors().set(i, newBlock);
return;
}
}
@@ -366,7 +400,8 @@
public void replacePredecessor(BasicBlock block, BasicBlock newBlock) {
for (int i = 0; i < predecessors.size(); i++) {
if (predecessors.get(i) == block) {
- predecessors.set(i, newBlock);
+ assert notifyPredecessorsMayChangeListeners();
+ getMutablePredecessors().set(i, newBlock);
return;
}
}
@@ -378,6 +413,7 @@
return;
}
assert ListUtils.verifyListIsOrdered(successorsToRemove);
+ List<BasicBlock> successors = getMutableSuccessors();
List<BasicBlock> copy = new ArrayList<>(successors);
successors.clear();
int current = 0;
@@ -426,6 +462,7 @@
if (predecessorsToRemove.isEmpty()) {
return;
}
+ List<BasicBlock> predecessors = getMutablePredecessors();
List<BasicBlock> copy = new ArrayList<>(predecessors);
predecessors.clear();
int current = 0;
@@ -592,24 +629,16 @@
public void link(BasicBlock successor) {
assert !successors.contains(successor);
assert !successor.predecessors.contains(this);
- successors.add(successor);
- successor.predecessors.add(this);
- }
-
- private static boolean allPredecessorsDominated(BasicBlock block, DominatorTree dominator) {
- for (BasicBlock pred : block.predecessors) {
- if (!dominator.dominatedBy(pred, block)) {
- return false;
- }
- }
- return true;
+ getMutableSuccessors().add(successor);
+ successor.getMutablePredecessors().add(this);
}
private static boolean blocksClean(List<BasicBlock> blocks) {
- blocks.forEach((b) -> {
- assert b.predecessors.size() == 0;
- assert b.successors.size() == 0;
- });
+ blocks.forEach(
+ b -> {
+ assert b.predecessors.size() == 0;
+ assert b.successors.size() == 0;
+ });
return true;
}
@@ -622,8 +651,8 @@
assert predecessors.size() == 1;
assert predecessors.get(0).successors.size() == 1;
BasicBlock unlinkedBlock = predecessors.get(0);
- unlinkedBlock.successors.clear();
- predecessors.clear();
+ unlinkedBlock.getMutableSuccessors().clear();
+ getMutablePredecessors().clear();
return unlinkedBlock;
}
@@ -631,8 +660,9 @@
public void unlinkSinglePredecessorSiblingsAllowed() {
assert predecessors.size() == 1; // There are no critical edges.
assert predecessors.get(0).successors.contains(this);
- predecessors.get(0).successors.remove(this);
- predecessors.clear();
+ List<BasicBlock> predecessors = getMutablePredecessors();
+ predecessors.get(0).getMutableSuccessors().remove(this);
+ getMutablePredecessors().clear();
}
/**
@@ -645,8 +675,8 @@
assert successors.size() == 1;
assert successors.get(0).predecessors.size() == 1;
BasicBlock unlinkedBlock = successors.get(0);
- unlinkedBlock.predecessors.clear();
- successors.clear();
+ unlinkedBlock.getMutablePredecessors().clear();
+ getMutableSuccessors().clear();
return unlinkedBlock;
}
@@ -659,7 +689,7 @@
public void unlinkCatchHandler() {
assert predecessors.size() == 1;
predecessors.get(0).removeSuccessor(this);
- predecessors.clear();
+ getMutablePredecessors().clear();
}
/**
@@ -691,7 +721,7 @@
catchHandlers = catchHandlers.removeGuard(guard);
if (getCatchHandlers().getAllTargets().stream()
.noneMatch(target -> target == successors.get(successorIndex))) {
- successors.remove(successorIndex);
+ getMutableSuccessors().remove(successorIndex);
}
assert consistentCatchHandlers();
}
@@ -712,9 +742,9 @@
public void detachAllSuccessors() {
for (BasicBlock successor : successors) {
- successor.predecessors.remove(this);
+ successor.getMutablePredecessors().remove(this);
}
- successors.clear();
+ getMutableSuccessors().clear();
}
public List<BasicBlock> unlink(BasicBlock successor, DominatorTree dominator) {
@@ -735,11 +765,11 @@
for (BasicBlock block : successors) {
block.removePredecessor(this);
}
- successors.clear();
+ getMutableSuccessors().clear();
for (BasicBlock block : predecessors) {
block.removeSuccessor(this);
}
- predecessors.clear();
+ getMutablePredecessors().clear();
for (Phi phi : getPhis()) {
affectValuesBuilder.addAll(phi.affectedValues());
for (Value operand : phi.getOperands()) {
@@ -778,7 +808,7 @@
public void addCatchHandler(BasicBlock rethrowBlock, DexType guard) {
assert !hasCatchHandlers();
- successors.add(0, rethrowBlock);
+ getMutableSuccessors().add(0, rethrowBlock);
rethrowBlock.getMutablePredecessors().add(this);
catchHandlers = new CatchHandlers<>(ImmutableList.of(guard), ImmutableList.of(0));
}
@@ -1449,11 +1479,11 @@
newBlock.setNumber(blockNumber);
// Copy all successors including catch handlers to the new block, and update predecessors.
- newBlock.successors.addAll(successors);
+ newBlock.getMutableSuccessors().addAll(successors);
for (BasicBlock successor : newBlock.getSuccessors()) {
successor.replacePredecessor(this, newBlock);
}
- successors.clear();
+ getMutableSuccessors().clear();
newBlock.catchHandlers = catchHandlers;
catchHandlers = CatchHandlers.EMPTY_INDICES;
@@ -1478,7 +1508,7 @@
public void moveCatchHandlers(BasicBlock fromBlock) {
List<BasicBlock> catchSuccessors = appendCatchHandlers(fromBlock);
for (BasicBlock successor : catchSuccessors) {
- fromBlock.successors.remove(successor);
+ fromBlock.getMutableSuccessors().remove(successor);
successor.removePredecessor(fromBlock);
}
fromBlock.catchHandlers = CatchHandlers.EMPTY_INDICES;
@@ -1651,6 +1681,7 @@
}
// Create the new successors list and link things up.
+ List<BasicBlock> successors = getMutableSuccessors();
List<BasicBlock> formerSuccessors = new ArrayList<>(successors);
successors.clear();
List<BasicBlock> sharedCatchSuccessors = new ArrayList<>();
diff --git a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
index 51fd38e..b8192be 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
@@ -6,11 +6,14 @@
import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS;
+import com.android.tools.r8.ir.code.BasicBlock.BasicBlockChangeListener;
import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
+import java.util.List;
-public class DominatorTree {
+public class DominatorTree implements BasicBlockChangeListener {
public enum Assumption {
NO_UNREACHABLE_BLOCKS,
@@ -23,6 +26,8 @@
private final int unreachableStartIndex;
+ private boolean obsolete = false;
+
public DominatorTree(IRCode code) {
this(code, Assumption.NO_UNREACHABLE_BLOCKS);
}
@@ -68,12 +73,17 @@
}
numberBlocks();
build();
+
+ // This is intentionally implemented via an `assert` so that we do not attach listeners to all
+ // basic blocks when running without assertions.
+ assert recordChangesToControlFlowEdges(code.blocks);
}
/**
* Get the immediate dominator block for a block.
*/
public BasicBlock immediateDominator(BasicBlock block) {
+ assert !obsolete;
return doms[block.getNumber()];
}
@@ -85,6 +95,7 @@
* @return wether {@code subject} is dominated by {@code dominator}
*/
public boolean dominatedBy(BasicBlock subject, BasicBlock dominator) {
+ assert !obsolete;
if (subject == dominator) {
return true;
}
@@ -99,6 +110,7 @@
* @return wether {@code subject} is strictly dominated by {@code dominator}
*/
public boolean strictlyDominatedBy(BasicBlock subject, BasicBlock dominator) {
+ assert !obsolete;
if (subject.getNumber() == 0 || subject == normalExitBlock) {
return false;
}
@@ -121,6 +133,7 @@
* @return the closest dominator for the collection of blocks
*/
public BasicBlock closestDominator(Collection<BasicBlock> blocks) {
+ assert !obsolete;
if (blocks.size() == 0) {
return null;
}
@@ -132,31 +145,17 @@
return dominator;
}
- /** Returns an iterator over all blocks dominated by dominator, including dominator itself. */
- public Iterable<BasicBlock> dominatedBlocks(BasicBlock dominator) {
- return () ->
- new Iterator<BasicBlock>() {
- private int current = dominator.getNumber();
-
- @Override
- public boolean hasNext() {
- boolean found = false;
- while (current < unreachableStartIndex
- && !(found = dominatedBy(sorted[current], dominator))) {
- current++;
- }
- return found && current < unreachableStartIndex;
- }
-
- @Override
- public BasicBlock next() {
- if (!hasNext()) {
- return null;
- } else {
- return sorted[current++];
- }
- }
- };
+ /** Returns the blocks dominated by dominator, including dominator itself. */
+ public List<BasicBlock> dominatedBlocks(BasicBlock dominator) {
+ assert !obsolete;
+ List<BasicBlock> dominatedBlocks = new ArrayList<>();
+ for (int i = dominator.getNumber(); i < unreachableStartIndex; ++i) {
+ BasicBlock block = sorted[i];
+ if (dominatedBy(block, dominator)) {
+ dominatedBlocks.add(block);
+ }
+ }
+ return dominatedBlocks;
}
/**
@@ -166,6 +165,7 @@
* iteration starts by returning <code>dominated</code>.
*/
public Iterable<BasicBlock> dominatorBlocks(BasicBlock dominated) {
+ assert !obsolete;
return () -> new Iterator<BasicBlock>() {
private BasicBlock current = dominated;
@@ -193,6 +193,7 @@
}
public Iterable<BasicBlock> normalExitDominatorBlocks() {
+ assert !obsolete;
return dominatorBlocks(normalExitBlock);
}
@@ -279,4 +280,21 @@
}
return builder.toString();
}
+
+ private boolean recordChangesToControlFlowEdges(List<BasicBlock> blocks) {
+ for (BasicBlock block : blocks) {
+ block.addControlFlowEdgesMayChangeListener(this);
+ }
+ return true;
+ }
+
+ @Override
+ public void onSuccessorsMayChange(BasicBlock block) {
+ obsolete = true;
+ }
+
+ @Override
+ public void onPredecessorsMayChange(BasicBlock block) {
+ obsolete = true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java b/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
index fd9afa8..4e1e0ed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
@@ -37,6 +37,10 @@
this.to = to;
}
+ public boolean isLongToIntConversion() {
+ return from == NumericType.LONG && to == NumericType.INT;
+ }
+
@Override
public void buildDex(DexBuilder builder) {
com.android.tools.r8.code.Instruction instruction;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 13fb6cb..4b2fabb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -3,8 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.conversion;
-import com.android.tools.r8.code.Const4;
-import com.android.tools.r8.code.ConstWide16;
import com.android.tools.r8.code.FillArrayData;
import com.android.tools.r8.code.FillArrayDataPayload;
import com.android.tools.r8.code.Format31t;
@@ -35,7 +33,6 @@
import com.android.tools.r8.code.MoveWideFrom16;
import com.android.tools.r8.code.Nop;
import com.android.tools.r8.code.ReturnVoid;
-import com.android.tools.r8.code.ReturnWide;
import com.android.tools.r8.code.Throw;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
@@ -220,22 +217,14 @@
if (options.canHaveTracingPastInstructionsStreamBug()
&& dexInstructions.get(dexInstructions.size() - 1) instanceof Throw
&& hasBackwardsBranch) {
- List<Instruction> instructions = new ArrayList<>();
- DexType returnType = ir.method.method.proto.returnType;
- if (returnType.isVoidType()) {
- instructions.add(new ReturnVoid());
- } else if (returnType.isDoubleType() || returnType.isLongType()) {
- instructions.add(new ConstWide16(0, 0));
- instructions.add(new ReturnWide(0));
- } else {
- instructions.add(new Const4(0, 0));
- instructions.add(new com.android.tools.r8.code.Return(0));
- }
- for (Instruction instruction : instructions) {
- instruction.setOffset(offset);
- offset += instruction.getSize();
- dexInstructions.add(instruction);
- }
+ // Generating a throw with the right type makes some Art constant propagation
+ // implementations crash. Therefore, since this is dead code anyway, we do not
+ // generate a return of the right type. Instead we just generate an unreachable
+ // return-void. See b/121355317.
+ Instruction returnVoid = new ReturnVoid();
+ returnVoid.setOffset(offset);
+ offset += returnVoid.getSize();
+ dexInstructions.add(returnVoid);
}
// Compute switch payloads.
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 c77be90..0434f66 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
@@ -68,6 +68,7 @@
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.IdentifierNameStringMarker;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.MainDexClasses;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -109,6 +110,7 @@
public final AppInfo appInfo;
public final AppView<? extends AppInfoWithSubtyping> appView;
+ public final Set<DexType> mainDexClasses;
public final RootSet rootSet;
private final Timing timing;
@@ -157,6 +159,7 @@
Timing timing,
CfgPrinter printer,
AppView<? extends AppInfoWithSubtyping> appView,
+ MainDexClasses mainDexClasses,
RootSet rootSet) {
assert appInfo != null;
assert options != null;
@@ -170,6 +173,7 @@
this.rootSet = rootSet;
this.options = options;
this.printer = printer;
+ this.mainDexClasses = mainDexClasses.getClasses();
this.codeRewriter = new CodeRewriter(this, libraryMethodsReturningReceiver(), options);
this.stringConcatRewriter = new StringConcatRewriter(appInfo);
this.lambdaRewriter = options.enableDesugaring ? new LambdaRewriter(this) : null;
@@ -194,7 +198,7 @@
assert rootSet != null;
this.nonNullTracker = new NonNullTracker(
appInfoWithLiveness, libraryMethodsReturningNonNull(appInfo.dexItemFactory));
- this.inliner = new Inliner(appViewWithLiveness, this, options);
+ this.inliner = new Inliner(appViewWithLiveness, this, options, mainDexClasses);
this.outliner = new Outliner(appInfoWithLiveness, options, this);
this.memberValuePropagation =
options.enableValuePropagation ? new MemberValuePropagation(appInfoWithLiveness) : null;
@@ -253,14 +257,14 @@
* Create an IR converter for processing methods with full program optimization disabled.
*/
public IRConverter(AppInfo appInfo, InternalOptions options) {
- this(appInfo, options, null, null, null, null);
+ this(appInfo, options, null, null, null, MainDexClasses.NONE, null);
}
/**
* Create an IR converter for processing methods with full program optimization disabled.
*/
public IRConverter(AppInfo appInfo, InternalOptions options, Timing timing, CfgPrinter printer) {
- this(appInfo, options, timing, printer, null, null);
+ this(appInfo, options, timing, printer, null, MainDexClasses.NONE, null);
}
/**
@@ -271,8 +275,9 @@
InternalOptions options,
Timing timing,
CfgPrinter printer,
+ MainDexClasses mainDexClasses,
RootSet rootSet) {
- this(appView.appInfo(), options, timing, printer, appView, rootSet);
+ this(appView.appInfo(), options, timing, printer, appView, mainDexClasses, rootSet);
}
private boolean enableInterfaceMethodDesugaring() {
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 b78f6df..bf36c20 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
@@ -517,6 +517,11 @@
for (int i = 0; i < directMethods.length; i++) {
DexEncodedMethod encodedMethod = directMethods[i];
if (implMethod.match(encodedMethod)) {
+ // Check that this method is synthetic, since this implies that the method cannot be kept
+ // by an explicit -keep rule. This is necessary because we could otherwise be changing
+ // the signature of a kept method here, which is not allowed (see b/120971047).
+ assert encodedMethod.accessFlags.isSynthetic();
+
// We need to create a new static method with the same code to be able to safely
// relax its accessibility without making it virtual.
MethodAccessFlags newAccessFlags = encodedMethod.accessFlags.copy();
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 d10951a..2244653 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
@@ -2118,7 +2118,7 @@
} else if (inType.lessThanOrEqual(instanceOfType, appInfo) && !inType.isNullable()) {
result = InstanceOfResult.TRUE;
} else if (!inValue.isPhi()
- && inValue.definition.isNewInstance()
+ && inValue.definition.isCreatingInstanceOrArray()
&& instanceOfType.strictlyLessThan(inType, appInfo)) {
result = InstanceOfResult.FALSE;
} else if (appInfo.hasLiveness()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index dd7f85c..173de18 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.MainDexDirectReferenceTracer;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IteratorUtils;
import java.util.BitSet;
@@ -29,7 +30,7 @@
import java.util.ListIterator;
import java.util.function.Predicate;
-final class DefaultInliningOracle implements InliningOracle, InliningStrategy {
+public final class DefaultInliningOracle implements InliningOracle, InliningStrategy {
private final AppView<? extends AppInfoWithLiveness> appView;
private final Inliner inliner;
@@ -187,6 +188,7 @@
}
DexClass holder = inliner.appView.appInfo().definitionFor(candidate.method.getHolder());
+
if (holder.isInterface()) {
// Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception at
// runtime.
@@ -233,6 +235,21 @@
return false;
}
}
+
+ if (!inliner.mainDexClasses.isEmpty()) {
+ // Don't inline code with references beyond root main dex classes into a root main dex class.
+ // If we do this it can increase the size of the main dex dependent classes.
+ if (inliner.mainDexClasses.getRoots().contains(method.method.holder)
+ && MainDexDirectReferenceTracer.hasReferencesOutsideFromCode(
+ appView.appInfo(), candidate, inliner.mainDexClasses.getRoots())) {
+ if (info != null) {
+ info.exclude(invoke, "target has references beyond main dex");
+ }
+ return false;
+ }
+ // Allow inlining into the classes in the main dex dependent set without restrictions.
+ }
+
return true;
}
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 3cd19dc..2b0d831 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
@@ -30,6 +30,7 @@
import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.MainDexClasses;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Sets;
import java.util.ArrayList;
@@ -46,6 +47,7 @@
protected final AppView<? extends AppInfoWithLiveness> appView;
private final IRConverter converter;
final InternalOptions options;
+ final MainDexClasses mainDexClasses;
// State for inlining methods which are known to be called twice.
private boolean applyDoubleInlining = false;
@@ -58,10 +60,12 @@
public Inliner(
AppView<? extends AppInfoWithLiveness> appView,
IRConverter converter,
- InternalOptions options) {
+ InternalOptions options,
+ MainDexClasses mainDexClasses) {
this.appView = appView;
this.converter = converter;
this.options = options;
+ this.mainDexClasses = mainDexClasses;
fillInBlackList(appView.appInfo());
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 4e3f9be..3292704 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -24,6 +24,8 @@
private AppInfoWithLiveness appInfo;
+ private boolean allowStaticInterfaceMethodCalls = true;
+
// Currently used only by the vertical class merger (in all other cases this is the identity).
//
// When merging a type A into its subtype B we need to inline A.<init>() into B.<init>().
@@ -46,6 +48,10 @@
this.graphLense = graphLense;
}
+ public void disallowStaticInterfaceMethodCalls() {
+ allowStaticInterfaceMethodCalls = false;
+ }
+
public ConstraintWithTarget forAlwaysMaterializingUser() {
return ConstraintWithTarget.ALWAYS;
}
@@ -280,6 +286,11 @@
DexType methodHolder = graphLense.lookupType(target.method.holder);
DexClass methodClass = appInfo.definitionFor(methodHolder);
if (methodClass != null) {
+ if (!allowStaticInterfaceMethodCalls && methodClass.isInterface() && target.hasCode()) {
+ // See b/120121170.
+ return ConstraintWithTarget.NEVER;
+ }
+
ConstraintWithTarget methodConstraintWithTarget =
ConstraintWithTarget.deriveConstraint(
invocationContext, methodHolder, target.accessFlags, appInfo);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
index f906137..6957cb1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
@@ -19,7 +19,7 @@
@Override
LambdaGroupId validateAndCreate(Kotlin kotlin, DexClass lambda, InternalOptions options)
throws LambdaStructureError {
- boolean accessRelaxed = options.proguardConfiguration.isAccessModificationAllowed();
+ boolean accessRelaxed = options.getProguardConfiguration().isAccessModificationAllowed();
assert lambda.hasKotlinInfo() && lambda.getKotlinInfo().isSyntheticClass();
assert lambda.getKotlinInfo().asSyntheticClass().isJavaStyleLambda();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
index 36f9ecd..3d60e0f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
@@ -19,7 +19,7 @@
@Override
LambdaGroupId validateAndCreate(Kotlin kotlin, DexClass lambda, InternalOptions options)
throws LambdaStructureError {
- boolean accessRelaxed = options.proguardConfiguration.isAccessModificationAllowed();
+ boolean accessRelaxed = options.getProguardConfiguration().isAccessModificationAllowed();
assert lambda.hasKotlinInfo() && lambda.getKotlinInfo().isSyntheticClass();
assert lambda.getKotlinInfo().asSyntheticClass().isKotlinStyleLambda();
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 05c8e87..049b2e7 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
@@ -1442,7 +1442,7 @@
}
private boolean needsSingleResultOverlappingLongOperandsWorkaround(LiveIntervals intervals) {
- if (!options.canHaveCmpLongBug()) {
+ if (!options.canHaveCmpLongBug() && !options.canHaveLongToIntBug()) {
return false;
}
if (intervals.requiredRegisters() == 2) {
@@ -1459,7 +1459,11 @@
return false;
}
Instruction definition = intervals.getValue().definition;
- return definition.isCmp() && definition.asCmp().inValues().get(0).outType().isWide();
+ if (definition.isCmp()) {
+ return definition.inValues().get(0).outType().isWide();
+ }
+ return definition.isNumberConversion()
+ && definition.asNumberConversion().isLongToIntConversion();
}
private boolean singleOverlappingLong(int register1, int register2) {
@@ -1470,15 +1474,23 @@
// allocating for the result?
private boolean isSingleResultOverlappingLongOperands(LiveIntervals intervals, int register) {
assert needsSingleResultOverlappingLongOperandsWorkaround(intervals);
- Value left = intervals.getValue().definition.asCmp().leftValue();
- Value right = intervals.getValue().definition.asCmp().rightValue();
- int leftReg =
- left.getLiveIntervals().getSplitCovering(intervals.getStart()).getRegister();
- int rightReg =
- right.getLiveIntervals().getSplitCovering(intervals.getStart()).getRegister();
- assert leftReg != NO_REGISTER;
- assert rightReg != NO_REGISTER;
- return singleOverlappingLong(register, leftReg) || singleOverlappingLong(register, rightReg);
+ if (intervals.getValue().definition.isCmp()) {
+ Value left = intervals.getValue().definition.asCmp().leftValue();
+ Value right = intervals.getValue().definition.asCmp().rightValue();
+ int leftReg =
+ left.getLiveIntervals().getSplitCovering(intervals.getStart()).getRegister();
+ int rightReg =
+ right.getLiveIntervals().getSplitCovering(intervals.getStart()).getRegister();
+ assert leftReg != NO_REGISTER;
+ assert rightReg != NO_REGISTER;
+ return singleOverlappingLong(register, leftReg) || singleOverlappingLong(register, rightReg);
+ } else {
+ assert intervals.getValue().definition.isNumberConversion();
+ Value inputValue = intervals.getValue().definition.asNumberConversion().inValues().get(0);
+ int inputReg
+ = inputValue.getLiveIntervals().getSplitCovering(intervals.getStart()).getRegister();
+ return register == inputReg;
+ }
}
// The dalvik jit had a bug where the long operations add, sub, or, xor and and would write
diff --git a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
index c707968..18998c8 100644
--- a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
@@ -14,6 +14,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.graph.GraphLense.GraphLenseLookupResult;
import com.android.tools.r8.graph.JarApplicationReader;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.conversion.JarSourceCode;
@@ -62,6 +63,10 @@
? inliningConstraints.forMonitor() : ConstraintWithTarget.ALWAYS;
}
+ public void disallowStaticInterfaceMethodCalls() {
+ inliningConstraints.disallowStaticInterfaceMethodCalls();
+ }
+
public ConstraintWithTarget getConstraint() {
return constraint;
}
@@ -160,12 +165,15 @@
}
break;
- case Opcodes.INVOKESTATIC:
- type = Invoke.Type.STATIC;
- assert noNeedToUseGraphLense(target, type);
+ case Opcodes.INVOKESTATIC: {
+ // Static invokes may have changed as a result of horizontal class merging.
+ GraphLenseLookupResult lookup = graphLense.lookupMethod(target, null, Invoke.Type.STATIC);
+ target = lookup.getMethod();
+ type = lookup.getType();
break;
+ }
- case Opcodes.INVOKEVIRTUAL:
+ case Opcodes.INVOKEVIRTUAL: {
type = Invoke.Type.VIRTUAL;
// Instructions that target a private method in the same class translates to invoke-direct.
if (target.holder == method.method.holder) {
@@ -174,8 +182,13 @@
type = Invoke.Type.DIRECT;
}
}
- assert noNeedToUseGraphLense(target, type);
+
+ // Virtual invokes may have changed to interface invokes as a result of member rebinding.
+ GraphLenseLookupResult lookup = graphLense.lookupMethod(target, null, type);
+ target = lookup.getMethod();
+ type = lookup.getType();
break;
+ }
default:
throw new Unreachable("Unexpected opcode " + opcode);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index d15ed5a..4f79660 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -6,6 +6,7 @@
import static kotlinx.metadata.Flag.Property.IS_VAR;
+import com.android.tools.r8.graph.DexClass;
import kotlinx.metadata.KmClassVisitor;
import kotlinx.metadata.KmConstructorVisitor;
import kotlinx.metadata.KmFunctionVisitor;
@@ -14,14 +15,15 @@
public class KotlinClass extends KotlinInfo<KotlinClassMetadata.Class> {
- static KotlinClass fromKotlinClassMetadata(KotlinClassMetadata kotlinClassMetadata) {
+ static KotlinClass fromKotlinClassMetadata(
+ KotlinClassMetadata kotlinClassMetadata, DexClass clazz) {
assert kotlinClassMetadata instanceof KotlinClassMetadata.Class;
KotlinClassMetadata.Class kClass = (KotlinClassMetadata.Class) kotlinClassMetadata;
- return new KotlinClass(kClass);
+ return new KotlinClass(kClass, clazz);
}
- private KotlinClass(KotlinClassMetadata.Class metadata) {
- super(metadata);
+ private KotlinClass(KotlinClassMetadata.Class metadata, DexClass clazz) {
+ super(metadata, clazz);
}
@Override
@@ -38,7 +40,7 @@
@Override
public KmConstructorVisitor visitConstructor(int ctorFlags) {
- return new NonNullParameterHintCollector.ConstructorVisitor(nonNullparamHints);
+ return new NonNullParameterHintCollector.ConstructorVisitor(nonNullparamHints, clazz);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index 6518bb8..6004201 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -77,7 +77,7 @@
KotlinClassMetadata kMetadata = KotlinClassMetadata.read(header);
if (kMetadata instanceof KotlinClassMetadata.Class) {
- return KotlinClass.fromKotlinClassMetadata(kMetadata);
+ return KotlinClass.fromKotlinClassMetadata(kMetadata, clazz);
} else if (kMetadata instanceof KotlinClassMetadata.FileFacade) {
return KotlinFile.fromKotlinClassMetadata(kMetadata);
} else if (kMetadata instanceof KotlinClassMetadata.MultiFileClassFacade) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
index 7cc8aa1..ef90173 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.kotlin;
+import com.android.tools.r8.graph.DexClass;
import com.google.common.collect.HashBasedTable;
import java.util.BitSet;
import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -11,12 +12,18 @@
// Provides access to kotlin information.
public abstract class KotlinInfo<MetadataKind extends KotlinClassMetadata> {
MetadataKind metadata;
+ DexClass clazz;
final HashBasedTable<String, String, BitSet> nonNullparamHints = HashBasedTable.create();
KotlinInfo() {
}
KotlinInfo(MetadataKind metadata) {
+ this(metadata, null);
+ }
+
+ KotlinInfo(MetadataKind metadata, DexClass clazz) {
+ this.clazz = clazz;
processMetadata(metadata);
this.metadata = metadata;
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/NonNullParameterHintCollector.java b/src/main/java/com/android/tools/r8/kotlin/NonNullParameterHintCollector.java
index 20621f9..079980f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/NonNullParameterHintCollector.java
+++ b/src/main/java/com/android/tools/r8/kotlin/NonNullParameterHintCollector.java
@@ -6,6 +6,7 @@
import static kotlinx.metadata.Flag.Type.IS_NULLABLE;
+import com.android.tools.r8.graph.DexClass;
import com.google.common.collect.HashBasedTable;
import java.util.BitSet;
import kotlinx.metadata.KmConstructorExtensionVisitor;
@@ -94,8 +95,18 @@
private final String name = "<init>";
private String descriptor = "";
- ConstructorVisitor(HashBasedTable<String, String, BitSet> paramHints) {
+ ConstructorVisitor(HashBasedTable<String, String, BitSet> paramHints, DexClass clazz) {
this.paramHints = paramHints;
+ // Enum constructor has two synthetic arguments to java.lang.Enum's sole constructor:
+ // https://docs.oracle.com/javase/8/docs/api/java/lang/Enum.html#Enum-java.lang.String-int-
+ // whereas Kotlin @Metadata is still based on constructor signature, not descriptor.
+ if (clazz != null && clazz.isEnum()) {
+ // name - The name of this enum constant, which is the identifier used to declare it.
+ paramIndex++;
+ // ordinal - The ordinal of this enumeration constant (its position in the enum declaration,
+ // where the initial constant is assigned an ordinal of zero).
+ paramIndex++;
+ }
}
@Override
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index ca54e6c..1f0e3de 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -80,11 +80,12 @@
InternalOptions options) {
this.appInfo = appInfo;
this.reporter = options.reporter;
- this.packageObfuscationMode = options.proguardConfiguration.getPackageObfuscationMode();
- this.isAccessModificationAllowed = options.proguardConfiguration.isAccessModificationAllowed();
- this.packageDictionary = options.proguardConfiguration.getPackageObfuscationDictionary();
- this.classDictionary = options.proguardConfiguration.getClassObfuscationDictionary();
- this.keepInnerClassStructure = options.proguardConfiguration.getKeepAttributes().signature;
+ this.packageObfuscationMode = options.getProguardConfiguration().getPackageObfuscationMode();
+ this.isAccessModificationAllowed =
+ options.getProguardConfiguration().isAccessModificationAllowed();
+ this.packageDictionary = options.getProguardConfiguration().getPackageObfuscationDictionary();
+ this.classDictionary = options.getProguardConfiguration().getClassObfuscationDictionary();
+ this.keepInnerClassStructure = options.getProguardConfiguration().getKeepAttributes().signature;
this.noObfuscationTypes =
DexReference.filterDexType(
DexDefinition.mapToReference(rootSet.noObfuscation.stream()))
@@ -96,7 +97,7 @@
// Initialize top-level naming state.
topLevelState = new Namespace(
- getPackageBinaryNameFromJavaType(options.proguardConfiguration.getPackagePrefix()));
+ getPackageBinaryNameFromJavaType(options.getProguardConfiguration().getPackagePrefix()));
states.computeIfAbsent("", k -> topLevelState);
}
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index 481f6c4..95fcfd2 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -304,7 +304,7 @@
return;
}
// Undetermined identifiers matter only if minification is enabled.
- if (!options.proguardConfiguration.isObfuscating()) {
+ if (!options.getProguardConfiguration().isObfuscating()) {
return;
}
Origin origin = appInfo.originFor(originHolder);
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
index 43eab1d..312991e 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
@@ -31,10 +31,10 @@
this.appInfo = appInfo;
this.rootSet = rootSet;
this.options = options;
- this.dictionary = options.proguardConfiguration.getObfuscationDictionary();
- this.useUniqueMemberNames = options.proguardConfiguration.isUseUniqueClassMemberNames();
+ this.dictionary = options.getProguardConfiguration().getObfuscationDictionary();
+ this.useUniqueMemberNames = options.getProguardConfiguration().isUseUniqueClassMemberNames();
this.overloadAggressively =
- options.proguardConfiguration.isOverloadAggressivelyWithoutUseUniqueClassMemberNames();
+ options.getProguardConfiguration().isOverloadAggressivelyWithoutUseUniqueClassMemberNames();
this.globalState = NamingState.createRoot(
appInfo.dexItemFactory, dictionary, getKeyTransform(), useUniqueMemberNames);
}
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 992890b..e4ccbcd 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -72,8 +72,8 @@
fieldRenaming,
appInfo);
timing.begin("MinifyIdentifiers");
- new IdentifierMinifier(appInfo, options.proguardConfiguration.getAdaptClassStrings(), lens)
- .run();
+ new IdentifierMinifier(
+ appInfo, options.getProguardConfiguration().getAdaptClassStrings(), lens).run();
timing.end();
return lens;
}
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 bbf8259..6232fb3 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
@@ -27,6 +27,7 @@
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
+import java.util.function.Function;
public class ProguardMapApplier {
@@ -385,32 +386,15 @@
}
private DexAnnotationSet substituteTypesIn(DexAnnotationSet annotations) {
- if (annotations.isEmpty()) {
- return annotations;
- }
- DexAnnotation[] result = substituteTypesIn(annotations.annotations);
- return result == null ? annotations : new DexAnnotationSet(result);
- }
-
- private DexAnnotation[] substituteTypesIn(DexAnnotation[] annotations) {
- Map<Integer, DexAnnotation> changed = new Int2ObjectArrayMap<>();
- for (int i = 0; i < annotations.length; i++) {
- DexAnnotation applied = substituteTypesIn(annotations[i]);
- if (applied != annotations[i]) {
- changed.put(i, applied);
- }
- }
- return changed.isEmpty()
- ? null
- : ArrayUtils.copyWithSparseChanges(DexAnnotation[].class, annotations, changed);
+ return annotations.rewrite(this::substituteTypesIn);
}
private DexAnnotation substituteTypesIn(DexAnnotation annotation) {
- return new DexAnnotation(annotation.visibility, substituteTypesIn(annotation.annotation));
+ return annotation.rewrite(this::substituteTypesIn);
}
private DexEncodedAnnotation substituteTypesIn(DexEncodedAnnotation annotation) {
- return new DexEncodedAnnotation(substituteType(annotation.type, null), annotation.elements);
+ return annotation.rewrite(type -> substituteType(type, null), Function.identity());
}
private DexTypeList substituteTypesIn(DexTypeList types) {
diff --git a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
index fc28ab0..0f9b571 100644
--- a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
@@ -29,9 +29,10 @@
}
public void run() {
- String renameSourceFile = options.proguardConfiguration.getRenameSourceFileAttribute();
+ String renameSourceFile = options.getProguardConfiguration().getRenameSourceFileAttribute();
// Return early if a user wants to keep the current source file attribute as-is.
- if (renameSourceFile == null && options.proguardConfiguration.getKeepAttributes().sourceFile) {
+ if (renameSourceFile == null
+ && options.getProguardConfiguration().getKeepAttributes().sourceFile) {
return;
}
// Now, the user wants either to remove source file attribute or to rename it.
diff --git a/src/main/java/com/android/tools/r8/references/ArrayReference.java b/src/main/java/com/android/tools/r8/references/ArrayReference.java
index d6587fe..10d2a31 100644
--- a/src/main/java/com/android/tools/r8/references/ArrayReference.java
+++ b/src/main/java/com/android/tools/r8/references/ArrayReference.java
@@ -48,7 +48,7 @@
}
@Override
- public String toDescriptor() {
+ public String getDescriptor() {
return descriptor;
}
diff --git a/src/main/java/com/android/tools/r8/references/ClassReference.java b/src/main/java/com/android/tools/r8/references/ClassReference.java
index 2ad6f40..50ce220 100644
--- a/src/main/java/com/android/tools/r8/references/ClassReference.java
+++ b/src/main/java/com/android/tools/r8/references/ClassReference.java
@@ -25,7 +25,7 @@
}
@Override
- public String toDescriptor() {
+ public String getDescriptor() {
return descriptor;
}
@@ -38,4 +38,9 @@
public int hashCode() {
return System.identityHashCode(this);
}
+
+ @Override
+ public String toString() {
+ return getDescriptor();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/references/MethodReference.java b/src/main/java/com/android/tools/r8/references/MethodReference.java
index 98abfa3..e071fe9 100644
--- a/src/main/java/com/android/tools/r8/references/MethodReference.java
+++ b/src/main/java/com/android/tools/r8/references/MethodReference.java
@@ -4,6 +4,9 @@
package com.android.tools.r8.references;
import com.android.tools.r8.Keep;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.StringUtils.BraceType;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.Objects;
@@ -29,7 +32,6 @@
assert holderClass != null;
assert methodName != null;
assert formalTypes != null;
- assert returnType != null;
this.holderClass = holderClass;
this.methodName = methodName;
this.formalTypes = formalTypes;
@@ -74,4 +76,15 @@
public int hashCode() {
return Objects.hash(holderClass, methodName, formalTypes, returnType);
}
+
+ public String toDescriptor() {
+ return StringUtils.join(
+ ListUtils.map(getFormalTypes(), TypeReference::getDescriptor), "", BraceType.PARENS)
+ + (getReturnType() == null ? "V" : getReturnType().getDescriptor());
+ }
+
+ @Override
+ public String toString() {
+ return getHolderClass().toString() + getMethodName() + toDescriptor();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/references/PrimitiveReference.java b/src/main/java/com/android/tools/r8/references/PrimitiveReference.java
index 6cd92b4..66c1ae7 100644
--- a/src/main/java/com/android/tools/r8/references/PrimitiveReference.java
+++ b/src/main/java/com/android/tools/r8/references/PrimitiveReference.java
@@ -12,7 +12,7 @@
static final PrimitiveReference BOOL =
new PrimitiveReference() {
@Override
- public String toDescriptor() {
+ public String getDescriptor() {
return "Z";
}
};
@@ -20,7 +20,7 @@
static final PrimitiveReference BYTE =
new PrimitiveReference() {
@Override
- public String toDescriptor() {
+ public String getDescriptor() {
return "B";
}
};
@@ -28,7 +28,7 @@
static final PrimitiveReference CHAR =
new PrimitiveReference() {
@Override
- public String toDescriptor() {
+ public String getDescriptor() {
return "C";
}
};
@@ -36,7 +36,7 @@
static final PrimitiveReference SHORT =
new PrimitiveReference() {
@Override
- public String toDescriptor() {
+ public String getDescriptor() {
return "S";
}
};
@@ -44,7 +44,7 @@
static final PrimitiveReference INT =
new PrimitiveReference() {
@Override
- public String toDescriptor() {
+ public String getDescriptor() {
return "I";
}
};
@@ -52,7 +52,7 @@
static final PrimitiveReference FLOAT =
new PrimitiveReference() {
@Override
- public String toDescriptor() {
+ public String getDescriptor() {
return "F";
}
};
@@ -60,7 +60,7 @@
static final PrimitiveReference LONG =
new PrimitiveReference() {
@Override
- public String toDescriptor() {
+ public String getDescriptor() {
return "J";
}
};
@@ -68,7 +68,7 @@
static final PrimitiveReference DOUBLE =
new PrimitiveReference() {
@Override
- public String toDescriptor() {
+ public String getDescriptor() {
return "D";
}
};
@@ -88,6 +88,8 @@
return SHORT;
case 'I':
return INT;
+ case 'F':
+ return FLOAT;
case 'J':
return LONG;
case 'D':
@@ -103,7 +105,7 @@
}
@Override
- public abstract String toDescriptor();
+ public abstract String getDescriptor();
@Override
public boolean equals(Object o) {
diff --git a/src/main/java/com/android/tools/r8/references/Reference.java b/src/main/java/com/android/tools/r8/references/Reference.java
index a354d31..bb8e57e 100644
--- a/src/main/java/com/android/tools/r8/references/Reference.java
+++ b/src/main/java/com/android/tools/r8/references/Reference.java
@@ -43,13 +43,13 @@
private final ConcurrentMap<String, ArrayReference> arrays =
new MapMaker().weakValues().makeMap();
- // Weak set (via map) of method references. Both keys and values are weak.
+ // Weak map of method references. Keys are strong and must not be identical to the value.
private final ConcurrentMap<MethodReference, MethodReference> methods =
- new MapMaker().weakKeys().weakValues().makeMap();
+ new MapMaker().weakValues().makeMap();
- // Weak set (via map) of field references. Both keys and values are weak.
+ // Weak map of field references. Keys are strong and must not be identical to the value.
private final ConcurrentMap<FieldReference, FieldReference> fields =
- new MapMaker().weakKeys().weakValues().makeMap();
+ new MapMaker().weakValues().makeMap();
private Reference() {
// Intentionally hidden.
@@ -122,12 +122,13 @@
ImmutableList<TypeReference> formalTypes,
TypeReference returnType) {
MethodReference key = new MethodReference(holderClass, methodName, formalTypes, returnType);
- MethodReference reference = getInstance().methods.get(key);
- if (reference != null) {
- return reference;
- }
- getInstance().methods.put(key, key);
- return key;
+ return getInstance().methods.computeIfAbsent(key,
+ // Allocate a distinct value for the canonical reference so the key is not a strong pointer.
+ k -> new MethodReference(
+ k.getHolderClass(),
+ k.getMethodName(),
+ (ImmutableList<TypeReference>) k.getFormalTypes(),
+ k.getReturnType()));
}
/** Get a method reference from a Java reflection method. */
@@ -141,19 +142,19 @@
builder.add(typeFromClass(parameterType));
}
return method(
- classFromClass(holderClass), methodName, builder.build(), typeFromClass(returnType));
+ classFromClass(holderClass),
+ methodName,
+ builder.build(),
+ returnType == Void.TYPE ? null : typeFromClass(returnType));
}
/** Get a field reference from its full reference specification. */
public static FieldReference field(
ClassReference holderClass, String fieldName, TypeReference fieldType) {
FieldReference key = new FieldReference(holderClass, fieldName, fieldType);
- FieldReference reference = getInstance().fields.get(key);
- if (reference != null) {
- return reference;
- }
- getInstance().fields.put(key, key);
- return key;
+ return getInstance().fields.computeIfAbsent(key,
+ // Allocate a distinct value for the canonical reference so the key is not a strong pointer.
+ k -> new FieldReference(k.getHolderClass(), k.getFieldName(), k.getFieldType()));
}
/** Get a field reference from a Java reflection field. */
diff --git a/src/main/java/com/android/tools/r8/references/TypeReference.java b/src/main/java/com/android/tools/r8/references/TypeReference.java
index df0a502..44fb54a 100644
--- a/src/main/java/com/android/tools/r8/references/TypeReference.java
+++ b/src/main/java/com/android/tools/r8/references/TypeReference.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.references;
import com.android.tools.r8.Keep;
+import com.android.tools.r8.utils.DescriptorUtils;
@Keep
public interface TypeReference {
@@ -13,7 +14,7 @@
*
* @return The descriptor for the type.
*/
- String toDescriptor();
+ String getDescriptor();
/** Predicate that is true iff the TypeReference is an instance of ClassTypeReference. */
default boolean isClass() {
@@ -29,4 +30,8 @@
default boolean isPrimitive() {
return false;
}
+
+ default String getTypeName() {
+ return DescriptorUtils.descriptorToJavaType(getDescriptor());
+ }
}
diff --git a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
index 610bfd3..ae88ba7 100644
--- a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
@@ -3,11 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import java.util.ArrayList;
import java.util.List;
@@ -22,10 +22,10 @@
*/
public class AbstractMethodRemover {
- private final AppInfoWithSubtyping appInfo;
+ private final AppInfoWithLiveness appInfo;
private ScopedDexMethodSet scope = new ScopedDexMethodSet();
- public AbstractMethodRemover(AppInfoWithSubtyping appInfo) {
+ public AbstractMethodRemover(AppInfoWithLiveness appInfo) {
this.appInfo = appInfo;
}
@@ -52,7 +52,9 @@
List<DexEncodedMethod> methods = null;
for (int i = 0; i < virtualMethods.length; i++) {
DexEncodedMethod method = virtualMethods[i];
- if (scope.addMethodIfMoreVisible(method) || !method.accessFlags.isAbstract()) {
+ if (scope.addMethodIfMoreVisible(method)
+ || !method.accessFlags.isAbstract()
+ || appInfo.isPinned(method.method)) {
if (methods != null) {
methods.add(method);
}
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 d626407..8ce5cb0 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -5,26 +5,33 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedAnnotation;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
import java.util.List;
public class AnnotationRemover {
private final AppInfoWithLiveness appInfo;
+ private final GraphLense lense;
private final ProguardKeepAttributes keep;
private final InternalOptions options;
- public AnnotationRemover(AppInfoWithLiveness appInfo, InternalOptions options) {
+ public AnnotationRemover(AppInfoWithLiveness appInfo, GraphLense lense, InternalOptions options) {
this.appInfo = appInfo;
- this.keep = options.proguardConfiguration.getKeepAttributes();
+ this.lense = lense;
+ this.keep = options.getProguardConfiguration().getKeepAttributes();
this.options = options;
}
@@ -36,12 +43,16 @@
annotation, isAnnotationTypeLive(annotation), appInfo.dexItemFactory, options);
}
- public static boolean shouldKeepAnnotation(
+ static boolean shouldKeepAnnotation(
DexAnnotation annotation,
boolean isAnnotationTypeLive,
DexItemFactory dexItemFactory,
InternalOptions options) {
- ProguardKeepAttributes config = options.proguardConfiguration.getKeepAttributes();
+ ProguardKeepAttributes config =
+ options.getProguardConfiguration() != null
+ ? options.getProguardConfiguration().getKeepAttributes()
+ : ProguardKeepAttributes.fromPatterns(ImmutableList.of());
+
switch (annotation.visibility) {
case DexAnnotation.VISIBILITY_SYSTEM:
// InnerClass and EnclosingMember are represented in class attributes, not annotations.
@@ -94,7 +105,7 @@
private boolean isAnnotationTypeLive(DexAnnotation annotation) {
DexType annotationType = annotation.annotation.type.toBaseType(appInfo.dexItemFactory);
DexClass definition = appInfo.definitionFor(annotationType);
- // TODO(73102187): How to handle annotations without definition.
+ // TODO(b/73102187): How to handle annotations without definition.
if (options.enableTreeShaking && definition == null) {
return false;
}
@@ -133,20 +144,49 @@
public void run() {
for (DexProgramClass clazz : appInfo.classes()) {
stripAttributes(clazz);
- clazz.annotations = clazz.annotations.keepIf(this::filterAnnotations);
+ clazz.annotations = clazz.annotations.rewrite(this::rewriteAnnotation);
clazz.forEachMethod(this::processMethod);
clazz.forEachField(this::processField);
}
}
private void processMethod(DexEncodedMethod method) {
- method.annotations = method.annotations.keepIf(this::filterAnnotations);
+ method.annotations = method.annotations.rewrite(this::rewriteAnnotation);
method.parameterAnnotationsList =
method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations);
}
private void processField(DexEncodedField field) {
- field.annotations = field.annotations.keepIf(this::filterAnnotations);
+ field.annotations = field.annotations.rewrite(this::rewriteAnnotation);
+ }
+
+ private DexAnnotation rewriteAnnotation(DexAnnotation original) {
+ // Check if we should keep this annotation first.
+ if (!filterAnnotations(original)) {
+ return null;
+ }
+ // Then, filter out values that refer to dead definitions.
+ return original.rewrite(this::rewriteEncodedAnnotation);
+ }
+
+ private DexEncodedAnnotation rewriteEncodedAnnotation(DexEncodedAnnotation original) {
+ DexType annotationType = original.type.toBaseType(appInfo.dexItemFactory);
+ return original.rewrite(
+ lense::lookupType,
+ element -> rewriteAnnotationElement(lense.lookupType(annotationType), element));
+ }
+
+ private DexAnnotationElement rewriteAnnotationElement(
+ DexType annotationType, DexAnnotationElement original) {
+ DexClass definition = appInfo.definitionFor(annotationType);
+ // TODO(b/73102187): How to handle annotations without definition.
+ if (definition == null) {
+ return original;
+ }
+ assert definition.isInterface();
+ boolean liveGetter = Arrays.stream(definition.virtualMethods())
+ .anyMatch(method -> method.method.name == original.name);
+ return liveGetter ? original : null;
}
private boolean enclosingMethodPinned(DexClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/shaking/CollectingGraphConsumer.java b/src/main/java/com/android/tools/r8/shaking/CollectingGraphConsumer.java
new file mode 100644
index 0000000..27bdd95
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/CollectingGraphConsumer.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.shaking;
+
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.experimental.graphinfo.GraphNode;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/** Base implementation of a graph consumer that collects all of graph nodes and edges. */
+public class CollectingGraphConsumer implements GraphConsumer {
+
+ // Possible sub-consumer that is also inspecting the kept-graph.
+ private final GraphConsumer subConsumer;
+
+ // Directional map backwards from targets to direct sources.
+ private final Map<GraphNode, Map<GraphNode, Set<GraphEdgeInfo>>> target2sources =
+ new IdentityHashMap<>();
+
+ public CollectingGraphConsumer(GraphConsumer subConsumer) {
+ this.subConsumer = subConsumer;
+ }
+
+ @Override
+ public void acceptEdge(GraphNode source, GraphNode target, GraphEdgeInfo info) {
+ target2sources
+ .computeIfAbsent(target, k -> new IdentityHashMap<>())
+ .computeIfAbsent(source, k -> new HashSet<>())
+ .add(info);
+ if (subConsumer != null) {
+ subConsumer.acceptEdge(source, target, info);
+ }
+ }
+
+ public Set<GraphNode> getTargets() {
+ return target2sources.keySet();
+ }
+
+ public Map<GraphNode, Set<GraphEdgeInfo>> getSourcesTargeting(GraphNode target) {
+ return target2sources.get(target);
+ }
+}
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 c579500..95f077d 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -9,7 +9,17 @@
import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.experimental.graphinfo.AnnotationGraphNode;
+import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
+import com.android.tools.r8.experimental.graphinfo.FieldGraphNode;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo.EdgeKind;
+import com.android.tools.r8.experimental.graphinfo.GraphNode;
+import com.android.tools.r8.experimental.graphinfo.KeepRuleGraphNode;
+import com.android.tools.r8.experimental.graphinfo.MethodGraphNode;
import com.android.tools.r8.graph.AppInfo.ResolutionResult;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.AppView;
@@ -36,15 +46,6 @@
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.KeyedDexItem;
import com.android.tools.r8.graph.PresortedComparable;
-import com.android.tools.r8.graphinfo.AnnotationGraphNode;
-import com.android.tools.r8.graphinfo.ClassGraphNode;
-import com.android.tools.r8.graphinfo.FieldGraphNode;
-import com.android.tools.r8.graphinfo.GraphConsumer;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo.EdgeKind;
-import com.android.tools.r8.graphinfo.GraphNode;
-import com.android.tools.r8.graphinfo.KeepRuleGraphNode;
-import com.android.tools.r8.graphinfo.MethodGraphNode;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Invoke.Type;
@@ -52,6 +53,8 @@
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
import com.android.tools.r8.shaking.RootSetBuilder.IfRuleEvaluator;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
@@ -59,6 +62,7 @@
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.ImmutableList.Builder;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
@@ -1153,9 +1157,9 @@
private void markDirectStaticOrConstructorMethodAsLive(
DexEncodedMethod encodedMethod, KeepReason reason) {
assert encodedMethod != null;
+ markMethodAsTargeted(encodedMethod, reason);
if (!liveMethods.contains(encodedMethod)) {
markTypeAsLive(encodedMethod.method.holder);
- markMethodAsTargeted(encodedMethod, reason);
if (Log.ENABLED) {
Log.verbose(getClass(), "Method `%s` has become live due to direct invoke",
encodedMethod.method);
@@ -2107,26 +2111,32 @@
this.callSites = previous.callSites;
this.brokenSuperInvokes = lense.rewriteMethodsConservatively(previous.brokenSuperInvokes);
this.prunedTypes = rewriteItems(previous.prunedTypes, lense::lookupType);
- assert lense.assertDefinitionNotModified(previous.noSideEffects.keySet());
+ assert lense.assertDefinitionsNotModified(previous.noSideEffects.keySet());
this.noSideEffects = previous.noSideEffects;
- assert lense.assertDefinitionNotModified(previous.assumedValues.keySet());
+ assert lense.assertDefinitionsNotModified(previous.assumedValues.keySet());
this.assumedValues = previous.assumedValues;
- assert lense.assertDefinitionNotModified(
- previous.alwaysInline.stream().map(this::definitionFor).filter(Objects::nonNull)
+ assert lense.assertDefinitionsNotModified(
+ previous.alwaysInline.stream()
+ .map(this::definitionFor)
+ .filter(Objects::nonNull)
.collect(Collectors.toList()));
this.alwaysInline = previous.alwaysInline;
this.forceInline = lense.rewriteMethodsWithRenamedSignature(previous.forceInline);
this.neverInline = lense.rewriteMethodsWithRenamedSignature(previous.neverInline);
- assert lense.assertDefinitionNotModified(
- previous.neverMerge.stream().map(this::definitionFor).filter(Objects::nonNull)
+ assert lense.assertDefinitionsNotModified(
+ previous.neverMerge.stream()
+ .map(this::definitionFor)
+ .filter(Objects::nonNull)
.collect(Collectors.toList()));
this.neverClassInline = rewriteItems(previous.neverClassInline, lense::lookupType);
this.neverMerge = previous.neverMerge;
this.identifierNameStrings =
lense.rewriteReferencesConservatively(previous.identifierNameStrings);
// Switchmap classes should never be affected by renaming.
- assert lense.assertDefinitionNotModified(
- previous.switchMaps.keySet().stream().map(this::definitionFor).filter(Objects::nonNull)
+ assert lense.assertDefinitionsNotModified(
+ previous.switchMaps.keySet().stream()
+ .map(this::definitionFor)
+ .filter(Objects::nonNull)
.collect(Collectors.toList()));
this.switchMaps = previous.switchMaps;
this.ordinalsMaps = rewriteKeys(previous.ordinalsMaps, lense::lookupType);
@@ -2757,7 +2767,11 @@
}
private void registerEdge(GraphNode target, KeepReason reason) {
- keptGraphConsumer.acceptEdge(getSourceNode(reason), target, getEdgeInfo(reason));
+ GraphNode sourceNode = getSourceNode(reason);
+ // TODO(b/120959039): Make sure we do have edges to nodes deriving library nodes!
+ if (!sourceNode.isLibraryNode()) {
+ keptGraphConsumer.acceptEdge(sourceNode, target, getEdgeInfo(reason));
+ }
}
private GraphNode getSourceNode(KeepReason reason) {
@@ -2782,19 +2796,52 @@
}
AnnotationGraphNode getAnnotationGraphNode(DexItem type) {
- return annotationNodes.computeIfAbsent(type, AnnotationGraphNode::new);
+ return annotationNodes.computeIfAbsent(type, t -> {
+ if (t instanceof DexType) {
+ return new AnnotationGraphNode(getClassGraphNode(((DexType) t)));
+ }
+ throw new Unimplemented("Incomplete support for annotation node on item: " + type.getClass());
+ });
}
ClassGraphNode getClassGraphNode(DexType type) {
- return classNodes.computeIfAbsent(type, ClassGraphNode::new);
+ return classNodes.computeIfAbsent(type,
+ t -> new ClassGraphNode(
+ appInfo.definitionFor(type).isLibraryClass(),
+ Reference.classFromDescriptor(t.toDescriptorString())));
}
MethodGraphNode getMethodGraphNode(DexMethod context) {
- return methodNodes.computeIfAbsent(context, MethodGraphNode::new);
+ return methodNodes.computeIfAbsent(
+ context,
+ m -> {
+ boolean isLibraryNode = appInfo.definitionFor(context.holder).isLibraryClass();
+ Builder<TypeReference> builder = ImmutableList.builder();
+ for (DexType param : m.proto.parameters.values) {
+ builder.add(Reference.typeFromDescriptor(param.toDescriptorString()));
+ }
+ return new MethodGraphNode(
+ isLibraryNode,
+ Reference.method(
+ Reference.classFromDescriptor(m.holder.toDescriptorString()),
+ m.name.toString(),
+ builder.build(),
+ m.proto.returnType.isVoidType()
+ ? null
+ : Reference.typeFromDescriptor(m.proto.returnType.toDescriptorString())));
+ });
}
FieldGraphNode getFieldGraphNode(DexField context) {
- return fieldNodes.computeIfAbsent(context, FieldGraphNode::new);
+ return fieldNodes.computeIfAbsent(
+ context,
+ f ->
+ new FieldGraphNode(
+ appInfo.definitionFor(context.getHolder()).isLibraryClass(),
+ Reference.field(
+ Reference.classFromDescriptor(f.getHolder().toDescriptorString()),
+ f.name.toString(),
+ Reference.typeFromDescriptor(f.type.toDescriptorString()))));
}
KeepRuleGraphNode getKeepRuleGraphNode(ProguardKeepRule rule) {
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepReason.java b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
index 82737a6..16d78a7 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepReason.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepReason.java
@@ -3,12 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo.EdgeKind;
+import com.android.tools.r8.experimental.graphinfo.GraphNode;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo.EdgeKind;
-import com.android.tools.r8.graphinfo.GraphNode;
// TODO(herhut): Canonicalize reason objects.
public abstract class KeepReason {
@@ -50,7 +50,7 @@
}
public static KeepReason fieldReferencedIn(DexEncodedMethod method) {
- return new ReferenedFrom(method);
+ return new ReferencedFrom(method);
}
public static KeepReason referencedInAnnotation(DexItem holder) {
@@ -219,9 +219,9 @@
}
}
- private static class ReferenedFrom extends BasedOnOtherMethod {
+ private static class ReferencedFrom extends BasedOnOtherMethod {
- private ReferenedFrom(DexEncodedMethod method) {
+ private ReferencedFrom(DexEncodedMethod method) {
super(method);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index 9e66c52..543dcc4 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
@@ -18,6 +19,7 @@
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.UseRegistry;
+import com.google.common.collect.ImmutableSet;
import java.util.Set;
import java.util.function.Consumer;
@@ -44,14 +46,54 @@
// Super and interfaces are live, no need to add them.
traceAnnotationsDirectDependencies(clazz.annotations);
clazz.forEachField(field -> consumer.accept(field.field.type));
- clazz.forEachMethod(
- method -> {
- traceMethodDirectDependencies(method.method, consumer);
- method.registerCodeReferences(codeDirectReferenceCollector);
- });
+ clazz.forEachMethod(method -> {
+ traceMethodDirectDependencies(method.method, consumer);
+ method.registerCodeReferences(codeDirectReferenceCollector);
+ });
}
}
+ public void runOnCode(DexEncodedMethod method) {
+ method.registerCodeReferences(codeDirectReferenceCollector);
+ }
+
+ private static class BooleanBox {
+ boolean value = false;
+ }
+
+ public static boolean hasReferencesOutside(
+ AppInfoWithSubtyping appInfo, DexProgramClass clazz, Set<DexType> types) {
+ BooleanBox result = new BooleanBox();
+
+ new MainDexDirectReferenceTracer(appInfo, type -> {
+ if (!types.contains(type)) {
+ DexClass cls = appInfo.definitionFor(type);
+ if (cls != null && !cls.isLibraryClass()) {
+ result.value = true;
+ }
+ }
+ }).run(ImmutableSet.of(clazz.type));
+
+ return result.value;
+ }
+
+ public static boolean hasReferencesOutsideFromCode(
+ AppInfoWithSubtyping appInfo, DexEncodedMethod method, Set<DexType> classes) {
+
+ BooleanBox result = new BooleanBox();
+
+ new MainDexDirectReferenceTracer(appInfo, type -> {
+ if (!classes.contains(type)) {
+ DexClass cls = appInfo.definitionFor(type);
+ if (cls != null && !cls.isLibraryClass()) {
+ result.value = true;
+ }
+ }
+ }).runOnCode(method);
+
+ return result.value;
+ }
+
private void traceAnnotationsDirectDependencies(DexAnnotationSet annotations) {
annotations.collectIndexedItems(annotationDirectReferenceCollector);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index 26bfcfb..c153fe3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -103,12 +103,14 @@
this.ignoreWarnings = ignoreWarnings;
}
- public void disableOptimization() {
+ public Builder disableOptimization() {
this.optimizing = false;
+ return this;
}
- public void disableObfuscation() {
+ public Builder disableObfuscation() {
this.obfuscating = false;
+ return this;
}
boolean isObfuscating() {
@@ -123,8 +125,9 @@
return shrinking;
}
- public void disableShrinking() {
+ public Builder disableShrinking() {
shrinking = false;
+ return this;
}
public void setPrintConfiguration(boolean printConfiguration) {
@@ -165,8 +168,9 @@
this.renameSourceFileAttribute = renameSourceFileAttribute;
}
- public void addKeepAttributePatterns(List<String> keepAttributePatterns) {
+ public Builder addKeepAttributePatterns(List<String> keepAttributePatterns) {
this.keepAttributePatterns.addAll(keepAttributePatterns);
+ return this;
}
public void addRule(ProguardConfigurationRule rule) {
@@ -315,20 +319,13 @@
}
public ProguardConfiguration build() {
- boolean rulesWasEmpty = rules.isEmpty();
- if (rules.isEmpty()) {
- disableObfuscation();
- disableShrinking();
- // TODO(sgjesse): Honor disable-optimization flag when no config provided.
- // disableOptimization();
- }
-
- if ((keepAttributePatterns.isEmpty() && rulesWasEmpty)
- || (forceProguardCompatibility && !isObfuscating())
- || !isShrinking()) {
+ if (forceProguardCompatibility && !isObfuscating()) {
+ // For Proguard -keepattributes are only applicable when obfuscating.
keepAttributePatterns.addAll(ProguardKeepAttributes.KEEP_ALL);
}
-
+ // If either of the flags -dontshrink, -dontobfuscate or -dontoptimize is passed, or
+ // shrinking or minification is turned off through the API, then add a match all rule
+ // which will apply that.
if (!isShrinking() || !isObfuscating() || !isOptimizing()) {
addRule(ProguardKeepRule.defaultKeepAllRule(modifiers -> {
modifiers.setAllowsShrinking(isShrinking());
@@ -601,11 +598,6 @@
return keepDirectories;
}
- public static ProguardConfiguration defaultConfiguration(DexItemFactory dexItemFactory,
- Reporter reporter) {
- return builder(dexItemFactory, reporter).build();
- }
-
public boolean isPrintSeeds() {
return printSeeds;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 33ee223..8d2c1b9 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -81,6 +81,10 @@
// TODO(b/37137994): -outjars should be reported as errors, not just as warnings!
"outjars");
+ private static final List<String> WARNED_OPTIONAL_SINGLE_ARG_OPTIONS = ImmutableList.of(
+ // TODO(b/121340442): we may support this later.
+ "dump");
+
private static final List<String> WARNED_FLAG_OPTIONS = ImmutableList.of(
// TODO(b/73707846): add support -addconfigurationdebugging
"addconfigurationdebugging");
@@ -416,7 +420,11 @@
if (option == null) {
option = Iterables.find(WARNED_SINGLE_ARG_OPTIONS, this::skipOptionWithSingleArg, null);
if (option == null) {
- return false;
+ option = Iterables.find(
+ WARNED_OPTIONAL_SINGLE_ARG_OPTIONS, this::skipOptionWithOptionalSingleArg, null);
+ if (option == null) {
+ return false;
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
index 79828b3..cff6164 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepAttributes.java
@@ -73,7 +73,7 @@
if (previous) {
return true;
}
- if (pattern.charAt(0) == '!') {
+ if (pattern.length() > 0 && pattern.charAt(0) == '!') {
if (matches(pattern, 1, text, 0)) {
break;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 75523bf..69d2ee1 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.Descriptor;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexApplication;
@@ -25,12 +26,16 @@
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.OffOrAuto;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThreadUtils;
import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
@@ -40,6 +45,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -58,7 +64,7 @@
private final Map<DexDefinition, ProguardKeepRule> noShrinking = new IdentityHashMap<>();
private final Set<DexDefinition> noOptimization = Sets.newIdentityHashSet();
private final Set<DexDefinition> noObfuscation = Sets.newIdentityHashSet();
- private final Set<DexDefinition> reasonAsked = Sets.newIdentityHashSet();
+ private final LinkedHashMap<DexDefinition, DexDefinition> reasonAsked = new LinkedHashMap<>();
private final Set<DexDefinition> keepPackageName = Sets.newIdentityHashSet();
private final Set<ProguardConfigurationRule> rulesThatUseExtendsOrImplementsWrong =
Sets.newIdentityHashSet();
@@ -123,21 +129,22 @@
Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier;
if (rule instanceof ProguardKeepRule) {
+ if (clazz.isLibraryClass()) {
+ return;
+ }
switch (((ProguardKeepRule) rule).getType()) {
- case KEEP_CLASS_MEMBERS: {
+ case KEEP_CLASS_MEMBERS:
// Members mentioned at -keepclassmembers always depend on their holder.
preconditionSupplier = ImmutableMap.of(definition -> true, clazz);
- markMatchingVisibleMethods(clazz, memberKeepRules, rule, preconditionSupplier);
- markMatchingVisibleFields(clazz, memberKeepRules, rule, preconditionSupplier);
+ markMatchingVisibleMethods(clazz, memberKeepRules, rule, preconditionSupplier, false);
+ markMatchingVisibleFields(clazz, memberKeepRules, rule, preconditionSupplier, false);
break;
- }
- case KEEP_CLASSES_WITH_MEMBERS: {
+ case KEEP_CLASSES_WITH_MEMBERS:
if (!allRulesSatisfied(memberKeepRules, clazz)) {
break;
}
// fallthrough;
- }
- case KEEP: {
+ case KEEP:
markClass(clazz, rule);
preconditionSupplier = new HashMap<>();
if (ifRule != null) {
@@ -150,10 +157,9 @@
// not triggered conditionally.
preconditionSupplier.put((definition -> true), null);
}
- markMatchingVisibleMethods(clazz, memberKeepRules, rule, preconditionSupplier);
- markMatchingVisibleFields(clazz, memberKeepRules, rule, preconditionSupplier);
+ markMatchingVisibleMethods(clazz, memberKeepRules, rule, preconditionSupplier, false);
+ markMatchingVisibleFields(clazz, memberKeepRules, rule, preconditionSupplier, false);
break;
- }
case CONDITIONAL:
throw new Unreachable("-if rule will be evaluated separately, not here.");
}
@@ -170,11 +176,11 @@
} else if (rule instanceof ProguardWhyAreYouKeepingRule
|| rule instanceof ProguardKeepPackageNamesRule) {
markClass(clazz, rule);
- markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
- markMatchingVisibleFields(clazz, memberKeepRules, rule, null);
+ markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true);
+ markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true);
} else if (rule instanceof ProguardAssumeNoSideEffectRule) {
- markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
- markMatchingVisibleFields(clazz, memberKeepRules, rule, null);
+ markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true);
+ markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true);
} else if (rule instanceof ClassMergingRule) {
if (allRulesSatisfied(memberKeepRules, clazz)) {
markClass(clazz, rule);
@@ -186,8 +192,8 @@
markClass(clazz, rule);
}
} else if (rule instanceof ProguardAssumeValuesRule) {
- markMatchingVisibleMethods(clazz, memberKeepRules, rule, null);
- markMatchingVisibleFields(clazz, memberKeepRules, rule, null);
+ markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true);
+ markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true);
} else {
assert rule instanceof ProguardIdentifierNameStringRule;
markMatchingFields(clazz, memberKeepRules, rule, null);
@@ -249,7 +255,7 @@
noShrinking,
noOptimization,
noObfuscation,
- reasonAsked,
+ ImmutableList.copyOf(reasonAsked.values()),
keepPackageName,
checkDiscarded,
alwaysInline,
@@ -463,22 +469,30 @@
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
+ Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+ boolean includeLibraryClasses) {
Set<Wrapper<DexMethod>> methodsMarked =
options.forceProguardCompatibility ? null : new HashSet<>();
DexClass startingClass = clazz;
while (clazz != null) {
+ if (!includeLibraryClasses && clazz.isLibraryClass()) {
+ return;
+ }
// In compat mode traverse all direct methods in the hierarchy.
if (clazz == startingClass || options.forceProguardCompatibility) {
- Arrays.stream(clazz.directMethods()).forEach(method -> {
- DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
- markMethod(method, memberKeepRules, methodsMarked, rule, precondition);
- });
+ Arrays.stream(clazz.directMethods())
+ .forEach(
+ method -> {
+ DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
+ markMethod(method, memberKeepRules, methodsMarked, rule, precondition);
+ });
}
- Arrays.stream(clazz.virtualMethods()).forEach(method -> {
- DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
- markMethod(method, memberKeepRules, methodsMarked, rule, precondition);
- });
+ Arrays.stream(clazz.virtualMethods())
+ .forEach(
+ method -> {
+ DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
+ markMethod(method, memberKeepRules, methodsMarked, rule, precondition);
+ });
clazz = clazz.superType == null ? null : application.definitionFor(clazz.superType);
}
}
@@ -488,18 +502,23 @@
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
- clazz.forEachMethod(method -> {
- DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
- markMethod(method, memberKeepRules, null, rule, precondition);
- });
+ clazz.forEachMethod(
+ method -> {
+ DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
+ markMethod(method, memberKeepRules, null, rule, precondition);
+ });
}
private void markMatchingVisibleFields(
DexClass clazz,
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
- Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
+ Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier,
+ boolean includeLibraryClasses) {
while (clazz != null) {
+ if (!includeLibraryClasses && clazz.isLibraryClass()) {
+ return;
+ }
clazz.forEachField(
field -> {
DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
@@ -514,10 +533,11 @@
Collection<ProguardMemberRule> memberKeepRules,
ProguardConfigurationRule rule,
Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) {
- clazz.forEachField(field -> {
- DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
- markField(field, memberKeepRules, rule, precondition);
- });
+ clazz.forEachField(
+ field -> {
+ DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier);
+ markField(field, memberKeepRules, rule, precondition);
+ });
}
// TODO(b/67934426): Test this code.
@@ -855,6 +875,11 @@
ProguardMemberRule rule,
DexDefinition precondition) {
if (context instanceof ProguardKeepRule) {
+ if (item.isDexEncodedMethod() && item.asDexEncodedMethod().accessFlags.isSynthetic()) {
+ // Don't keep synthetic methods (see b/120971047 for additional details).
+ return;
+ }
+
ProguardKeepRule keepRule = (ProguardKeepRule) context;
ProguardKeepRuleModifiers modifiers = keepRule.getModifiers();
if (!modifiers.allowsShrinking) {
@@ -877,7 +902,7 @@
} else if (context instanceof ProguardAssumeNoSideEffectRule) {
noSideEffects.put(item, rule);
} else if (context instanceof ProguardWhyAreYouKeepingRule) {
- reasonAsked.add(item);
+ reasonAsked.computeIfAbsent(item, i -> i);
} else if (context instanceof ProguardKeepPackageNamesRule) {
keepPackageName.add(item);
} else if (context instanceof ProguardAssumeValuesRule) {
@@ -934,7 +959,7 @@
public final Map<DexDefinition, ProguardKeepRule> noShrinking;
public final Set<DexDefinition> noOptimization;
public final Set<DexDefinition> noObfuscation;
- public final Set<DexDefinition> reasonAsked;
+ public final ImmutableList<DexDefinition> reasonAsked;
public final Set<DexDefinition> keepPackageName;
public final Set<DexDefinition> checkDiscarded;
public final Set<DexMethod> alwaysInline;
@@ -952,7 +977,7 @@
Map<DexDefinition, ProguardKeepRule> noShrinking,
Set<DexDefinition> noOptimization,
Set<DexDefinition> noObfuscation,
- Set<DexDefinition> reasonAsked,
+ ImmutableList<DexDefinition> reasonAsked,
Set<DexDefinition> keepPackageName,
Set<DexDefinition> checkDiscarded,
Set<DexMethod> alwaysInline,
@@ -968,7 +993,7 @@
this.noShrinking = Collections.unmodifiableMap(noShrinking);
this.noOptimization = noOptimization;
this.noObfuscation = noObfuscation;
- this.reasonAsked = Collections.unmodifiableSet(reasonAsked);
+ this.reasonAsked = reasonAsked;
this.keepPackageName = Collections.unmodifiableSet(keepPackageName);
this.checkDiscarded = Collections.unmodifiableSet(checkDiscarded);
this.alwaysInline = Collections.unmodifiableSet(alwaysInline);
@@ -986,10 +1011,11 @@
// Add dependent items that depend on -if rules.
void addDependentItems(
Map<DexDefinition, Map<DexDefinition, ProguardKeepRule>> dependentItems) {
- dependentItems.forEach((def, dependence) -> {
- dependentNoShrinking.computeIfAbsent(def, x -> new IdentityHashMap<>())
- .putAll(dependence);
- });
+ dependentItems.forEach(
+ (def, dependence) ->
+ dependentNoShrinking
+ .computeIfAbsent(def, x -> new IdentityHashMap<>())
+ .putAll(dependence));
}
Map<DexDefinition, ProguardKeepRule> getDependentItems(DexDefinition item) {
@@ -997,6 +1023,156 @@
.unmodifiableMap(dependentNoShrinking.getOrDefault(item, Collections.emptyMap()));
}
+ public boolean verifyKeptFieldsAreAccessedAndLive(AppInfoWithLiveness appInfo) {
+ for (DexDefinition definition : noShrinking.keySet()) {
+ if (definition.isDexEncodedField()) {
+ DexEncodedField field = definition.asDexEncodedField();
+ if (field.isStatic() || isKeptDirectlyOrIndirectly(field.field.clazz, appInfo)) {
+ // TODO(b/121354886): Enable asserts for reads and writes.
+ /*assert appInfo.fieldsRead.contains(field.field)
+ : "Expected kept field `" + field.field.toSourceString() + "` to be read";
+ assert appInfo.fieldsWritten.contains(field.field)
+ : "Expected kept field `" + field.field.toSourceString() + "` to be written";*/
+ assert appInfo.liveFields.contains(field.field)
+ : "Expected kept field `" + field.field.toSourceString() + "` to be live";
+ }
+ }
+ }
+ return true;
+ }
+
+ public boolean verifyKeptMethodsAreTargetedAndLive(AppInfoWithLiveness appInfo) {
+ for (DexDefinition definition : noShrinking.keySet()) {
+ if (definition.isDexEncodedMethod()) {
+ DexEncodedMethod method = definition.asDexEncodedMethod();
+ assert appInfo.targetedMethods.contains(method.method)
+ : "Expected kept method `" + method.method.toSourceString() + "` to be targeted";
+
+ if (!method.accessFlags.isAbstract()
+ && isKeptDirectlyOrIndirectly(method.method.holder, appInfo)) {
+ assert appInfo.liveMethods.contains(method.method)
+ : "Expected non-abstract kept method `"
+ + method.method.toSourceString()
+ + "` to be live";
+ }
+ }
+ }
+ return true;
+ }
+
+ public boolean verifyKeptTypesAreLive(AppInfoWithLiveness appInfo) {
+ for (DexDefinition definition : noShrinking.keySet()) {
+ if (definition.isDexClass()) {
+ DexClass clazz = definition.asDexClass();
+ assert appInfo.liveTypes.contains(clazz.type)
+ : "Expected kept type `" + clazz.type.toSourceString() + "` to be live";
+ }
+ }
+ return true;
+ }
+
+ private boolean isKeptDirectlyOrIndirectly(DexType type, AppInfoWithLiveness appInfo) {
+ DexClass clazz = appInfo.definitionFor(type);
+ if (clazz == null) {
+ return false;
+ }
+ if (noShrinking.containsKey(clazz)) {
+ return true;
+ }
+ if (clazz.superType != null) {
+ return isKeptDirectlyOrIndirectly(clazz.superType, appInfo);
+ }
+ return false;
+ }
+
+ public boolean verifyKeptItemsAreKept(
+ DexApplication application, AppInfo appInfo, InternalOptions options) {
+ if (options.getProguardConfiguration().hasApplyMappingFile()) {
+ // TODO(b/121295633): Root set is obsolete after mapping has been applied.
+ return true;
+ }
+
+ boolean isInterfaceMethodDesugaringEnabled =
+ options.enableDesugaring
+ && options.interfaceMethodDesugaring == OffOrAuto.Auto
+ && !options.canUseDefaultAndStaticInterfaceMethods();
+
+ Set<DexReference> pinnedItems =
+ appInfo.hasLiveness() ? appInfo.withLiveness().pinnedItems : null;
+
+ // Create a mapping from each required type to the set of required members on that type.
+ Map<DexType, Set<DexDefinition>> requiredDefinitionsPerType = new IdentityHashMap<>();
+ for (DexDefinition definition : noShrinking.keySet()) {
+ // Check that `pinnedItems` is a super set of the root set.
+ assert pinnedItems == null || pinnedItems.contains(definition.toReference());
+ if (definition.isDexClass()) {
+ DexType type = definition.toReference().asDexType();
+ requiredDefinitionsPerType.putIfAbsent(type, Sets.newIdentityHashSet());
+ } else {
+ assert definition.isDexEncodedField() || definition.isDexEncodedMethod();
+ Descriptor<?, ?> descriptor = definition.toReference().asDescriptor();
+ requiredDefinitionsPerType
+ .computeIfAbsent(descriptor.getHolder(), key -> Sets.newIdentityHashSet())
+ .add(definition);
+ }
+ }
+
+ // Run through each class in the program and check that it has members it must have.
+ for (DexProgramClass clazz : application.classes()) {
+ Set<DexDefinition> requiredDefinitions =
+ requiredDefinitionsPerType.getOrDefault(clazz.type, ImmutableSet.of());
+
+ Set<DexField> fields = null;
+ Set<DexMethod> methods = null;
+
+ for (DexDefinition requiredDefinition : requiredDefinitions) {
+ if (requiredDefinition.isDexEncodedField()) {
+ DexEncodedField requiredField = requiredDefinition.asDexEncodedField();
+ if (fields == null) {
+ // Create a Set of the fields to avoid quadratic behavior.
+ fields =
+ Streams.stream(clazz.fields())
+ .map(DexEncodedField::getKey)
+ .collect(Collectors.toSet());
+ }
+ assert fields.contains(requiredField.field);
+ } else if (requiredDefinition.isDexEncodedMethod()) {
+ DexEncodedMethod requiredMethod = requiredDefinition.asDexEncodedMethod();
+ if (isInterfaceMethodDesugaringEnabled) {
+ if (clazz.isInterface() && requiredMethod.hasCode()) {
+ // TODO(b/121240523): Ideally, these default interface methods should not be in the
+ // root set.
+ continue;
+ }
+ }
+ if (methods == null) {
+ // Create a Set of the methods to avoid quadratic behavior.
+ methods =
+ Streams.stream(clazz.methods())
+ .map(DexEncodedMethod::getKey)
+ .collect(Collectors.toSet());
+ }
+ assert methods.contains(requiredMethod.method);
+ } else {
+ assert false;
+ }
+ }
+ requiredDefinitionsPerType.remove(clazz.type);
+ }
+
+ // If the map is non-empty, then a type in the root set was not in the application.
+ if (!requiredDefinitionsPerType.isEmpty()) {
+ DexType type = requiredDefinitionsPerType.keySet().iterator().next();
+ DexClass clazz = application.definitionFor(type);
+ assert clazz == null || clazz.isProgramClass()
+ : "Unexpected library type in root set: `" + type + "`";
+ assert requiredDefinitionsPerType.isEmpty()
+ : "Expected type `" + type.toSourceString() + "` to be present";
+ }
+
+ return true;
+ }
+
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index d1e0606..a51116b 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.shaking;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -48,10 +49,99 @@
* of X into Y -- it does not change all occurrences of X in the program into Y. This makes the
* optimization more applicable, because it would otherwise not be possible to merge two classes if
* they inherited from, say, X' and Y' (since multiple inheritance is not allowed).
+ *
+ * <p>When there is a main dex specification, merging will only happen within the three groups of
+ * classes found from main dex tracing ("main dex roots", "main dex dependencies" and
+ * "non main dex classes"), and not between them. This ensures that the size of the main dex
+ * will not grow out of proportions due to non main dex classes getting merged into main dex
+ * classes.
*/
public class StaticClassMerger {
- private static final String GLOBAL = "<global>";
+ enum MergeGroup {
+ MAIN_DEX_ROOTS,
+ MAIN_DEX_DEPENDENCIES,
+ NOT_MAIN_DEX,
+ DONT_MERGE;
+
+ private static final String GLOBAL = "<global>";
+ private static Key mainDexRootsGlobalKey = new Key(MergeGroup.MAIN_DEX_ROOTS, GLOBAL);
+ private static Key mainDexDependenciesGlobalKey =
+ new Key(MergeGroup.MAIN_DEX_DEPENDENCIES, GLOBAL);
+ private static Key notMainDexGlobalKey = new Key(MergeGroup.NOT_MAIN_DEX, GLOBAL);
+
+ private static class Key {
+ private final MergeGroup mergeGroup;
+ private final String packageOrGlobal;
+
+ public Key(MergeGroup mergeGroup, String packageOrGlobal) {
+ this.mergeGroup = mergeGroup;
+ this.packageOrGlobal = packageOrGlobal;
+ }
+
+ public MergeGroup getMergeGroup() {
+ return mergeGroup;
+ }
+
+ public String getPackageOrGlobal() {
+ return packageOrGlobal;
+ }
+
+ public boolean isGlobal() {
+ return packageOrGlobal.equals(GLOBAL);
+ }
+
+ @Override
+ public int hashCode() {
+ return mergeGroup.ordinal() * 13 + packageOrGlobal.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (other == null || this.getClass() != other.getClass()) {
+ return false;
+ }
+ Key o = (Key) other;
+ return o.mergeGroup == mergeGroup && o.packageOrGlobal.equals(packageOrGlobal);
+ }
+ }
+
+ public Key globalKey() {
+ switch (this) {
+ case NOT_MAIN_DEX:
+ return notMainDexGlobalKey;
+ case MAIN_DEX_ROOTS:
+ return mainDexRootsGlobalKey;
+ case MAIN_DEX_DEPENDENCIES:
+ return mainDexDependenciesGlobalKey;
+ default:
+ throw new Unreachable("Unexpected MergeGroup value");
+ }
+ }
+
+ public Key key(String pkg) {
+ assert this != DONT_MERGE;
+ return new Key(this, pkg);
+ }
+
+ @Override
+ public String toString() {
+ switch (this) {
+ case NOT_MAIN_DEX:
+ return "outside main dex";
+ case MAIN_DEX_ROOTS:
+ return "main dex roots";
+ case MAIN_DEX_DEPENDENCIES:
+ return "main dex dependencies";
+ default:
+ assert this == DONT_MERGE;
+ return "don't merge";
+ }
+ }
+ }
// There are 52 characters in [a-zA-Z], so with a capacity just below 52 the minifier should be
// able to find single-character names for all members, but around 30 appears to work better in
@@ -101,12 +191,13 @@
}
private final AppView<? extends AppInfoWithLiveness> appView;
+ private final MainDexClasses mainDexClasses;
/** The equivalence that should be used for the member buckets in {@link Representative}. */
private final Equivalence<DexField> fieldEquivalence;
private final Equivalence<DexMethod> methodEquivalence;
- private final Map<String, Representative> representatives = new HashMap<>();
+ private final Map<MergeGroup.Key, Representative> representatives = new HashMap<>();
private final BiMap<DexField, DexField> fieldMapping = HashBiMap.create();
private final BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
@@ -114,21 +205,26 @@
private int numberOfMergedClasses = 0;
public StaticClassMerger(
- AppView<? extends AppInfoWithLiveness> appView, InternalOptions options) {
+ AppView<? extends AppInfoWithLiveness> appView,
+ InternalOptions options,
+ MainDexClasses mainDexClasses) {
this.appView = appView;
- if (options.proguardConfiguration.isOverloadAggressivelyWithoutUseUniqueClassMemberNames()) {
+ if (options
+ .getProguardConfiguration().isOverloadAggressivelyWithoutUseUniqueClassMemberNames()) {
fieldEquivalence = FieldSignatureEquivalence.getEquivalenceIgnoreName();
methodEquivalence = MethodSignatureEquivalence.getEquivalenceIgnoreName();
} else {
fieldEquivalence = new SingletonEquivalence<>();
methodEquivalence = MethodJavaSignatureEquivalence.getEquivalenceIgnoreName();
}
+ this.mainDexClasses = mainDexClasses;
}
public GraphLense run() {
for (DexProgramClass clazz : appView.appInfo().app.classesWithDeterministicOrder()) {
- if (satisfiesMergeCriteria(clazz)) {
- merge(clazz);
+ MergeGroup group = satisfiesMergeCriteria(clazz);
+ if (group != MergeGroup.DONT_MERGE) {
+ merge(clazz, group);
}
}
if (Log.ENABLED) {
@@ -157,26 +253,26 @@
return appView.graphLense();
}
- private boolean satisfiesMergeCriteria(DexProgramClass clazz) {
+ private MergeGroup satisfiesMergeCriteria(DexProgramClass clazz) {
if (appView.appInfo().neverMerge.contains(clazz.type)) {
- return false;
+ return MergeGroup.DONT_MERGE;
}
if (clazz.staticFields().length + clazz.directMethods().length + clazz.virtualMethods().length
== 0) {
- return false;
+ return MergeGroup.DONT_MERGE;
}
if (clazz.instanceFields().length > 0) {
- return false;
+ return MergeGroup.DONT_MERGE;
}
if (Arrays.stream(clazz.staticFields())
.anyMatch(field -> appView.appInfo().isPinned(field.field))) {
- return false;
+ return MergeGroup.DONT_MERGE;
}
if (Arrays.stream(clazz.directMethods()).anyMatch(DexEncodedMethod::isInitializer)) {
- return false;
+ return MergeGroup.DONT_MERGE;
}
if (!Arrays.stream(clazz.virtualMethods()).allMatch(DexEncodedMethod::isPrivateMethod)) {
- return false;
+ return MergeGroup.DONT_MERGE;
}
if (Streams.stream(clazz.methods())
.anyMatch(
@@ -187,7 +283,7 @@
// modify any methods from the sets alwaysInline and noSideEffects.
|| appView.appInfo().alwaysInline.contains(method.method)
|| appView.appInfo().noSideEffects.keySet().contains(method))) {
- return false;
+ return MergeGroup.DONT_MERGE;
}
if (clazz.classInitializationMayHaveSideEffects(appView.appInfo())) {
// This could have a negative impact on inlining.
@@ -196,33 +292,41 @@
// for further details.
//
// Note that this will be true for all classes that inherit from or implement a library class.
- return false;
+ return MergeGroup.DONT_MERGE;
}
- return true;
+ if (!mainDexClasses.isEmpty()) {
+ if (mainDexClasses.getRoots().contains(clazz.type)) {
+ return MergeGroup.MAIN_DEX_ROOTS;
+ }
+ if (mainDexClasses.getDependencies().contains(clazz.type)) {
+ return MergeGroup.MAIN_DEX_DEPENDENCIES;
+ }
+ }
+ return MergeGroup.NOT_MAIN_DEX;
}
private boolean isValidRepresentative(DexProgramClass clazz) {
// Disallow interfaces from being representatives, since interface methods require desugaring.
return !clazz.isInterface();
}
-
- private boolean merge(DexProgramClass clazz) {
- assert satisfiesMergeCriteria(clazz);
+ private boolean merge(DexProgramClass clazz, MergeGroup group) {
+ assert satisfiesMergeCriteria(clazz) == group;
+ assert group != MergeGroup.DONT_MERGE;
String pkg = clazz.type.getPackageDescriptor();
return mayMergeAcrossPackageBoundaries(clazz)
- ? mergeGlobally(clazz, pkg)
- : mergeInsidePackage(clazz, pkg);
+ ? mergeGlobally(clazz, pkg, group)
+ : mergeInsidePackage(clazz, pkg, group);
}
- private boolean mergeGlobally(DexProgramClass clazz, String pkg) {
- Representative globalRepresentative = representatives.get(GLOBAL);
+ private boolean mergeGlobally(DexProgramClass clazz, String pkg, MergeGroup group) {
+ Representative globalRepresentative = representatives.get(group.globalKey());
if (globalRepresentative == null) {
if (isValidRepresentative(clazz)) {
// Make the current class the global representative.
- setRepresentative(GLOBAL, getOrCreateRepresentative(clazz, pkg));
+ setRepresentative(group.globalKey(), getOrCreateRepresentative(group.key(pkg), clazz));
} else {
- clearRepresentative(GLOBAL);
+ clearRepresentative(group.globalKey());
}
// Do not attempt to merge this class inside its own package, because that could lead to
@@ -235,9 +339,9 @@
if (globalRepresentative.isFull()) {
if (isValidRepresentative(clazz)) {
// Make the current class the global representative instead.
- setRepresentative(GLOBAL, getOrCreateRepresentative(clazz, pkg));
+ setRepresentative(group.globalKey(), getOrCreateRepresentative(group.key(pkg), clazz));
} else {
- clearRepresentative(GLOBAL);
+ clearRepresentative(group.globalKey());
}
// Do not attempt to merge this class inside its own package, because that could lead to
@@ -251,17 +355,18 @@
}
}
- private boolean mergeInsidePackage(DexProgramClass clazz, String pkg) {
- Representative packageRepresentative = representatives.get(pkg);
+ private boolean mergeInsidePackage(DexProgramClass clazz, String pkg, MergeGroup group) {
+ MergeGroup.Key key = group.key(pkg);
+ Representative packageRepresentative = representatives.get(key);
if (packageRepresentative != null) {
if (isValidRepresentative(clazz)
&& clazz.accessFlags.isMoreVisibleThan(packageRepresentative.clazz.accessFlags)) {
// Use `clazz` as a representative for this package instead.
- Representative newRepresentative = getOrCreateRepresentative(clazz, pkg);
+ Representative newRepresentative = getOrCreateRepresentative(key, clazz);
newRepresentative.include(packageRepresentative.clazz);
if (!newRepresentative.isFull()) {
- setRepresentative(pkg, newRepresentative);
+ setRepresentative(group.key(pkg), newRepresentative);
moveMembersFromSourceToTarget(packageRepresentative.clazz, clazz);
return true;
}
@@ -283,51 +388,54 @@
// We were unable to use the current representative for this package (if any).
if (isValidRepresentative(clazz)) {
- setRepresentative(pkg, getOrCreateRepresentative(clazz, pkg));
+ setRepresentative(key, getOrCreateRepresentative(key, clazz));
}
return false;
}
- private Representative getOrCreateRepresentative(DexProgramClass clazz, String pkg) {
- Representative globalRepresentative = representatives.get(GLOBAL);
+ private Representative getOrCreateRepresentative(MergeGroup.Key key, DexProgramClass clazz) {
+ Representative globalRepresentative = representatives.get(key.getMergeGroup().globalKey());
if (globalRepresentative != null && globalRepresentative.clazz == clazz) {
return globalRepresentative;
}
- Representative packageRepresentative = representatives.get(pkg);
+ Representative packageRepresentative = representatives.get(key);
if (packageRepresentative != null && packageRepresentative.clazz == clazz) {
return packageRepresentative;
}
return new Representative(clazz);
}
- private void setRepresentative(String pkg, Representative representative) {
+ private void setRepresentative(MergeGroup.Key key, Representative representative) {
assert isValidRepresentative(representative.clazz);
if (Log.ENABLED) {
- if (pkg.equals(GLOBAL)) {
+ if (key.isGlobal()) {
Log.info(
getClass(),
- "Making %s the global representative",
- representative.clazz.type.toSourceString());
+ "Making %s the global representative in group %s",
+ representative.clazz.type.toSourceString(),
+ key.getMergeGroup().toString());
} else {
Log.info(
getClass(),
- "Making %s the representative for package %s",
+ "Making %s the representative for package %s in group %s",
representative.clazz.type.toSourceString(),
- pkg);
+ key.getPackageOrGlobal(),
+ key.getMergeGroup().toString());
}
}
- representatives.put(pkg, representative);
+ representatives.put(key, representative);
}
- private void clearRepresentative(String pkg) {
+ private void clearRepresentative(MergeGroup.Key key) {
if (Log.ENABLED) {
- if (pkg.equals(GLOBAL)) {
+ if (key.isGlobal()) {
Log.info(getClass(), "Removing the global representative");
} else {
- Log.info(getClass(), "Removing the representative for package %s", pkg);
+ Log.info(
+ getClass(), "Removing the representative for package %s", key.getPackageOrGlobal());
}
}
- representatives.remove(pkg);
+ representatives.remove(key);
}
private boolean mayMergeAcrossPackageBoundaries(DexProgramClass clazz) {
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 1d57ddf..6cf0da0 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -8,8 +8,10 @@
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.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.KeyedDexItem;
import com.android.tools.r8.graph.PresortedComparable;
@@ -38,7 +40,9 @@
this.application = application;
this.appInfo = appInfo;
this.options = options;
- this.usagePrinter = options.proguardConfiguration.isPrintUsage()
+ this.usagePrinter =
+ options.getProguardConfiguration() != null
+ && options.getProguardConfiguration().isPrintUsage()
? new UsagePrinter() : UsagePrinter.DONT_PRINT;
}
@@ -100,15 +104,44 @@
clazz.setInstanceFields(reachableFields(clazz.instanceFields()));
clazz.setStaticFields(reachableFields(clazz.staticFields()));
clazz.removeInnerClasses(this::isAttributeReferencingPrunedType);
+ clazz.removeEnclosingMethod(this::isAttributeReferencingPrunedItem);
usagePrinter.visited();
}
}
return newClasses;
}
+ private boolean isAttributeReferencingPrunedItem(EnclosingMethodAttribute attr) {
+ return
+ (attr.getEnclosingClass() != null
+ && !appInfo.liveTypes.contains(attr.getEnclosingClass()))
+ || (attr.getEnclosingMethod() != null
+ && !appInfo.liveMethods.contains(attr.getEnclosingMethod()));
+ }
+
private boolean isAttributeReferencingPrunedType(InnerClassAttribute attr) {
- return (attr.getInner() != null && !appInfo.liveTypes.contains(attr.getInner()))
- || (attr.getOuter() != null && !appInfo.liveTypes.contains(attr.getOuter()));
+ if (!appInfo.liveTypes.contains(attr.getInner())) {
+ return true;
+ }
+ DexType context = attr.getOuter();
+ if (context == null) {
+ DexClass inner = appInfo.definitionFor(attr.getInner());
+ if (inner != null && inner.getEnclosingMethod() != null) {
+ EnclosingMethodAttribute enclosingMethodAttribute = inner.getEnclosingMethod();
+ if (enclosingMethodAttribute.getEnclosingClass() != null) {
+ context = enclosingMethodAttribute.getEnclosingClass();
+ } else {
+ DexMethod enclosingMethod = enclosingMethodAttribute.getEnclosingMethod();
+ if (!appInfo.liveMethods.contains(enclosingMethod)) {
+ // EnclosingMethodAttribute will be pruned as it references the pruned method.
+ // Hence, removal of the current InnerClassAttribute too.
+ return true;
+ }
+ context = enclosingMethod.getHolder();
+ }
+ }
+ }
+ return context == null || !appInfo.liveTypes.contains(context);
}
private <S extends PresortedComparable<S>, T extends KeyedDexItem<S>> int firstUnreachableIndex(
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 77e0b40..45ca1af 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -225,12 +225,15 @@
// All the bridge methods that have been synthesized during vertical class merging.
private final List<SynthesizedBridgeCode> synthesizedBridges = new ArrayList<>();
+ private final MainDexClasses mainDexClasses;
+
public VerticalClassMerger(
DexApplication application,
AppView<AppInfoWithLiveness> appView,
ExecutorService executorService,
InternalOptions options,
- Timing timing) {
+ Timing timing,
+ MainDexClasses mainDexClasses) {
this.application = application;
this.appInfo = appView.appInfo();
this.appView = appView;
@@ -240,6 +243,7 @@
this.options = options;
this.renamedMembersLense = new VerticalClassMergerGraphLense.Builder();
this.timing = timing;
+ this.mainDexClasses = mainDexClasses;
Iterable<DexProgramClass> classes = application.classesWithDeterministicOrder();
initializePinnedTypes(classes); // Must be initialized prior to mergeCandidates.
@@ -604,28 +608,67 @@
timing.begin("fixup");
GraphLense result = new TreeFixer().fixupTypeReferences(mergingGraphLense);
timing.end();
- assert result.assertDefinitionNotModified(
- appInfo
- .alwaysInline
- .stream()
+ assert result.assertDefinitionsNotModified(
+ appInfo.alwaysInline.stream()
.map(appInfo::definitionFor)
.filter(Objects::nonNull)
.collect(Collectors.toList()));
- assert result.assertDefinitionNotModified(appInfo.noSideEffects.keySet());
- // TODO(christofferqa): Enable this assert.
- // assert result.assertNotModified(appInfo.pinnedItems);
- assert verifyGraphLense(graphLense);
+ assert verifyGraphLense(result);
return result;
}
private boolean verifyGraphLense(GraphLense graphLense) {
+ assert graphLense.assertDefinitionsNotModified(appInfo.noSideEffects.keySet());
+
+ // Note that the method assertReferencesNotModified() relies on getRenamedFieldSignature() and
+ // getRenamedMethodSignature() instead of lookupField() and lookupMethod(). This is important
+ // for this check to succeed, since it is not guaranteed that calling lookupMethod() with a
+ // pinned method will return the method itself.
+ //
+ // Consider the following example.
+ //
+ // class A {
+ // public void method() {}
+ // }
+ // class B extends A {
+ // @Override
+ // public void method() {}
+ // }
+ // class C extends B {
+ // @Override
+ // public void method() {}
+ // }
+ //
+ // If A.method() is pinned, then A cannot be merged into B, but B can still be merged into C.
+ // Now, if there is an invoke-super instruction in C that hits B.method(), then this needs to
+ // be rewritten into an invoke-direct instruction. In particular, there could be an instruction
+ // `invoke-super A.method` in C. This would hit B.method(). Therefore, the graph lens records
+ // that `invoke-super A.method` instructions, which are in one of the methods from C, needs to
+ // be rewritten to `invoke-direct C.method$B`. This is valid even though A.method() is actually
+ // pinned, because this rewriting does not affect A.method() in any way.
+ assert graphLense.assertReferencesNotModified(appInfo.pinnedItems);
+
for (DexProgramClass clazz : appInfo.classes()) {
for (DexEncodedMethod encodedMethod : clazz.methods()) {
DexMethod method = encodedMethod.method;
DexMethod originalMethod = graphLense.getOriginalMethodSignature(method);
+ DexMethod renamedMethod = graphLense.getRenamedMethodSignature(originalMethod);
- // Must be able to map back.
- assert method == graphLense.getRenamedMethodSignature(originalMethod);
+ // Must be able to map back and forth.
+ if (encodedMethod.hasCode() && encodedMethod.getCode() instanceof SynthesizedBridgeCode) {
+ // For virtual methods, the vertical class merger creates two methods in the sub class
+ // in order to deal with invoke-super instructions (one that is private and one that is
+ // virtual). Therefore, it is not possible to go back and forth. Instead, we check that
+ // the two methods map back to the same original method, and that the original method
+ // can be mapped to the implementation method.
+ DexMethod implementationMethod =
+ ((SynthesizedBridgeCode) encodedMethod.getCode()).invocationTarget;
+ DexMethod originalImplementationMethod = graphLense.getOriginalMethodSignature(method);
+ assert originalMethod == originalImplementationMethod;
+ assert implementationMethod == renamedMethod;
+ } else {
+ assert method == renamedMethod;
+ }
// Verify that all types are up-to-date. After vertical class merging, there should be no
// more references to types that have been merged into another type.
@@ -699,6 +742,10 @@
return false;
}
+ private boolean hasReferencesOutside(DexProgramClass clazz, Set<DexType> types) {
+ return MainDexDirectReferenceTracer.hasReferencesOutside(appInfo, clazz, types);
+ }
+
private void mergeClassIfPossible(DexProgramClass clazz) {
if (!mergeCandidates.contains(clazz)) {
return;
@@ -730,6 +777,23 @@
return;
}
+ // For a main dex class in the dependent set only merge with other classes in either main dex
+ // set.
+ if ((mainDexClasses.getDependencies().contains(clazz.type)
+ || mainDexClasses.getDependencies().contains(targetClass.type))
+ && !(mainDexClasses.getClasses().contains(clazz.type)
+ && mainDexClasses.getClasses().contains(targetClass.type))) {
+ return;
+ }
+
+ // For a main dex class in the root set only merge with other classes in main dex root set.
+ if ((mainDexClasses.getRoots().contains(clazz.type)
+ || mainDexClasses.getRoots().contains(targetClass.type))
+ && !(mainDexClasses.getRoots().contains(clazz.type)
+ && mainDexClasses.getRoots().contains(targetClass.type))) {
+ return;
+ }
+
ClassMerger merger = new ClassMerger(clazz, targetClass);
boolean merged;
try {
@@ -1594,7 +1658,7 @@
ConstraintWithTarget constraint =
jarCode.computeInliningConstraint(
method,
- appInfo,
+ appView,
new SingleTypeMapperGraphLense(method.method.holder, invocationContext),
invocationContext);
return constraint == ConstraintWithTarget.NEVER;
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index 6b66465..20a05ab 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -222,16 +222,32 @@
Map<DexType, DexType> mergedClasses,
DexItemFactory dexItemFactory,
Map<DexProto, DexProto> cache) {
+ assert !signature.holder.isArrayType();
DexType newHolder = mergedClasses.getOrDefault(signature.holder, signature.holder);
DexProto newProto =
dexItemFactory.applyClassMappingToProto(
- signature.proto, type -> mergedClasses.getOrDefault(type, type), cache);
+ signature.proto,
+ type -> getTypeAfterClassMerging(type, mergedClasses, dexItemFactory),
+ cache);
if (signature.holder.equals(newHolder) && signature.proto.equals(newProto)) {
return signature;
}
return dexItemFactory.createMethod(newHolder, newProto, signature.name);
}
+ private static DexType getTypeAfterClassMerging(
+ DexType type, Map<DexType, DexType> mergedClasses, DexItemFactory dexItemFactory) {
+ if (type.isArrayType()) {
+ DexType baseType = type.toBaseType(dexItemFactory);
+ DexType newBaseType = mergedClasses.getOrDefault(baseType, baseType);
+ if (newBaseType != baseType) {
+ return type.replaceBaseType(newBaseType, dexItemFactory);
+ }
+ return type;
+ }
+ return mergedClasses.getOrDefault(type, type);
+ }
+
public boolean hasMappingForSignatureInContext(DexType context, DexMethod signature) {
Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap =
contextualVirtualToDirectMethodMaps.get(context);
diff --git a/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
index e2fc31e..0572e88 100644
--- a/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
+++ b/src/main/java/com/android/tools/r8/shaking/WhyAreYouKeepingConsumer.java
@@ -4,36 +4,43 @@
package com.android.tools.r8.shaking;
import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graphinfo.ClassGraphNode;
-import com.android.tools.r8.graphinfo.FieldGraphNode;
-import com.android.tools.r8.graphinfo.GraphConsumer;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo;
-import com.android.tools.r8.graphinfo.GraphEdgeInfo.EdgeKind;
-import com.android.tools.r8.graphinfo.GraphNode;
-import com.android.tools.r8.graphinfo.KeepRuleGraphNode;
-import com.android.tools.r8.graphinfo.MethodGraphNode;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
+import com.android.tools.r8.experimental.graphinfo.FieldGraphNode;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo.EdgeKind;
+import com.android.tools.r8.experimental.graphinfo.GraphNode;
+import com.android.tools.r8.experimental.graphinfo.KeepRuleGraphNode;
+import com.android.tools.r8.experimental.graphinfo.MethodGraphNode;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
import com.android.tools.r8.position.TextPosition;
import com.android.tools.r8.position.TextRange;
import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.StringUtils.BraceType;
import java.io.PrintStream;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Deque;
-import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
-public class WhyAreYouKeepingConsumer implements GraphConsumer {
+/**
+ * Implementation of the whyareyoukeeping rule using an R8 kept-graph consumer.
+ *
+ * <p>This class is not intended for public use and clients should define their own consumer
+ * instead.
+ */
+public class WhyAreYouKeepingConsumer extends CollectingGraphConsumer {
// Single-linked path description when BF searching for a path.
private static class GraphPath {
@@ -47,37 +54,50 @@
}
}
- // Possible sub-consumer that is also inspecting the kept-graph.
- private final GraphConsumer subConsumer;
-
- // Directional map backwards from targets to direct sources.
- private final Map<GraphNode, Map<GraphNode, Set<GraphEdgeInfo>>> target2sources =
- new IdentityHashMap<>();
-
public WhyAreYouKeepingConsumer(GraphConsumer subConsumer) {
- this.subConsumer = subConsumer;
+ super(subConsumer);
}
- @Override
- public void acceptEdge(GraphNode source, GraphNode target, GraphEdgeInfo info) {
- target2sources
- .computeIfAbsent(target, k -> new IdentityHashMap<>())
- .computeIfAbsent(source, k -> new HashSet<>())
- .add(info);
- if (subConsumer != null) {
- subConsumer.acceptEdge(source, target, info);
- }
- }
-
- /** Print the shortest path from a root to a node in the graph. */
- public void printWhyAreYouKeeping(ClassReference clazz, PrintStream out) {
- for (GraphNode node : target2sources.keySet()) {
- if (node.identity().equals(clazz.toDescriptor())) {
- printWhyAreYouKeeping(node, out);
- return;
+ public ClassGraphNode getClassNode(ClassReference clazz) {
+ for (GraphNode node : getTargets()) {
+ if (node instanceof ClassGraphNode && ((ClassGraphNode) node).getReference() == clazz) {
+ return (ClassGraphNode) node;
}
}
- printNothingKeeping(clazz, out);
+ return null;
+ }
+
+ public MethodGraphNode getMethodNode(MethodReference method) {
+ for (GraphNode node : getTargets()) {
+ if (node instanceof MethodGraphNode && ((MethodGraphNode) node).getReference() == method) {
+ return (MethodGraphNode) node;
+ }
+ }
+ return null;
+ }
+
+ public FieldGraphNode getFieldNode(FieldReference field) {
+ for (GraphNode node : getTargets()) {
+ if (node instanceof FieldGraphNode && ((FieldGraphNode) node).getReference() == field) {
+ return (FieldGraphNode) node;
+ }
+ }
+ return null;
+ }
+
+ public void printWhyAreYouKeeping(ClassReference reference, PrintStream out) {
+ ClassGraphNode node = getClassNode(reference);
+ printWhyAreYouKeeping(node != null ? node : new ClassGraphNode(false, reference), out);
+ }
+
+ public void printWhyAreYouKeeping(MethodReference reference, PrintStream out) {
+ MethodGraphNode node = getMethodNode(reference);
+ printWhyAreYouKeeping(node != null ? node : new MethodGraphNode(false, reference), out);
+ }
+
+ public void printWhyAreYouKeeping(FieldReference reference, PrintStream out) {
+ FieldGraphNode node = getFieldNode(reference);
+ printWhyAreYouKeeping(node != null ? node : new FieldGraphNode(false, reference), out);
}
public void printWhyAreYouKeeping(GraphNode node, PrintStream out) {
@@ -102,13 +122,16 @@
private void printNothingKeeping(ClassReference clazz, PrintStream out) {
out.print("Nothing is keeping ");
- out.println(DescriptorUtils.descriptorToJavaType(clazz.toDescriptor()));
+ out.println(DescriptorUtils.descriptorToJavaType(clazz.getDescriptor()));
}
- private List<Pair<GraphNode, GraphEdgeInfo>> findShortestPathTo(final GraphNode node) {
+ public List<Pair<GraphNode, GraphEdgeInfo>> findShortestPathTo(final GraphNode node) {
+ if (node == null) {
+ return null;
+ }
Deque<GraphPath> queue;
{
- Map<GraphNode, Set<GraphEdgeInfo>> sources = target2sources.get(node);
+ Map<GraphNode, Set<GraphEdgeInfo>> sources = getSourcesTargeting(node);
if (sources == null) {
// The node is not targeted at all (it is not reachable).
return null;
@@ -121,7 +144,7 @@
Map<GraphNode, GraphNode> seen = new IdentityHashMap<>();
while (!queue.isEmpty()) {
GraphPath path = queue.removeFirst();
- Map<GraphNode, Set<GraphEdgeInfo>> sources = target2sources.get(path.node);
+ Map<GraphNode, Set<GraphEdgeInfo>> sources = getSourcesTargeting(path.node);
if (sources == null) {
return getCanonicalPath(path, node);
}
@@ -144,11 +167,11 @@
while (path.path != null) {
GraphNode source = path.node;
GraphNode target = path.path.node;
- Set<GraphEdgeInfo> infos = target2sources.get(target).get(source);
+ Set<GraphEdgeInfo> infos = getSourcesTargeting(target).get(source);
canonical.add(new Pair<>(source, getCanonicalInfo(infos)));
path = path.path;
}
- Set<GraphEdgeInfo> infos = target2sources.get(endTarget).get(path.node);
+ Set<GraphEdgeInfo> infos = getSourcesTargeting(endTarget).get(path.node);
canonical.add(new Pair<>(path.node, getCanonicalInfo(infos)));
return canonical;
}
@@ -201,27 +224,28 @@
private String getNodeString(GraphNode node) {
if (node instanceof ClassGraphNode) {
- return DescriptorUtils.descriptorToJavaType(((ClassGraphNode) node).getDescriptor());
+ return DescriptorUtils.descriptorToJavaType(
+ ((ClassGraphNode) node).getReference().getDescriptor());
}
if (node instanceof MethodGraphNode) {
- MethodGraphNode methodNode = (MethodGraphNode) node;
- MethodSignature signature =
- MethodSignature.fromSignature(
- methodNode.getMethodName(), methodNode.getMethodDescriptor());
- return signature.type
+ MethodReference method = ((MethodGraphNode) node).getReference();
+ return (method.getReturnType() == null ? "void" : method.getReturnType().getTypeName())
+ ' '
- + DescriptorUtils.descriptorToJavaType(methodNode.getHolderDescriptor())
+ + method.getHolderClass().getTypeName()
+ '.'
- + methodNode.getMethodName()
- + StringUtils.join(Arrays.asList(signature.parameters), ",", BraceType.PARENS);
+ + method.getMethodName()
+ + StringUtils.join(
+ ListUtils.map(method.getFormalTypes(), TypeReference::getTypeName),
+ ",",
+ BraceType.PARENS);
}
if (node instanceof FieldGraphNode) {
- FieldGraphNode fieldNode = (FieldGraphNode) node;
- return DescriptorUtils.descriptorToJavaType(fieldNode.getFieldDescriptor())
+ FieldReference field = ((FieldGraphNode) node).getReference();
+ return field.getFieldType().getTypeName()
+ ' '
- + DescriptorUtils.descriptorToJavaType(fieldNode.getHolderDescriptor())
+ + field.getHolderClass().getTypeName()
+ '.'
- + fieldNode.getFieldName();
+ + field.getFieldName();
}
if (node instanceof KeepRuleGraphNode) {
KeepRuleGraphNode keepRuleNode = (KeepRuleGraphNode) node;
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index 976768b..c059819 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -4,7 +4,10 @@
package com.android.tools.r8.utils;
import java.lang.reflect.Array;
+import java.util.ArrayList;
import java.util.Map;
+import java.util.function.Function;
+import java.util.function.Predicate;
public class ArrayUtils {
@@ -33,4 +36,74 @@
return results;
}
+ /**
+ * Filters the input array based on the given predicate.
+ *
+ * @param clazz target type's Class to cast
+ * @param original an array of original elements
+ * @param filter a predicate that tells us what to keep
+ * @param <T> target type
+ * @return a partial copy of the original array
+ */
+ public static <T> T[] filter(Class<T[]> clazz, T[] original, Predicate<T> filter) {
+ ArrayList<T> filtered = null;
+ for (int i = 0; i < original.length; i++) {
+ T elt = original[i];
+ if (filter.test(elt)) {
+ if (filtered != null) {
+ filtered.add(elt);
+ }
+ } else {
+ if (filtered == null) {
+ filtered = new ArrayList<>(original.length);
+ for (int j = 0; j < i; j++) {
+ filtered.add(original[j]);
+ }
+ }
+ }
+ }
+ if (filtered == null) {
+ return original;
+ }
+ return filtered.toArray(
+ clazz.cast(Array.newInstance(clazz.getComponentType(), filtered.size())));
+ }
+
+ /**
+ * Rewrites the input array based on the given function.
+ *
+ * @param clazz target type's Class to cast
+ * @param original an array of original elements
+ * @param mapper a mapper that rewrites an original element to a new one, maybe `null`
+ * @param <T> target type
+ * @return an array with written elements
+ */
+ public static <T> T[] map(Class<T[]> clazz, T[] original, Function<T, T> mapper) {
+ ArrayList<T> results = null;
+ for (int i = 0; i < original.length; i++) {
+ T oldOne = original[i];
+ T newOne = mapper.apply(oldOne);
+ if (newOne == oldOne) {
+ if (results != null) {
+ results.add(oldOne);
+ }
+ } else {
+ if (results == null) {
+ results = new ArrayList<>(original.length);
+ for (int j = 0; j < i; j++) {
+ results.add(original[j]);
+ }
+ }
+ if (newOne != null) {
+ results.add(newOne);
+ }
+ }
+ }
+ if (results == null) {
+ return original;
+ }
+ return results.toArray(
+ clazz.cast(Array.newInstance(clazz.getComponentType(), results.size())));
+ }
+
}
diff --git a/src/main/java/com/android/tools/r8/utils/ClassMap.java b/src/main/java/com/android/tools/r8/utils/ClassMap.java
index 0ebfb9d..33c1bf3 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassMap.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassMap.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.ClassKind;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Iterator;
@@ -134,6 +135,18 @@
return loadedClasses;
}
+ public Map<DexType, DexClass> getAllClassesInMap() {
+ if (classProvider.get() != null) {
+ throw new Unreachable("Getting all classes from not fully loaded collection.");
+ }
+ ImmutableMap.Builder<DexType, DexClass> builder = ImmutableMap.builder();
+ // This is fully loaded, so the class map will no longer change.
+ for (Map.Entry<DexType, Supplier<T>> entry : classes.entrySet()) {
+ builder.put(entry.getKey(), entry.getValue().get());
+ }
+ return builder.build();
+ }
+
public Iterable<DexType> getAllTypes() {
return classes.keySet();
}
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 8ae156e..90b9e18 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -14,11 +14,11 @@
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.InvalidDebugInfoException;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graphinfo.GraphConsumer;
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -47,7 +47,12 @@
}
public final DexItemFactory itemFactory;
- public final ProguardConfiguration proguardConfiguration;
+
+ public ProguardConfiguration getProguardConfiguration() {
+ return proguardConfiguration;
+ }
+
+ private final ProguardConfiguration proguardConfiguration;
public final Reporter reporter;
// TODO(zerny): Make this private-final once we have full program-consumer support.
@@ -60,7 +65,7 @@
public InternalOptions() {
reporter = new Reporter();
itemFactory = new DexItemFactory();
- proguardConfiguration = ProguardConfiguration.defaultConfiguration(itemFactory, reporter);
+ proguardConfiguration = null;
}
// Constructor for D8.
@@ -69,7 +74,7 @@
assert factory != null;
this.reporter = reporter;
itemFactory = factory;
- proguardConfiguration = ProguardConfiguration.defaultConfiguration(itemFactory, reporter);
+ proguardConfiguration = null;
}
// Constructor for R8.
@@ -522,6 +527,7 @@
public boolean nondeterministicCycleElimination = false;
public Set<Inliner.Reason> validInliningReasons = null;
public boolean allowFailureOnInnerClassErrors = false;
+ public boolean noLocalsTableOnInput = false;
public boolean forceNameReflectionOptimization = false;
}
@@ -809,7 +815,7 @@
//
// We used to insert a empty loop at the end, however, mediatek has an optimizer
// on lollipop devices that cannot deal with an unreachable infinite loop, so we
- // couldn't do that. See b/119895393.
+ // couldn't do that. See b/119895393.2
public boolean canHaveTracingPastInstructionsStreamBug() {
return minApiLevel < AndroidApiLevel.L.getLevel();
}
@@ -841,4 +847,27 @@
// TODO(ager): Update this with an actual bound when the issue has been fixed.
return true;
}
+
+ // Some Art Lollipop version do not deal correctly with long-to-int conversions.
+ //
+ // In particular, the following code performs an out of bounds array access when the
+ // long loaded from the long array is very large (has non-zero values in the upper 32 bits).
+ //
+ // aget-wide v9, v3, v1
+ // long-to-int v9, v9
+ // aget-wide v10, v3, v9
+ //
+ // The issue seems to be that the higher bits of the 64-bit register holding the long
+ // are not cleared and the integer is therefore a 64-bit integer that is not truncated
+ // to 32 bits.
+ //
+ // As a workaround, we do not allow long-to-int to have the same source and target register
+ // for min-apis where lollipop devices could be targeted.
+ //
+ // See b/80262475.
+ public boolean canHaveLongToIntBug() {
+ // We have only seen this happening on Lollipop arm64 backends. We have tested on
+ // Marshmallow and Nougat arm64 devices and they do not have the bug.
+ return minApiLevel < AndroidApiLevel.M.getLevel();
+ }
}
diff --git a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
index 7bce10a..fc18db1 100644
--- a/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
+++ b/src/test/apiUsageSample/com/android/tools/apiusagesample/R8ApiUsageSample.java
@@ -124,6 +124,8 @@
R8Command.builder(handler)
.setMode(mode)
.setMinApiLevel(minApiLevel)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addProgramFiles(inputs)
@@ -140,6 +142,8 @@
R8Command.Builder builder =
R8Command.builder(handler)
.setMinApiLevel(minApiLevel)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries);
for (ClassFileContent classfile : readClassFiles(inputs)) {
@@ -160,6 +164,8 @@
R8Command.Builder builder =
R8Command.builder(handler)
.setMinApiLevel(minApiLevel)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries);
for (Path input : inputs) {
@@ -189,6 +195,8 @@
R8Command.Builder builder =
R8Command.builder(handler)
.setMinApiLevel(minApiLevel)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.setProgramConsumer(new EnsureOutputConsumer())
.addProgramFiles(inputs);
for (Path library : libraries) {
@@ -211,6 +219,8 @@
R8.run(
R8Command.builder(handler)
.setMinApiLevel(minApiLevel)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addProgramFiles(inputs)
@@ -240,6 +250,8 @@
R8.run(
R8Command.builder(handler)
.setMinApiLevel(minApiLevel)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addProgramFiles(inputs)
@@ -261,6 +273,8 @@
R8.run(
R8Command.builder(handler)
.setMinApiLevel(minApiLevel)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addProgramFiles(inputs)
@@ -280,6 +294,8 @@
R8Command.Builder builder =
R8Command.builder(handler)
.setMinApiLevel(minApiLevel)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.setProgramConsumer(new EnsureOutputConsumer())
.addLibraryFiles(libraries)
.addProgramFiles(inputs);
diff --git a/src/test/java/com/android/tools/r8/AsmTestBase.java b/src/test/java/com/android/tools/r8/AsmTestBase.java
index 2c4cb6b..34c05d1 100644
--- a/src/test/java/com/android/tools/r8/AsmTestBase.java
+++ b/src/test/java/com/android/tools/r8/AsmTestBase.java
@@ -76,15 +76,16 @@
ProcessResult javaResult =
useJavaNoVerify ? runOnJavaRawNoVerify(main, classes) : runOnJavaRaw(main, classes);
ProcessResult d8Result = runOnArtRaw(compileWithD8(app), main);
- ProcessResult r8Result = runOnArtRaw(compileWithR8(app), main);
+ ProcessResult r8NonShakenResult =
+ runOnArtRaw(compileWithR8(app, "-dontshrink\n-dontobfuscate\n"), main);
ProcessResult r8ShakenResult = runOnArtRaw(
compileWithR8(app, keepMainProguardConfiguration(main) + "-dontobfuscate\n"), main);
Assert.assertEquals(javaResult.stdout, d8Result.stdout);
- Assert.assertEquals(javaResult.stdout, r8Result.stdout);
+ Assert.assertEquals(javaResult.stdout, r8NonShakenResult.stdout);
Assert.assertEquals(javaResult.stdout, r8ShakenResult.stdout);
Assert.assertEquals(0, javaResult.exitCode);
Assert.assertEquals(0, d8Result.exitCode);
- Assert.assertEquals(0, r8Result.exitCode);
+ Assert.assertEquals(0, r8NonShakenResult.exitCode);
Assert.assertEquals(0, r8ShakenResult.exitCode);
}
@@ -95,7 +96,7 @@
CompilationFailedException r8Error = null;
CompilationFailedException r8ShakenError = null;
try {
- runOnArtRaw(compileWithR8(app), main);
+ runOnArtRaw(compileWithR8(app, "-dontshrink\n-dontobfuscate\n"), main);
} catch (CompilationFailedException e) {
r8Error = e;
}
diff --git a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
index 6f40632..2253f1f 100644
--- a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
@@ -7,6 +7,8 @@
import static com.google.common.io.ByteStreams.toByteArray;
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.origin.Origin;
+import com.google.common.collect.ImmutableList;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Path;
@@ -305,6 +307,9 @@
.addProgramFiles(inputJar)
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
.setOutput(outputJar, OutputMode.ClassFile)
.build();
ToolHelper.runR8(
diff --git a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
index 6d0ef5a..c5e44a3 100644
--- a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
+++ b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
@@ -49,13 +49,16 @@
Path mainDexList = temp.getRoot().toPath().resolve("maindexlist.txt");
FileUtils.writeTextFile(mainDexList, "desugaringwithmissingclasstest1/Main.class");
+ // It is important to place the api usage sample jar after the current classpath because we want
+ // to find D8/R8 classes before the ones in the jar, otherwise renamed classes and fields cannot
+ // be found.
+ String classPath = System.getProperty("java.class.path") + File.pathSeparator + jar.toString();
List<String> command =
ImmutableList.<String>builder()
.addAll(
ImmutableList.of(
ToolHelper.getJavaExecutable(),
- "-cp",
- jar.toString() + File.pathSeparator + System.getProperty("java.class.path"),
+ "-cp", classPath,
main,
// Compiler arguments.
"--output",
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index d8d983f..35dafae 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -283,7 +283,7 @@
.put(
"lang.ClassLoader.getSystemResourceAsStreamLjava_lang_String.ClassLoader_getSystemResourceAsStream_A01",
anyDexVm())
- .put("lang.ClassLoader.getPackages.ClassLoader_getPackages_A01", any())
+ .put("lang.ClassLoader.getPackages.ClassLoader_getPackages_A01", anyDexVm())
.put(
"lang.ClassLoader.setClassAssertionStatusLjava_lang_StringZ.ClassLoader_setClassAssertionStatus_A01",
any())
diff --git a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
index 109b42d..0743f18 100644
--- a/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ProguardTestBuilder.java
@@ -202,6 +202,18 @@
}
@Override
+ public ProguardTestBuilder addKeepRuleFiles(List<Path> files) {
+ try {
+ for (Path file : files) {
+ config.addAll(FileUtils.readAllLines(file));
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return self();
+ }
+
+ @Override
public ProguardTestBuilder addKeepRules(Collection<String> rules) {
config.addAll(rules);
return self();
diff --git a/src/test/java/com/android/tools/r8/R8CFExamplesTests.java b/src/test/java/com/android/tools/r8/R8CFExamplesTests.java
index bb88036..0b464c7 100644
--- a/src/test/java/com/android/tools/r8/R8CFExamplesTests.java
+++ b/src/test/java/com/android/tools/r8/R8CFExamplesTests.java
@@ -104,6 +104,8 @@
R8Command.builder()
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setMode(mode)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.addProgramFiles(inputJar)
.setOutput(outputJar, OutputMode.ClassFile)
.build(),
diff --git a/src/test/java/com/android/tools/r8/R8CodeCanonicalizationTest.java b/src/test/java/com/android/tools/r8/R8CodeCanonicalizationTest.java
index 405994c..65aea6e 100644
--- a/src/test/java/com/android/tools/r8/R8CodeCanonicalizationTest.java
+++ b/src/test/java/com/android/tools/r8/R8CodeCanonicalizationTest.java
@@ -36,6 +36,8 @@
public void testNumberOfCodeItemsUnchanged() throws Exception {
int numberOfCodes = readNumberOfCodes(SOURCE_DEX);
R8Command.Builder builder = R8Command.builder()
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.addLibraryFiles(ToolHelper.getDefaultAndroidJar())
.setOutput(temp.getRoot().toPath(), OutputMode.DexIndexed);
ToolHelper.getAppBuilder(builder).addProgramFiles(SOURCE_DEX);
diff --git a/src/test/java/com/android/tools/r8/R8CommandTest.java b/src/test/java/com/android/tools/r8/R8CommandTest.java
index 518d26f..5331ca9 100644
--- a/src/test/java/com/android/tools/r8/R8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/R8CommandTest.java
@@ -64,8 +64,8 @@
private void verifyEmptyCommand(R8Command command) throws Throwable {
assertEquals(0, ToolHelper.getApp(command).getDexProgramResourcesForTesting().size());
assertEquals(0, ToolHelper.getApp(command).getClassProgramResourcesForTesting().size());
- assertFalse(command.getEnableMinification());
- assertFalse(command.getEnableTreeShaking());
+ assertTrue(command.getEnableMinification());
+ assertTrue(command.getEnableTreeShaking());
assertEquals(CompilationMode.RELEASE, command.getMode());
assertTrue(command.getProgramConsumer() instanceof DexIndexedConsumer);
}
@@ -93,7 +93,12 @@
Path output = working.resolve("classes.dex");
assertFalse(Files.exists(output));
ProcessResult result =
- ToolHelper.forkR8(working, input.toString(), "--lib", library.toAbsolutePath().toString());
+ ToolHelper.forkR8(
+ working,
+ input.toString(),
+ "--lib",
+ library.toAbsolutePath().toString(),
+ "--no-tree-shaking");
assertEquals("R8 run failed: " + result.stderr, 0, result.exitCode);
assertTrue(Files.exists(output));
}
@@ -113,6 +118,7 @@
"24",
"--lib",
library.toAbsolutePath().toString(),
+ "--no-tree-shaking",
input.toString());
assertEquals(0, ToolHelper.forkR8(working, "@flags.txt").exitCode);
assertTrue(Files.exists(output));
@@ -281,6 +287,8 @@
ProcessResult result =
ToolHelper.forkR8(
Paths.get("."),
+ "--no-tree-shaking",
+ "--no-minification",
input.toString(),
"--output",
existingDir.toString(),
@@ -552,7 +560,7 @@
@Test
public void noTreeShakingOption() throws Throwable {
// Default "keep all" rule implies no tree shaking.
- assertFalse(parse().getEnableTreeShaking());
+ assertTrue(parse().getEnableTreeShaking());
assertFalse(parse("--no-tree-shaking").getEnableTreeShaking());
// With a Proguard configuration --no-tree-shaking takes effect.
@@ -565,7 +573,7 @@
@Test
public void noMinificationOption() throws Throwable {
// Default "keep all" rule implies no tree minification.
- assertFalse(parse().getEnableMinification());
+ assertTrue(parse().getEnableMinification());
assertFalse(parse("--no-minification").getEnableMinification());
// With a Proguard configuration --no-tree-shaking takes effect.
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 0634d6f..0642d44 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -1576,6 +1576,10 @@
R8Command.Builder r8builder =
R8Command.builder()
.setMode(mode)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(
+ ImmutableList.of("-keepattributes *"), Origin.unknown())
.setProgramConsumer(
new ClassFileConsumer() {
@@ -1635,6 +1639,9 @@
R8Command.Builder builder =
R8Command.builder()
.setMode(mode)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
.setOutput(
Paths.get(resultPath),
cfBackend ? OutputMode.ClassFile : OutputMode.DexIndexed);
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidNTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidNTest.java
index 8d89f42..db48866 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidNTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidNTest.java
@@ -4,6 +4,8 @@
package com.android.tools.r8;
+import com.android.tools.r8.origin.Origin;
+import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.function.UnaryOperator;
@@ -21,6 +23,15 @@
}
@Override
+ TestRunner withKeepAll() {
+ return withBuilderTransformation(builder ->
+ builder
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown()));
+ }
+
+ @Override
void build(Path inputFile, Path out) throws Throwable {
R8Command.Builder builder = R8Command.builder();
for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 0b66e5a..003607b 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -210,12 +210,21 @@
return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel.getLevel()));
}
+ @Override R8TestRunner withKeepAll() {
+ return withBuilderTransformation(builder ->
+ builder
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown()));
+ }
+
@Override
void build(Path inputFile, Path out, OutputMode mode) throws Throwable {
R8Command.Builder builder = R8Command.builder().setOutput(out, mode);
for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
builder = transformation.apply(builder);
}
+
builder.addLibraryFiles(ToolHelper.getAndroidJar(
androidJarVersion == null ? builder.getMinApiLevel() : androidJarVersion.getLevel()));
R8Command command = builder.addProgramFiles(inputFile).build();
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
index 7d1a088..9ac01a0 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
@@ -5,6 +5,7 @@
package com.android.tools.r8;
import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -70,6 +71,15 @@
}
@Override
+ R8TestRunner withKeepAll() {
+ return withBuilderTransformation(builder ->
+ builder
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown()));
+ }
+
+ @Override
void build(Path inputFile, Path out) throws Throwable {
R8Command.Builder builder = R8Command.builder();
for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
index 428c611..195e01c 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesCommon.java
@@ -12,8 +12,10 @@
import com.android.tools.r8.R8RunArtTestsTest.DexTool;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.android.tools.r8.utils.TestDescriptionWatcher;
+import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
@@ -168,6 +170,9 @@
: ToolHelper.getDefaultAndroidJar())
.setOutput(getOutputFile(), outputMode)
.setMode(mode)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
.build();
ToolHelper.runR8(
command,
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
index 670e6be..11035f5 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
@@ -4,7 +4,9 @@
package com.android.tools.r8;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.function.UnaryOperator;
@@ -22,6 +24,15 @@
}
@Override
+ R8TestRunner withKeepAll() {
+ return withBuilderTransformation(builder ->
+ builder
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown()));
+ }
+
+ @Override
void build(Path inputFile, Path out) throws Throwable {
R8Command.Builder builder = R8Command.builder();
for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
diff --git a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
index 11ad12b..0d21319 100644
--- a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.TestDescriptionWatcher;
import com.google.common.collect.ImmutableList;
@@ -165,8 +166,9 @@
Path originalDexFile = Paths.get(SMALI_DIR, directoryName, dexFileName);
String outputPath = temp.getRoot().getCanonicalPath();
R8Command.Builder builder = R8Command.builder()
+ .addProguardConfiguration(ImmutableList.of("-keep class * { *; }"), Origin.unknown())
.addLibraryFiles(ToolHelper.getDefaultAndroidJar())
- .setOutput(Paths.get(outputPath), OutputMode.DexIndexed);
+ .setOutput(Paths.get(outputPath), OutputMode.DexIndexed);
ToolHelper.getAppBuilder(builder).addProgramFiles(originalDexFile);
if (failingOnX8.contains(directoryName)) {
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 8c4f682..2c72ec7 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -6,10 +6,11 @@
import com.android.tools.r8.R8Command.Builder;
import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.TestBase.R8Mode;
-import com.android.tools.r8.graphinfo.GraphConsumer;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -74,6 +75,12 @@
}
@Override
+ public R8TestBuilder addKeepRuleFiles(List<Path> files) {
+ builder.addProguardConfigurationFiles(files);
+ return self();
+ }
+
+ @Override
public R8TestBuilder addKeepRules(Collection<String> rules) {
builder.addProguardConfiguration(new ArrayList<>(rules), Origin.unknown());
return self();
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
index 5b4482e..f062988 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
@@ -99,6 +99,10 @@
abstract TestRunner withMinApiLevel(int minApiLevel);
+ TestRunner withKeepAll() {
+ return this;
+ }
+
abstract void build(Path inputFile, Path out) throws Throwable;
}
@@ -126,6 +130,7 @@
test("staticinterfacemethods", "interfacemethods", "StaticInterfaceMethods")
.withMinApiLevel(AndroidApiLevel.K.getLevel())
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
+ .withKeepAll()
.run();
}
@@ -134,7 +139,8 @@
test("staticinterfacemethods-error-due-to-min-sdk", "interfacemethods",
"StaticInterfaceMethods")
.withInterfaceMethodDesugaring(OffOrAuto.Off)
- .run();
+ .withKeepAll()
+ .run();
}
@Test
@@ -142,6 +148,7 @@
test("defaultmethods", "interfacemethods", "DefaultMethods")
.withMinApiLevel(AndroidApiLevel.K.getLevel())
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
+ .withKeepAll()
.run();
}
@@ -150,6 +157,7 @@
test("defaultmethods-error-due-to-min-sdk", "interfacemethods",
"DefaultMethods")
.withInterfaceMethodDesugaring(OffOrAuto.Off)
+ .withKeepAll()
.run();
}
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 4821c25..4f98b6f 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -44,7 +44,6 @@
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
-import java.util.zip.ZipException;
import java.util.zip.ZipFile;
import org.junit.Assert;
import org.junit.Assume;
@@ -175,6 +174,10 @@
abstract C withMinApiLevel(AndroidApiLevel minApiLevel);
+ C withKeepAll() {
+ return self();
+ }
+
C withAndroidJar(AndroidApiLevel androidJarVersion) {
assert this.androidJarVersion == null;
this.androidJarVersion = androidJarVersion;
@@ -312,6 +315,7 @@
public void stringConcat() throws Throwable {
test("stringconcat", "stringconcat", "StringConcat")
.withMinApiLevel(AndroidApiLevel.K)
+ .withKeepAll()
.run();
}
@@ -319,6 +323,7 @@
public void invokeCustom() throws Throwable {
test("invokecustom", "invokecustom", "InvokeCustom")
.withMinApiLevel(AndroidApiLevel.O)
+ .withKeepAll()
.run();
}
@@ -326,6 +331,7 @@
public void invokeCustom2() throws Throwable {
test("invokecustom2", "invokecustom2", "InvokeCustom")
.withMinApiLevel(AndroidApiLevel.O)
+ .withKeepAll()
.run();
}
@@ -333,6 +339,7 @@
public void invokeCustomErrorDueToMinSdk() throws Throwable {
test("invokecustom-error-due-to-min-sdk", "invokecustom", "InvokeCustom")
.withMinApiLevel(AndroidApiLevel.N_MR1)
+ .withKeepAll()
.run();
}
@@ -340,6 +347,7 @@
public void invokePolymorphic() throws Throwable {
test("invokepolymorphic", "invokepolymorphic", "InvokePolymorphic")
.withMinApiLevel(AndroidApiLevel.O)
+ .withKeepAll()
.run();
}
@@ -347,6 +355,7 @@
public void invokePolymorphicErrorDueToMinSdk() throws Throwable {
test("invokepolymorphic-error-due-to-min-sdk", "invokepolymorphic", "InvokePolymorphic")
.withMinApiLevel(AndroidApiLevel.N_MR1)
+ .withKeepAll()
.run();
}
@@ -354,6 +363,7 @@
public void lambdaDesugaring() throws Throwable {
test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
.withMinApiLevel(AndroidApiLevel.K)
+ .withKeepAll()
.run();
}
@@ -362,6 +372,7 @@
test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
.withMinApiLevel(AndroidApiLevel.K)
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
+ .withKeepAll()
.run();
}
@@ -371,6 +382,7 @@
.withMinApiLevel(AndroidApiLevel.K)
.withAndroidJar(AndroidApiLevel.O)
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
+ .withKeepAll()
.run();
}
@@ -380,6 +392,7 @@
.withMinApiLevel(AndroidApiLevel.K)
.withAndroidJar(AndroidApiLevel.O)
.withInterfaceMethodDesugaring(OffOrAuto.Auto)
+ .withKeepAll()
.run();
}
@@ -387,6 +400,7 @@
public void lambdaDesugaringValueAdjustments() throws Throwable {
test("lambdadesugaring-value-adjustments", "lambdadesugaring", "ValueAdjustments")
.withMinApiLevel(AndroidApiLevel.K)
+ .withKeepAll()
.run();
}
@@ -394,6 +408,7 @@
public void paramNames() throws Throwable {
test("paramnames", "paramnames", "ParameterNames")
.withMinApiLevel(AndroidApiLevel.O)
+ .withKeepAll()
.run();
}
@@ -410,13 +425,16 @@
// No need to specify minSdk as repeat annotations are handled by javac and we do not have
// to do anything to support them. The library methods to access them just have to be in
// the system.
- test("repeat_annotations", "repeat_annotations", "RepeatAnnotations").run();
+ test("repeat_annotations", "repeat_annotations", "RepeatAnnotations")
+ .withKeepAll()
+ .run();
}
@Test
public void testTryWithResources() throws Throwable {
test("try-with-resources-simplified", "trywithresources", "TryWithResourcesNotDesugaredTests")
.withTryWithResourcesDesugaring(OffOrAuto.Off)
+ .withKeepAll()
.run();
}
@@ -429,6 +447,7 @@
Assert.assertFalse(invoke.invokedMethod().name.toString().equals("addSuppressed"));
Assert.assertFalse(invoke.invokedMethod().name.toString().equals("getSuppressed"));
})
+ .withKeepAll()
.run();
}
@@ -477,6 +496,7 @@
.withMinApiLevel(AndroidApiLevel.K) // K to create dispatch classes
.withAndroidJar(AndroidApiLevel.O)
.withArg(String.valueOf(ToolHelper.getMinApiLevelForDexVm().getLevel() >= 24))
+ .withKeepAll()
.run();
}
@@ -507,7 +527,8 @@
.withMinApiLevel(minApi)
.withOptionConsumer(option -> option.minimalMainDex = true)
.withOptionConsumer(option -> option.enableInheritanceClassInDexDistributor = false)
- .withMainDexClass(mainDexClasses);
+ .withMainDexClass(mainDexClasses)
+ .withKeepAll();
Path fullDexes = temp.getRoot().toPath().resolve(packageName + "full" + ZIP_EXTENSION);
full.build(input, fullDexes);
@@ -564,7 +585,8 @@
.withOptionConsumer(option -> option.minimalMainDex = true)
.withOptionConsumer(option -> option.enableInheritanceClassInDexDistributor = false)
.withMainDexClass(mainDexClasses)
- .withMinApiLevel(minApi);
+ .withMinApiLevel(minApi)
+ .withKeepAll();
Path dexesThroughIntermediate =
temp.getRoot().toPath().resolve(packageName + "dex" + ZIP_EXTENSION);
@@ -581,7 +603,7 @@
}
void execute(String testName,
- String qualifiedMainClass, Path[] jars, Path[] dexes, List<String> args) throws IOException {
+ String qualifiedMainClass, Path[] jars, Path[] dexes, List<String> args) {
Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles());
boolean expectedToFail = expectedToFail(testName);
try {
@@ -614,7 +636,7 @@
}
protected CodeInspector getMainDexInspector(Path zip)
- throws ZipException, IOException, ExecutionException {
+ throws IOException, ExecutionException {
try (ZipFile zipFile = new ZipFile(zip.toFile(), StandardCharsets.UTF_8)) {
try (InputStream in =
zipFile.getInputStream(zipFile.getEntry(ToolHelper.DEFAULT_DEX_FILENAME))) {
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
index 2ab5e3e..661c815 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
@@ -161,6 +161,10 @@
abstract C withMinApiLevel(int minApiLevel);
+ C withKeepAll() {
+ return self();
+ }
+
C withAndroidJar(int androidJarVersion) {
assert this.androidJarVersion == null;
this.androidJarVersion = androidJarVersion;
@@ -219,6 +223,7 @@
public void invokeCustom() throws Throwable {
test("invokecustom", "invokecustom", "InvokeCustom")
.withMinApiLevel(AndroidApiLevel.P.getLevel())
+ .withKeepAll()
.run();
}
@@ -226,6 +231,7 @@
public void invokeCustomErrorDueToMinSdk() throws Throwable {
test("invokecustom-error-due-to-min-sdk", "invokecustom", "InvokeCustom")
.withMinApiLevel(AndroidApiLevel.O.getLevel())
+ .withKeepAll()
.run();
}
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
index 9c91282..c95d8a6 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
@@ -164,6 +164,10 @@
abstract C withMinApiLevel(int minApiLevel);
+ C withKeepAll() {
+ return self();
+ }
+
C withAndroidJar(int androidJarVersion) {
assert this.androidJarVersion == null;
this.androidJarVersion = androidJarVersion;
@@ -264,6 +268,7 @@
test("native-private-interface-methods",
"privateinterfacemethods", "PrivateInterfaceMethods")
.withMinApiLevel(AndroidApiLevel.N.getLevel())
+ .withKeepAll()
.run();
}
@@ -273,6 +278,7 @@
test("desugared-private-interface-methods",
"privateinterfacemethods", "PrivateInterfaceMethods")
.withMinApiLevel(AndroidApiLevel.M.getLevel())
+ .withKeepAll()
.withDexCheck(dexInspector -> {
ClassSubject companion = dexInspector.clazz(
iName + InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX);
@@ -298,6 +304,7 @@
public void varHandleErrorDueToMinSdk() throws Throwable {
test("varhandle-error-due-to-min-sdk", "varhandle", "VarHandleTests")
.withMinApiLevel(AndroidApiLevel.O.getLevel())
+ .withKeepAll()
.run();
}
@@ -306,6 +313,7 @@
TestRunner<?> test = test("twr-close-resource", "twrcloseresource", "TwrCloseResourceTest");
test
.withMinApiLevel(AndroidApiLevel.I.getLevel())
+ .withKeepAll()
.withAndroidJar(AndroidApiLevel.K.getLevel())
.withArg(test.getInputJar().toAbsolutePath().toString())
.run();
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 1cd1b3e..0783413 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -442,7 +442,7 @@
/** Compile an application with D8. */
protected AndroidApp compileWithD8(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
- throws IOException, CompilationFailedException {
+ throws CompilationFailedException {
return ToolHelper.runD8(app, optionsConsumer);
}
@@ -467,8 +467,7 @@
}
/** Compile an application with R8. */
- protected AndroidApp compileWithR8(AndroidApp app)
- throws IOException, CompilationFailedException {
+ protected AndroidApp compileWithR8(AndroidApp app) throws CompilationFailedException {
R8Command command = ToolHelper.prepareR8CommandBuilder(app).build();
return ToolHelper.runR8(command);
}
@@ -476,7 +475,12 @@
/** Compile an application with R8. */
protected AndroidApp compileWithR8(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
throws IOException, CompilationFailedException {
- R8Command command = ToolHelper.prepareR8CommandBuilder(app).build();
+ R8Command command = ToolHelper.prepareR8CommandBuilder(app)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
+ .build();
+
return ToolHelper.runR8(command, optionsConsumer);
}
@@ -590,6 +594,11 @@
+ "-printmapping\n";
}
+ public static String noShrinkingNoMinificationProguardConfiguration() {
+ return "-dontshrink\n"
+ + "-dontobfuscate\n";
+ }
+
/**
* Generate a Proguard configuration for keeping the "static void main(String[])" method of the
* specified class and specify if -allowaccessmodification and -dontobfuscate are added as well.
@@ -948,6 +957,10 @@
public boolean isMinify() {
return this != NONE;
}
+
+ public boolean isAggressive() {
+ return this == AGGRESSIVE;
+ }
}
public static ProgramConsumer emptyConsumer(Backend backend) {
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index ae7cb4f..42b56ca 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -5,12 +5,14 @@
package com.android.tools.r8;
import com.android.tools.r8.TestBase.Backend;
-import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.utils.StringUtils;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
public abstract class TestShrinkerBuilder<
C extends BaseCompilerCommand,
@@ -28,8 +30,10 @@
public abstract T noMinification();
- public T addKeepRules(Path path) throws IOException {
- return addKeepRules(FileUtils.readAllLines(path));
+ public abstract T addKeepRuleFiles(List<Path> files);
+
+ public T addKeepRuleFiles(Path... files) throws IOException {
+ return addKeepRuleFiles(Arrays.asList(files));
}
public abstract T addKeepRules(Collection<String> rules);
@@ -71,4 +75,31 @@
" public static void main(java.lang.String[]);",
"}"));
}
+
+ public T addKeepMethodRules(MethodReference... methods) {
+ for (MethodReference method : methods) {
+ addKeepRules(
+ "-keep class "
+ + method.getHolderClass().getTypeName()
+ + " { "
+ + getMethodLine(method)
+ + " }");
+ }
+ return self();
+ }
+
+ private static String getMethodLine(MethodReference method) {
+ // Should we encode modifiers in method references?
+ StringBuilder builder = new StringBuilder();
+ builder.append(method.getMethodName()).append("(");
+ boolean first = true;
+ for (TypeReference parameterType : method.getFormalTypes()) {
+ if (!first) {
+ builder.append(", ");
+ }
+ builder.append(parameterType.getTypeName());
+ first = false;
+ }
+ return builder.append(");").toString();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 69c710d..57650e2 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -19,7 +19,6 @@
import com.android.tools.r8.shaking.FilteredClassPath;
import com.android.tools.r8.shaking.ProguardConfiguration;
import com.android.tools.r8.shaking.ProguardConfigurationParser;
-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;
@@ -876,11 +875,15 @@
}
public static ProguardConfiguration loadProguardConfiguration(
- DexItemFactory factory, List<Path> configPaths)
- throws IOException, ProguardRuleParserException {
+ DexItemFactory factory, List<Path> configPaths) {
Reporter reporter = new Reporter();
if (configPaths.isEmpty()) {
- return ProguardConfiguration.defaultConfiguration(factory, reporter);
+ return ProguardConfiguration.builder(factory, reporter)
+ .disableShrinking()
+ .disableObfuscation()
+ .disableOptimization()
+ .addKeepAttributePatterns(ImmutableList.of("*"))
+ .build();
}
ProguardConfigurationParser parser =
new ProguardConfigurationParser(factory, reporter);
@@ -923,7 +926,11 @@
public static AndroidApp runR8(AndroidApp app, Consumer<InternalOptions> optionsConsumer)
throws CompilationFailedException {
- return runR8(prepareR8CommandBuilder(app).build(), optionsConsumer);
+ R8Command command = prepareR8CommandBuilder(app)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .build();
+ return runR8(command, optionsConsumer);
}
public static AndroidApp runR8(R8Command command) throws CompilationFailedException {
diff --git a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java
index 246e0c7..b0292e3 100644
--- a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java
@@ -40,6 +40,8 @@
R8.run(
R8Command.builder()
.setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
.addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
.setProgramConsumer(new DexIndexedConsumer.ArchiveConsumer(outDex))
@@ -47,6 +49,8 @@
R8.run(
R8Command.builder()
.setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outCf))
diff --git a/src/test/java/com/android/tools/r8/cf/AnnotationTestRunner.java b/src/test/java/com/android/tools/r8/cf/AnnotationTestRunner.java
index 7705228..e9b4755 100644
--- a/src/test/java/com/android/tools/r8/cf/AnnotationTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/AnnotationTestRunner.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.origin.Origin;
+import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import org.junit.Rule;
import org.junit.Test;
@@ -32,6 +33,10 @@
R8.run(
R8Command.builder()
.setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(
+ ImmutableList.of("-keepattributes *Annotation*"), Origin.unknown())
.addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setProgramConsumer(new DirectoryConsumer(out))
diff --git a/src/test/java/com/android/tools/r8/cf/CallLoopTestRunner.java b/src/test/java/com/android/tools/r8/cf/CallLoopTestRunner.java
index dd3f1e8..1ed7481 100644
--- a/src/test/java/com/android/tools/r8/cf/CallLoopTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/CallLoopTestRunner.java
@@ -24,6 +24,8 @@
R8.run(
R8Command.builder()
.setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setProgramConsumer(new DirectoryConsumer(out))
diff --git a/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java b/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
index 433483f..f8a654a 100644
--- a/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/DebugInfoTestRunner.java
@@ -54,6 +54,8 @@
Builder builder =
R8Command.builder()
.setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setProgramConsumer(consumer);
input.accept(builder);
diff --git a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
index 413bd83..3d75846 100644
--- a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
@@ -53,10 +53,19 @@
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.build();
assertEquals(2, countCatchHandlers(inputApp));
- AndroidApp outputDexApp = ToolHelper.runR8(inputApp);
+ AndroidApp outputDexApp =
+ ToolHelper.runR8(
+ ToolHelper.prepareR8CommandBuilder(inputApp)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .build());
assertEquals(1, countCatchHandlers(outputDexApp));
AndroidApp outputCfApp =
- ToolHelper.runR8WithProgramConsumer(inputApp, ClassFileConsumer.emptyConsumer());
+ ToolHelper.runR8(
+ ToolHelper.prepareR8CommandBuilder(inputApp, ClassFileConsumer.emptyConsumer())
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .build());
assertEquals(1, countCatchHandlers(outputCfApp));
}
diff --git a/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
index 632d0b9..077c7a7 100644
--- a/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/LambdaTestRunner.java
@@ -51,6 +51,8 @@
R8.run(
R8Command.builder()
.setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setProgramConsumer(appBuilder.wrapClassFileConsumer(new ArchiveConsumer(outPath)))
.addClassProgramData(inputClass, Origin.unknown())
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
index 8dd966d..46ed96b 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
@@ -156,7 +156,11 @@
// MethodHandle.invoke() only supported from Android O
// ConstMethodHandle only supported from Android P
Builder builder =
- R8Command.builder().setMode(compilationMode).setProgramConsumer(programConsumer);
+ R8Command.builder()
+ .setMode(compilationMode)
+ .setProgramConsumer(programConsumer)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true);
if (programConsumer instanceof ClassFileConsumer) {
builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
} else {
diff --git a/src/test/java/com/android/tools/r8/cf/NegativeZeroTestRunner.java b/src/test/java/com/android/tools/r8/cf/NegativeZeroTestRunner.java
index 32cf86a..a38bf60 100644
--- a/src/test/java/com/android/tools/r8/cf/NegativeZeroTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/NegativeZeroTestRunner.java
@@ -24,6 +24,8 @@
R8.run(
R8Command.builder()
.setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setProgramConsumer(new DirectoryConsumer(out))
diff --git a/src/test/java/com/android/tools/r8/cf/NestedExceptionTestRunner.java b/src/test/java/com/android/tools/r8/cf/NestedExceptionTestRunner.java
index a60eda9..a7c1c1d 100644
--- a/src/test/java/com/android/tools/r8/cf/NestedExceptionTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/NestedExceptionTestRunner.java
@@ -28,6 +28,8 @@
R8.run(
R8Command.builder()
.setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setProgramConsumer(sink.wrapClassFileConsumer(null))
diff --git a/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
index b778c41..2664ef4 100644
--- a/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
@@ -50,10 +50,20 @@
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.build();
assertEquals(2, countCatchHandlers(inputApp));
- AndroidApp outputDexApp = ToolHelper.runR8(inputApp);
+ AndroidApp outputDexApp =
+ ToolHelper.runR8(
+ ToolHelper.prepareR8CommandBuilder(inputApp)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .build());
assertEquals(1, countCatchHandlers(outputDexApp));
AndroidApp outputCfApp =
- ToolHelper.runR8WithProgramConsumer(inputApp, ClassFileConsumer.emptyConsumer());
+ ToolHelper.runR8(
+ ToolHelper.prepareR8CommandBuilder(inputApp)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .setProgramConsumer(ClassFileConsumer.emptyConsumer())
+ .build());
// ClassCastException and NullPointerException will not have same type on stack.
assertEquals(2, countCatchHandlers(outputCfApp));
}
diff --git a/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
index 1f6d7f7..4b9b937 100644
--- a/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
@@ -31,6 +31,8 @@
AndroidAppConsumers a = new AndroidAppConsumers();
R8.run(
R8Command.builder()
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setProgramConsumer(a.wrapClassFileConsumer(null))
diff --git a/src/test/java/com/android/tools/r8/cf/UninitializedInFrameTestRunner.java b/src/test/java/com/android/tools/r8/cf/UninitializedInFrameTestRunner.java
index 8182501..466ac41 100644
--- a/src/test/java/com/android/tools/r8/cf/UninitializedInFrameTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/UninitializedInFrameTestRunner.java
@@ -53,6 +53,8 @@
R8.run(
R8Command.builder()
.setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.addClassProgramData(clazz, Origin.unknown())
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setProgramConsumer(new ArchiveConsumer(output))
diff --git a/src/test/java/com/android/tools/r8/cf/UnneededLoadStoreDebugInfoTest.java b/src/test/java/com/android/tools/r8/cf/UnneededLoadStoreDebugInfoTest.java
index 016fe18..42bc68b 100644
--- a/src/test/java/com/android/tools/r8/cf/UnneededLoadStoreDebugInfoTest.java
+++ b/src/test/java/com/android/tools/r8/cf/UnneededLoadStoreDebugInfoTest.java
@@ -66,6 +66,8 @@
R8.run(
R8Command.builder()
.setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.addProgramFiles(inputJar)
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setProgramConsumer(new ArchiveConsumer(outputJar))
diff --git a/src/test/java/com/android/tools/r8/classmerging/ForceInliningWithStaticInterfaceMethodTest.java b/src/test/java/com/android/tools/r8/classmerging/ForceInliningWithStaticInterfaceMethodTest.java
new file mode 100644
index 0000000..7c86d1a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/ForceInliningWithStaticInterfaceMethodTest.java
@@ -0,0 +1,62 @@
+// 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.classmerging;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+/** Regression test for b/120121170. */
+public class ForceInliningWithStaticInterfaceMethodTest extends TestBase {
+
+ @Test
+ public void test() throws Exception {
+ String expectedOutput = StringUtils.lines("A.<init>()", "I.m()", "B.<init>()");
+ testForR8(Backend.DEX)
+ .addInnerClasses(ForceInliningWithStaticInterfaceMethodTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(AndroidApiLevel.M)
+ .compile()
+ .run(TestClass.class)
+ .assertSuccessWithOutput(expectedOutput);
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ new B();
+ }
+ }
+
+ static class A {
+
+ public A() {
+ System.out.println("A.<init>()");
+
+ // By the time the vertical class merger runs, I.m() still exists, so the inlining oracle
+ // concludes that A.<init>() is eligible for inlining. However, by the time A.<init>() will
+ // be force-inlined into B.<init>(), I.m() has been rewritten as a result of interface method
+ // desugaring, but the target method is not yet added to the application. Hence the inlining
+ // oracle concludes that A.<init>() is not eligible for inlining, which leads to an error
+ // "FORCE inlining on non-inlinable".
+ I.m();
+ }
+ }
+
+ static class B extends A {
+
+ public B() {
+ System.out.println("B.<init>()");
+ }
+ }
+
+ interface I {
+
+ static void m() {
+ System.out.println("I.m()");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java b/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java
index e0b953a..22bca9f 100644
--- a/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/ArraySimplificationLineNumberTestRunner.java
@@ -27,7 +27,7 @@
DebugTestConfig d8NoLocals = new D8DebugTestConfig().compileAndAdd(
temp,
Collections.singletonList(ToolHelper.getClassFileForTestClass(CLASS)),
- options -> options.proguardConfiguration.getKeepAttributes().localVariableTable = false);
+ options -> options.testing.noLocalsTableOnInput = true);
new DebugStreamComparator()
.add("CF", streamDebugTest(cf, NAME, NO_FILTER))
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 fb5ffe2..635f77e 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
import com.android.tools.r8.debug.DebugTestConfig.RuntimeKind;
+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;
@@ -46,7 +47,13 @@
Path outdir = temp.newFolder().toPath();
Path outjar = outdir.resolve("r8_compiled.jar");
R8Command.Builder builder =
- R8Command.builder().addProgramFiles(DEBUGGEE_JAR).setMode(CompilationMode.RELEASE);
+ R8Command.builder()
+ .addProgramFiles(DEBUGGEE_JAR)
+ .setMode(CompilationMode.RELEASE)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(
+ ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), Origin.unknown());
if (runtimeKind == RuntimeKind.DEX) {
AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
diff --git a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
index 6dccf0b..099f55b 100644
--- a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
@@ -9,8 +9,10 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Consumer;
@@ -54,6 +56,9 @@
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setMode(CompilationMode.DEBUG)
.setOutput(output, OutputMode.ClassFile)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
.build(),
optionsConsumer);
return new CfDebugTestConfig(output);
diff --git a/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
index c7a5b16..cfd0a98 100644
--- a/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/IincDebugTestRunner.java
@@ -17,6 +17,8 @@
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
+import com.android.tools.r8.origin.Origin;
+import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.stream.Stream;
import org.junit.Assume;
@@ -112,6 +114,10 @@
Builder builder =
R8Command.builder()
.setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(
+ ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), Origin.unknown())
.setProgramConsumer(consumer)
.addProgramFiles(inputJar);
if ((consumer instanceof ClassFileConsumer)) {
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 78e9a56..35f24c9 100644
--- a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.debug.DebugTestConfig.RuntimeKind;
+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;
@@ -58,7 +59,12 @@
R8Command.Builder builder =
R8Command.builder()
.addProgramFiles(DEBUGGEE_JAR)
- .setMode(dontOptimizeByEnablingDebug ? CompilationMode.DEBUG : CompilationMode.RELEASE);
+ .setMode(dontOptimizeByEnablingDebug ? CompilationMode.DEBUG : CompilationMode.RELEASE)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(
+ ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), Origin.unknown());
+
DebugTestConfig config = null;
if (runtimeKind == RuntimeKind.CF) {
diff --git a/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java b/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java
index 805473b..f741381 100644
--- a/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/LoadInvokeLoadOptimizationTestRunner.java
@@ -35,6 +35,9 @@
"R8/" + backend,
temp ->
testForR8(temp, backend)
+ .noTreeShaking()
+ .noMinification()
+ .addKeepRules("-keepattributes SourceFile,LineNumberTable")
.addProgramClasses(CLASS)
.setMode(CompilationMode.DEBUG)
.debugConfig());
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 1d9bf27..d1c3ac1 100644
--- a/src/test/java/com/android/tools/r8/debug/MinificationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
@@ -8,7 +8,6 @@
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8Command;
-import com.android.tools.r8.TestBase.MinifyMode;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.debug.DebugTestConfig.RuntimeKind;
import com.android.tools.r8.errors.Unreachable;
@@ -69,16 +68,17 @@
private DebugTestConfig getTestConfig() throws Throwable {
List<String> proguardConfigurations = Collections.emptyList();
- if (minificationMode.isMinify()) {
- ImmutableList.Builder<String> builder = ImmutableList.builder();
- builder.add("-keep public class Minified { public static void main(java.lang.String[]); }");
- builder.add("-keepattributes SourceFile");
- builder.add("-keepattributes LineNumberTable");
- if (minificationMode == MinifyMode.AGGRESSIVE) {
- builder.add("-overloadaggressively");
- }
- proguardConfigurations = builder.build();
+ ImmutableList.Builder<String> proguardConfigurationBuilder = ImmutableList.builder();
+ if (!minificationMode.isMinify()) {
+ proguardConfigurationBuilder.add("-dontobfuscate");
+ } else if (minificationMode.isAggressive()) {
+ proguardConfigurationBuilder.add("-overloadaggressively");
}
+ proguardConfigurationBuilder.add(
+ "-keep public class Minified { public static void main(java.lang.String[]); }");
+ proguardConfigurationBuilder.add("-keepattributes SourceFile");
+ proguardConfigurationBuilder.add("-keepattributes LineNumberTable");
+ proguardConfigurations = proguardConfigurationBuilder.build();
Path outputPath = temp.getRoot().toPath().resolve("classes.zip");
Path proguardMap = writeProguardMap ? temp.getRoot().toPath().resolve("proguard.map") : null;
diff --git a/src/test/java/com/android/tools/r8/debug/NonExitingMethodTestRunner.java b/src/test/java/com/android/tools/r8/debug/NonExitingMethodTestRunner.java
index 1405ce5..3e934b8 100644
--- a/src/test/java/com/android/tools/r8/debug/NonExitingMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/NonExitingMethodTestRunner.java
@@ -11,6 +11,8 @@
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.origin.Origin;
+import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
import org.junit.Assume;
@@ -40,6 +42,10 @@
ToolHelper.runR8(
R8Command.builder()
.setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(
+ ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), Origin.unknown())
.addProgramFiles(getClassFilePath())
.setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(path))
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
diff --git a/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java b/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java
index cf341fd..1739c30 100644
--- a/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debug/NopDebugTestRunner.java
@@ -10,7 +10,9 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.DebuggeeState;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.stream.Stream;
import org.junit.Assume;
@@ -49,6 +51,10 @@
R8Command.builder()
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(
+ ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), Origin.unknown())
.addProgramFiles(inputJar)
.setOutput(outputJar, OutputMode.ClassFile)
.build(),
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 67b7c1a..134b727 100644
--- a/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
@@ -8,8 +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.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import org.junit.rules.TemporaryFolder;
@@ -23,10 +25,15 @@
AndroidAppConsumers sink = new AndroidAppConsumers();
R8.run(
R8Command.builder()
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.setMode(CompilationMode.DEBUG)
.addProgramFiles(DebugTestBase.DEBUGGEE_JAR)
.setProgramConsumer(sink.wrapClassFileConsumer(null))
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
.build());
compiledResources = sink.build();
}
diff --git a/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java b/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java
index 17f0c04..51bb411 100644
--- a/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java
+++ b/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.origin.Origin;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import java.util.Collection;
@@ -42,6 +43,11 @@
.setOutput(out, OutputMode.ClassFile)
.setMode(CompilationMode.DEBUG)
.addProgramFiles(DebugTestBase.DEBUGGEE_JAR)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(
+ ImmutableList.of("-keepattributes SourceFile,LineNumberTable"),
+ Origin.unknown())
.build(),
options -> options.enableCfFrontend = true);
} catch (Exception e) {
diff --git a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
index ce84f51..980325e 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/InliningWithoutPositionsTestRunner.java
@@ -22,8 +22,9 @@
import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
import com.android.tools.r8.naming.Range;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApiLevel;
-import java.io.IOException;
+import com.google.common.collect.ImmutableList;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@@ -91,7 +92,7 @@
}
@Test
- public void testStackTrace() throws IOException, Exception {
+ public void testStackTrace() throws Exception {
// See InliningWithoutPositionsTestSourceDump for the code compiled here.
Path testClassDir = temp.newFolder(TEST_PACKAGE.split(".")).toPath();
Path testClassPath = testClassDir.resolve(TEST_CLASS + ".class");
@@ -121,6 +122,11 @@
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setOutput(outputPath, OutputMode.ClassFile);
}
+ builder
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(
+ ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), Origin.unknown());
ToolHelper.runR8(builder.build(), options -> options.inliningInstructionLimit = 40);
diff --git a/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
index 76d2291..a0e9a1d 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/KotlinDebugInfoTestRunner.java
@@ -15,6 +15,8 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
import org.junit.Test;
@@ -71,6 +73,10 @@
Builder builder =
R8Command.builder()
.setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(
+ ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), Origin.unknown())
.setProgramConsumer(consumer)
.addProgramFiles(inputJar);
if ((consumer instanceof ClassFileConsumer)) {
diff --git a/src/test/java/com/android/tools/r8/desugar/KeptLambdaDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/KeptLambdaDesugaringTest.java
index 4664d42..8fddde3 100644
--- a/src/test/java/com/android/tools/r8/desugar/KeptLambdaDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/KeptLambdaDesugaringTest.java
@@ -5,13 +5,11 @@
package com.android.tools.r8.desugar;
import com.android.tools.r8.TestBase;
-import org.junit.Ignore;
import org.junit.Test;
/** Regression test for b/120971047. */
public class KeptLambdaDesugaringTest extends TestBase {
- @Ignore("b/120971047")
@Test
public void test() throws Exception {
testForR8(Backend.DEX)
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
index be14412..9f25230 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -19,7 +19,6 @@
import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.dex.ApplicationWriter;
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -140,6 +139,9 @@
if (pgConfs != null) {
builder.addProguardConfigurationFiles(
pgConfs.stream().map(Paths::get).collect(Collectors.toList()));
+ } else {
+ builder.setDisableTreeShaking(true);
+ builder.setDisableMinification(true);
}
builder.setMode(mode);
builder.setProgramConsumer(dexIndexedConsumerSupplier.get());
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
index 1b3bc40..7f970b9 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
@@ -27,6 +27,8 @@
private CompilationResult doRun() throws CompilationFailedException {
R8Command command =
R8Command.builder()
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.addProgramFiles(Paths.get(GMSCORE_V7_DIR, GMSCORE_APK))
.setProgramConsumer(DexIndexedConsumer.emptyConsumer())
.setMinApiLevel(AndroidApiLevel.L.getLevel())
diff --git a/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java b/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
index e6ec726..b13a990 100644
--- a/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/ir/PhiDefinitionsTestRunner.java
@@ -108,6 +108,8 @@
Builder builder =
R8Command.builder()
.setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.setProgramConsumer(consumer)
.addProgramFiles(inputJar);
if (consumer instanceof ClassFileConsumer) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
index e681c77..58c4237 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTestBase.java
@@ -12,12 +12,15 @@
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.ProguardClassFilter;
+import com.android.tools.r8.shaking.ProguardKeepRule;
import com.android.tools.r8.shaking.RootSetBuilder;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableList;
import java.util.concurrent.ExecutorService;
public abstract class NonNullTrackerTestBase extends TestBase {
@@ -29,16 +32,23 @@
DexApplication dexApplication =
new ApplicationReader(app, TEST_OPTIONS, timing).read().toDirect();
AppView<AppInfoWithSubtyping> appView =
- new AppView<>(new AppInfoWithSubtyping(dexApplication), GraphLense.getIdentityLense());
+ new AppView<>(
+ new AppInfoWithSubtyping(dexApplication), GraphLense.getIdentityLense(), TEST_OPTIONS);
ExecutorService executorService = ThreadUtils.getExecutorService(TEST_OPTIONS);
RootSet rootSet =
new RootSetBuilder(
- appView, dexApplication, TEST_OPTIONS.proguardConfiguration.getRules(), TEST_OPTIONS)
+ appView,
+ dexApplication,
+ ImmutableList.of(ProguardKeepRule.defaultKeepAllRule(unused -> {})),
+ TEST_OPTIONS)
.run(executorService);
Enqueuer enqueuer =
new Enqueuer(appView, TEST_OPTIONS, null, TEST_OPTIONS.forceProguardCompatibility);
return enqueuer.traceApplication(
- rootSet, TEST_OPTIONS.proguardConfiguration.getDontWarnPatterns(), executorService, timing);
+ rootSet,
+ ProguardClassFilter.empty(),
+ executorService,
+ timing);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
index 6fea6cd..0194a1a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/instanceofremoval/InstanceOfRemovalTest.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -42,16 +43,28 @@
System.out.println("B instanceof B: " + (getBForFoo() instanceof B));
System.out.println("null instanceof A: " + (getNullForFoo() instanceof A));
System.out.println("null instanceof B: " + (getNullForFoo() instanceof B));
+ System.out.println("A[] instanceof A[]: " + (getAarrayForFoo() instanceof A[]));
+ System.out.println("A[] instanceof B[]: " + (getAarrayForFoo() instanceof B[]));
+ System.out.println("B[] instanceof A[]: " + (getBarrayForFoo() instanceof A[]));
+ System.out.println("B[] instanceof B[]: " + (getBarrayForFoo() instanceof B[]));
}
public static A getAForFoo() {
return new A();
}
+ public static A[] getAarrayForFoo() {
+ return new A[] { };
+ }
+
public static A getBForFoo() {
return new B();
}
+ public static A[] getBarrayForFoo() {
+ return new B[0];
+ }
+
public static A getNullForFoo() { return null; }
@NeverInline
@@ -61,7 +74,11 @@
System.out.println("B instanceof A: " + (getBForBar() instanceof A));
System.out.println("B instanceof B: " + (getBForBar() instanceof B));
System.out.println("null instanceof A: " + (getNullForBar(true) instanceof A));
- System.out.print("null instanceof B: " + (getNullForBar(true) instanceof B));
+ System.out.println("null instanceof B: " + (getNullForBar(true) instanceof B));
+ System.out.println("A[] instanceof A[]: " + (getAarrayForBar() instanceof A[]));
+ System.out.println("A[] instanceof B[]: " + (getAarrayForBar() instanceof B[]));
+ System.out.println("B[] instanceof A[]: " + (getBarrayForBar() instanceof A[]));
+ System.out.println("B[] instanceof B[]: " + (getBarrayForBar() instanceof B[]));
}
@NeverInline
@@ -70,11 +87,21 @@
}
@NeverInline
+ public static A[] getAarrayForBar() {
+ return new A[] { new A(), new B() };
+ }
+
+ @NeverInline
public static A getBForBar() {
return new B();
}
@NeverInline
+ public static A[] getBarrayForBar() {
+ return new B[0];
+ }
+
+ @NeverInline
public static A getNullForBar(boolean returnNull) {
// Avoid `return null` since we will then use the fact that getNullForBar() always returns
// null at the call site.
@@ -103,12 +130,21 @@
"B instanceof B: true",
"null instanceof A: false",
"null instanceof B: false",
+ "A[] instanceof A[]: true",
+ "A[] instanceof B[]: false",
+ "B[] instanceof A[]: true",
+ "B[] instanceof B[]: true",
"A instanceof A: true",
"A instanceof B: false",
"B instanceof A: true",
"B instanceof B: true",
"null instanceof A: false",
- "null instanceof B: false");
+ "null instanceof B: false",
+ "A[] instanceof A[]: true",
+ "A[] instanceof B[]: false",
+ "B[] instanceof A[]: true",
+ "B[] instanceof B[]: true",
+ "");
testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expected);
@@ -122,18 +158,18 @@
.assertSuccessWithOutput(expected)
.inspector();
+ ClassSubject testClass = inspector.clazz(TestClass.class);
+
// With inlining we can prove that all instance-of checks succeed or fail.
- MethodSubject fooMethodSubject =
- inspector.clazz(TestClass.class).method("void", "foo", ImmutableList.of());
+ MethodSubject fooMethodSubject = testClass.uniqueMethodWithName("foo");
Iterator<InstructionSubject> fooInstructionIterator =
fooMethodSubject.iterateInstructions(InstructionSubject::isInstanceOf);
assertEquals(0, Streams.stream(fooInstructionIterator).count());
// Without inlining we cannot prove any of the instance-of checks to be trivial.
- MethodSubject barMethodSubject =
- inspector.clazz(TestClass.class).method("void", "bar", ImmutableList.of());
+ MethodSubject barMethodSubject = testClass.uniqueMethodWithName("bar");
Iterator<InstructionSubject> barInstructionIterator =
barMethodSubject.iterateInstructions(InstructionSubject::isInstanceOf);
- assertEquals(4, Streams.stream(barInstructionIterator).count());
+ assertEquals(6, Streams.stream(barInstructionIterator).count());
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
index 9cc69fa..54f9df3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetNameTest.java
@@ -263,7 +263,7 @@
.addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
.addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
if (!enableMinification) {
- builder.addKeepRules("-dontobfuscate");
+ builder.noMinification();
}
TestRunResult result =
builder
@@ -284,7 +284,7 @@
.addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
.addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
if (!enableMinification) {
- builder.addKeepRules("-dontobfuscate");
+ builder.noMinification();
}
TestRunResult result =
builder
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
index b75e4ee..5a9ca75 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/reflection/GetSimpleNameTest.java
@@ -124,7 +124,7 @@
"Inner",
"Inner"
);
- private static final String OUTPUT_WITH_SHRUNK_ATTRIBUTE = StringUtils.lines(
+ private static final String OUTPUT_WITH_SHRUNK_ATTRIBUTES = StringUtils.lines(
"Local_t03",
"InnerLocal",
"$",
@@ -211,7 +211,7 @@
.addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
.addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
if (!enableMinification) {
- builder.addKeepRules("-dontobfuscate");
+ builder.noMinification();
}
TestRunResult result =
builder
@@ -231,12 +231,12 @@
.addKeepRules("-keep,allowobfuscation class **.ClassGetSimpleName*")
// See b/119471127: some old VMs are not resilient to broken attributes.
// Comment out the following line to reproduce b/120130435
- // then use OUTPUT_WITH_SHRUNK_ATTRIBUTE
+ // then use OUTPUT_WITH_SHRUNK_ATTRIBUTES
.addKeepRules("-keep,allowobfuscation class **.Outer*")
.addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
.addKeepRules("-printmapping " + createNewMappingPath().toAbsolutePath().toString());
if (!enableMinification) {
- builder.addKeepRules("-dontobfuscate");
+ builder.noMinification();
}
R8TestRunResult result =
builder
diff --git a/src/test/java/com/android/tools/r8/jar/UnicodeSetRegression/UnicodeSetRegressionTest.java b/src/test/java/com/android/tools/r8/jar/UnicodeSetRegression/UnicodeSetRegressionTest.java
index 3107636..689824f 100644
--- a/src/test/java/com/android/tools/r8/jar/UnicodeSetRegression/UnicodeSetRegressionTest.java
+++ b/src/test/java/com/android/tools/r8/jar/UnicodeSetRegression/UnicodeSetRegressionTest.java
@@ -50,6 +50,8 @@
R8Command.Builder builder =
R8Command.builder()
.addProgramFiles(Paths.get(JAR_FILE))
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.setOutput(Paths.get(combinedInput.toString()), OutputMode.DexIndexed);
AndroidAppConsumers compatSink = new AndroidAppConsumers(builder);
// Ignore missing classes since we don't want to link to the IBM text library.
@@ -87,7 +89,6 @@
@Test
public void testUnicodeSetFromJarToCF() throws Throwable {
Path combinedInput = temp.getRoot().toPath().resolve("all.zip");
- Path oatFile = temp.getRoot().toPath().resolve("all.oat");
R8Command.Builder builder =
R8Command.builder()
.addProgramFiles(Paths.get(JAR_FILE))
@@ -96,6 +97,6 @@
AndroidAppConsumers compatSink = new AndroidAppConsumers(builder);
// Ignore missing classes since we don't want to link to the IBM text library.
ToolHelper.runR8(builder.build(), options -> options.ignoreMissingClasses = true);
- AndroidApp result = compatSink.build();
+ compatSink.build();
}
}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
index fd68772..1c23340 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
@@ -40,6 +41,9 @@
.addProgramFiles(inputJar)
.setOutput(outputJar, OutputMode.ClassFile)
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(ImmutableList.of("-keepattributes *"), Origin.unknown())
.build(),
options -> options.enableCfFrontend = true);
ProcessResult processResult = ToolHelper.runJava(outputJar, main);
diff --git a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
index 4dba00d..33f28fc 100644
--- a/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
+++ b/src/test/java/com/android/tools/r8/jasmin/MemberResolutionTest.java
@@ -671,7 +671,8 @@
Assert.assertTrue(dxOutput.stderr.contains(name));
ProcessResult d8Output = runOnArtD8Raw(app, library, MAIN_CLASS);
Assert.assertTrue(d8Output.stderr.contains(name));
- ProcessResult r8Output = runOnArtR8Raw(app, library, MAIN_CLASS, null, null);
+ ProcessResult r8Output = runOnArtR8Raw(app, library, MAIN_CLASS,
+ noShrinkingNoMinificationProguardConfiguration(), null);
Assert.assertTrue(r8Output.stderr.contains(name));
ProcessResult r8ShakenOutput = runOnArtR8Raw(app, library, MAIN_CLASS,
keepMainProguardConfiguration(MAIN_CLASS), null);
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java b/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java
index 1463b65..458ddbc 100644
--- a/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java
@@ -80,6 +80,8 @@
ToolHelper.runR8(
ToolHelper.prepareR8CommandBuilder(originalApplication, emptyConsumer(backend))
.addLibraryFiles(runtimeJar(backend))
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.build());
CodeInspector inspector =
diff --git a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
index 4919227..ac48680 100644
--- a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
+++ b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
@@ -49,7 +49,7 @@
public TemporaryFolder tmpOutputDir = ToolHelper.getTemporaryFolderForTest();
private AndroidApp compileWithD8(Path intputPath, Path outputPath)
- throws IOException, CompilationFailedException {
+ throws CompilationFailedException {
D8Command.Builder builder =
D8Command.builder()
.setMinApiLevel(AndroidApiLevel.O.getLevel())
@@ -61,7 +61,7 @@
}
private AndroidApp compileWithR8(Path inputPath, Path outputPath, Path keepRulesPath)
- throws IOException, CompilationFailedException {
+ throws CompilationFailedException {
return ToolHelper.runR8(
R8Command.builder()
.addProgramFiles(inputPath)
diff --git a/src/test/java/com/android/tools/r8/jsr45/keep-rules-1.txt b/src/test/java/com/android/tools/r8/jsr45/keep-rules-1.txt
index 04e2d30..f659607 100644
--- a/src/test/java/com/android/tools/r8/jsr45/keep-rules-1.txt
+++ b/src/test/java/com/android/tools/r8/jsr45/keep-rules-1.txt
@@ -2,6 +2,8 @@
# 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.
+-keepattributes SourceDebugExtension
+
-dontshrink
-dontobfuscate
diff --git a/src/test/java/com/android/tools/r8/jsr45/keep-rules-2.txt b/src/test/java/com/android/tools/r8/jsr45/keep-rules-2.txt
index 63b52e4..d04091d 100644
--- a/src/test/java/com/android/tools/r8/jsr45/keep-rules-2.txt
+++ b/src/test/java/com/android/tools/r8/jsr45/keep-rules-2.txt
@@ -2,6 +2,8 @@
# 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.
+-keepattributes SourceDebugExtension
+
-dontshrink
# allow access modification to enable minification
-allowaccessmodification
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
index c7a8ef2..b8c4b47 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
@@ -22,7 +22,6 @@
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
-import org.junit.Ignore;
import org.junit.Test;
public class KotlinLambdaMergingTest extends AbstractR8KotlinTestBase {
@@ -455,7 +454,6 @@
});
}
- @Ignore("b/121107286: assertion failure in VerticalClassMerger#verifyGraphLense")
@Test
public void testSingleton() throws Exception {
final String mainClassName = "lambdas_singleton.MainKt";
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index 48b23fe..11e92e1 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -96,6 +96,8 @@
Path mainDexListOutput = temp.getRoot().toPath().resolve("main-dex-output.txt");
R8Command command =
ToolHelper.prepareR8CommandBuilder(readClasses(HelloWorldMain.class))
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.addMainDexRulesFiles(mainDexRules)
.setMainDexListOutputPath(mainDexListOutput)
.setOutput(temp.getRoot().toPath(), OutputMode.DexIndexed)
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 2674caf..ff948aa 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.maindexlist;
-import static com.android.tools.r8.resolution.SingleTargetLookupTest.appInfo;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -474,7 +473,11 @@
jasminBuilder.addClass(name);
}
Path input = temp.newFolder().toPath().resolve("input.zip");
- ToolHelper.runR8(jasminBuilder.build()).writeToZip(input, OutputMode.DexIndexed);
+ ToolHelper.runR8(
+ ToolHelper.prepareR8CommandBuilder(jasminBuilder.build())
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .build()).writeToZip(input, OutputMode.DexIndexed);
// Test with empty main dex list.
runDeterministicTest(input, null, true);
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 7df3a29..31170d3 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ir.desugar.LambdaRewriter;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -23,6 +24,7 @@
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
@@ -321,6 +323,10 @@
Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
.addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
.setOutput(out, OutputMode.DexIndexed)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(
+ ImmutableList.of("-keepattributes *Annotation*"), Origin.unknown())
.addMainDexRulesFiles(mainDexRules)
.setMainDexListConsumer((string, handler) -> r8MainDexListOutput.content = string)
.build();
diff --git a/src/test/java/com/android/tools/r8/maindexlist/checkdiscard/MainDexListCheckDiscard.java b/src/test/java/com/android/tools/r8/maindexlist/checkdiscard/MainDexListCheckDiscard.java
index a68ae3b..cd85cd4 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/checkdiscard/MainDexListCheckDiscard.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/checkdiscard/MainDexListCheckDiscard.java
@@ -36,6 +36,8 @@
.addMainDexRules(ImmutableList.of(checkDiscardRule), Origin.unknown())
.setOutput(temp.getRoot().toPath(), OutputMode.DexIndexed)
.setMode(CompilationMode.RELEASE)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.build();
try {
ToolHelper.runR8(command);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java b/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
index 1d8f7d8..0acd733 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/whyareyoukeeping/MainDexListWhyAreYouKeeping.java
@@ -12,7 +12,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graphinfo.GraphConsumer;
+import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
@@ -85,6 +85,8 @@
public void runTestWithR8(GraphConsumer consumer, String rule) throws Exception {
R8TestBuilder builder =
testForR8(Backend.DEX)
+ .noTreeShaking()
+ .noMinification()
.setMinApi(AndroidApiLevel.K)
.addProgramClasses(CLASSES)
.addMainDexRules(keepMainProguardConfiguration(HelloWorldMain.class))
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 7fc9cd5..4c54504 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
@@ -95,7 +95,9 @@
R8Command.Builder builder =
R8Command.builder()
.setOutput(Paths.get(out), TestBase.outputMode(backend))
- .addLibraryFiles(JAR_LIBRARY, TestBase.runtimeJar(backend));
+ .addLibraryFiles(JAR_LIBRARY, TestBase.runtimeJar(backend))
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true);
if (backend == Backend.DEX) {
builder.setMinApiLevel(minApiLevel);
}
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
new file mode 100644
index 0000000..179f9c6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinificationKotlinTest.java
@@ -0,0 +1,64 @@
+// 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;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.KotlinTestBase;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.Streams;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EnumMinificationKotlinTest extends KotlinTestBase {
+ private static final String FOLDER = "minify_enum";
+ private static final String MAIN_CLASS_NAME = "minify_enum.MainKt";
+ private static final String ENUM_CLASS_NAME = "minify_enum.MinifyEnum";
+
+ private final Backend backend;
+ private final boolean minify;
+
+ @Parameterized.Parameters(name = "Backend: {0} target: {1} minify: {2}")
+ public static Collection<Object[]> data() {
+ return buildParameters(Backend.values(), KotlinTargetVersion.values(), BooleanUtils.values());
+ }
+
+ public EnumMinificationKotlinTest(
+ Backend backend, KotlinTargetVersion targetVersion, boolean minify) {
+ super(targetVersion);
+ this.backend = backend;
+ this.minify = minify;
+ }
+
+ @Test
+ public void b121221542() throws Exception {
+ R8TestBuilder builder = testForR8(backend)
+ .addProgramFiles(getKotlinJarFile(FOLDER))
+ .addProgramFiles(getJavaJarFile(FOLDER))
+ .addKeepMainRule(MAIN_CLASS_NAME);
+ if (!minify) {
+ builder.noMinification();
+ }
+ CodeInspector inspector = builder.run(MAIN_CLASS_NAME).inspector();
+ ClassSubject enumClass = inspector.clazz(ENUM_CLASS_NAME);
+ assertThat(enumClass, isPresent());
+ assertEquals(minify, enumClass.isRenamed());
+ MethodSubject clinit = enumClass.clinit();
+ assertThat(clinit, isPresent());
+ assertEquals(0,
+ Streams.stream(clinit.iterateInstructions(InstructionSubject::isThrow)).count());
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index 3d9edfc..4f4639a 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.optimize.ClassAndMemberPublicizer;
import com.android.tools.r8.shaking.Enqueuer;
import com.android.tools.r8.shaking.ProguardConfiguration;
-import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.shaking.RootSetBuilder;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.utils.InternalOptions;
@@ -45,7 +44,6 @@
private DexApplication program;
DexItemFactory dexItemFactory;
- private AppView<AppInfoWithSubtyping> appView;
NamingTestBase(
String test,
@@ -62,21 +60,21 @@
public void readApp() throws IOException, ExecutionException {
program = ToolHelper.buildApplication(ImmutableList.of(appFileName));
dexItemFactory = program.dexItemFactory;
- appView = new AppView<>(new AppInfoWithSubtyping(program), GraphLense.getIdentityLense());
}
- NamingLens runMinifier(List<Path> configPaths)
- throws IOException, ProguardRuleParserException, ExecutionException {
+ NamingLens runMinifier(List<Path> configPaths) throws ExecutionException {
ProguardConfiguration configuration =
ToolHelper.loadProguardConfiguration(dexItemFactory, configPaths);
InternalOptions options = new InternalOptions(configuration, new Reporter());
ExecutorService executor = ThreadUtils.getExecutorService(1);
+ AppView<AppInfoWithSubtyping> appView =
+ new AppView<>(new AppInfoWithSubtyping(program), GraphLense.getIdentityLense(), options);
RootSet rootSet =
new RootSetBuilder(appView, program, configuration.getRules(), options).run(executor);
- if (options.proguardConfiguration.isAccessModificationAllowed()) {
+ if (options.getProguardConfiguration().isAccessModificationAllowed()) {
ClassAndMemberPublicizer.run(executor, timing, program, appView, rootSet);
rootSet =
new RootSetBuilder(appView, program, configuration.getRules(), options).run(executor);
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 e7b0554..2ef1f5e 100644
--- a/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
@@ -13,6 +13,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.shaking.ProguardKeepRule;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.google.common.collect.ImmutableList;
import java.nio.file.Path;
@@ -41,6 +42,7 @@
ToolHelper.addProguardConfigurationConsumer(
R8Command.builder(),
pgConfig -> {
+ pgConfig.addRule(ProguardKeepRule.defaultKeepAllRule(unused -> {}));
pgConfig.setRenameSourceFileAttribute(TEST_FILE);
pgConfig.addKeepAttributePatterns(
ImmutableList.of("SourceFile", "LineNumberTable"));
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingTest.java
index e490c6f..1038f5c 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingTest.java
@@ -261,6 +261,8 @@
ToolHelper.addProguardConfigurationConsumer(
getCommandForApps(out, flag, NAMING001_JAR).setDisableMinification(true),
pgConfig -> {
+ pgConfig.disableShrinking();
+ pgConfig.disableObfuscation();
pgConfig.setPrintMapping(true);
pgConfig.setPrintMappingFile(proguardMap);
})
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ShrunkenLibraryTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ShrunkenLibraryTest.java
new file mode 100644
index 0000000..61250a2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ShrunkenLibraryTest.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.naming.applymapping;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class ShrunkenLibraryTest extends TestBase {
+
+ private static Path mappingFile;
+
+ @Before
+ public void setup() throws Exception {
+ // Mapping file that describes that Runnable has been renamed to A.
+ mappingFile = temp.newFile("mapping.txt").toPath();
+ FileUtils.writeTextFile(
+ mappingFile, Runnable.class.getTypeName() + " -> " + A.class.getTypeName() + ":");
+ }
+
+ @Test
+ public void testProguard() throws Exception {
+ testForProguard()
+ .addProgramClasses(ShrunkenLibraryTestClass.class)
+ .addKeepRules(
+ "-keep class " + ShrunkenLibraryTestClass.class.getTypeName() + " {",
+ " public void method(" + Runnable.class.getTypeName() + ");",
+ "}",
+ "-applymapping " + mappingFile.toAbsolutePath())
+ .compile()
+ .inspect(this::inspect);
+ }
+
+ @Ignore("b/121305642")
+ @Test
+ public void test() throws Exception {
+ testForR8(Backend.DEX)
+ .addProgramClasses(ShrunkenLibraryTestClass.class)
+ .addKeepRules(
+ "-keep class " + ShrunkenLibraryTestClass.class.getTypeName() + " {",
+ " public void method(" + Runnable.class.getTypeName() + ");",
+ "}",
+ "-applymapping " + mappingFile.toAbsolutePath())
+ .compile()
+ .inspect(this::inspect);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ MethodSubject methodSubject =
+ inspector.clazz(ShrunkenLibraryTestClass.class).uniqueMethodWithName("method");
+ assertThat(methodSubject, isPresent());
+ assertEquals(
+ A.class.getTypeName(), methodSubject.getMethod().method.proto.parameters.toSourceString());
+ }
+}
+
+class ShrunkenLibraryTestClass {
+
+ public void method(Runnable obj) {}
+}
+
+class A {}
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 f1d846e..06c0ca4 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
@@ -5,6 +5,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.google.common.collect.ImmutableList;
import org.junit.Assert;
@@ -14,8 +15,14 @@
@Test
public void buildWithD8AndRunWithDalvikOrArt() throws Exception {
- AndroidApp androidApp = compileWithR8(
- ImmutableList.of(ClassWithAnnotations.class, AnAnnotation.class),
+ AndroidApp androidApp = ToolHelper.runR8(
+ ToolHelper.prepareR8CommandBuilder(
+ readClasses(ClassWithAnnotations.class, AnAnnotation.class))
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
+ .addProguardConfiguration(
+ ImmutableList.of("-keepattributes *Annotation*"), Origin.unknown())
+ .build(),
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/regress/b72485384/Regress72485384Test.java b/src/test/java/com/android/tools/r8/regress/b72485384/Regress72485384Test.java
index ba65d92..be733d3 100644
--- a/src/test/java/com/android/tools/r8/regress/b72485384/Regress72485384Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b72485384/Regress72485384Test.java
@@ -36,8 +36,6 @@
{baseConfig + "-dontshrink", null},
{baseConfig + "-dontshrink -dontobfuscate", null},
{baseConfig + "-dontobfuscate", null},
- {"", null},
- {"-dontshrink", null},
{"-keep class DoesNotExist -dontshrink", "ClassNotFoundException"}
});
}
diff --git a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
index 72ed9d6..8f3eea0 100644
--- a/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
+++ b/src/test/java/com/android/tools/r8/regress/b78493232/Regress78493232_WithPhi.java
@@ -37,7 +37,9 @@
ProcessResult d8Result =
runOnArtRaw(compileWithD8(app), Regress78493232Dump_WithPhi.CLASS_NAME);
ProcessResult r8Result =
- runOnArtRaw(compileWithR8(app), Regress78493232Dump_WithPhi.CLASS_NAME);
+ runOnArtRaw(
+ compileWithR8(app, "-dontshrink\n-dontobfuscate\n"),
+ Regress78493232Dump_WithPhi.CLASS_NAME);
String proguardConfig =
keepMainProguardConfiguration(Regress78493232Dump_WithPhi.CLASS_NAME) + "-dontobfuscate\n";
ProcessResult r8ShakenResult =
diff --git a/src/test/java/com/android/tools/r8/regress/b80262475/B80262475.java b/src/test/java/com/android/tools/r8/regress/b80262475/B80262475.java
new file mode 100644
index 0000000..b3fc08f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b80262475/B80262475.java
@@ -0,0 +1,69 @@
+// 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.regress.b80262475;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static junit.framework.TestCase.assertTrue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.LongToInt;
+import com.android.tools.r8.code.Return;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.code.ReturnWide;
+import com.android.tools.r8.code.Throw;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+class TestClass {
+ public static void f(long[] a) {
+ int i = (int) a[0];
+ a[i] = a[0];
+ }
+}
+
+public class B80262475 extends TestBase {
+
+ private boolean overlappingLongToIntInputAndOutput(Instruction instruction) {
+ if (instruction instanceof LongToInt) {
+ LongToInt longToInt = (LongToInt) instruction;
+ return longToInt.A == longToInt.B;
+ }
+ return false;
+ }
+
+ @Test
+ public void testLongToIntOverlap()
+ throws IOException, CompilationFailedException, ExecutionException {
+ MethodSubject method = getMethodSubject(AndroidApiLevel.L);
+ Instruction[] instructions = method.getMethod().getCode().asDexCode().instructions;
+ for (Instruction instruction : instructions) {
+ assertFalse(overlappingLongToIntInputAndOutput(instruction));
+ }
+ }
+
+ private MethodSubject getMethodSubject(AndroidApiLevel level)
+ throws CompilationFailedException, IOException, ExecutionException {
+ CodeInspector inspector = testForD8()
+ .addProgramClasses(TestClass.class)
+ .setMinApi(level)
+ .compile()
+ .inspector();
+ ClassSubject clazz = inspector.clazz(TestClass.class);
+ assertThat(clazz, isPresent());
+ MethodSubject method = clazz.method("void", "f", ImmutableList.of("long[]"));
+ assertThat(method, isPresent());
+ return method;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java
index fc31713..d70c724 100644
--- a/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java
+++ b/src/test/java/com/android/tools/r8/release/ShareCommonCodeOnDistinctPositionsTestRunner.java
@@ -10,12 +10,14 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidAppConsumers;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
import com.android.tools.r8.utils.codeinspector.LineNumberTable;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import it.unimi.dsi.fastutil.ints.IntCollection;
import java.io.IOException;
@@ -51,6 +53,8 @@
.setProgramConsumer(sink.wrapProgramConsumer(emptyConsumer(backend)))
.setDisableMinification(true)
.setDisableTreeShaking(true)
+ .addProguardConfiguration(
+ ImmutableList.of("-keepattributes LineNumberTable"), Origin.unknown())
.build(),
options -> options.lineNumberOptimization = LineNumberOptimization.OFF);
CodeInspector inspector = new CodeInspector(sink.build());
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index a84f585..7ec1613 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -105,7 +105,8 @@
AndroidApp app = readClassesAndAsmDump(CLASSES, ASM_CLASSES);
DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
AppView<? extends AppInfoWithSubtyping> appView =
- new AppView<>(new AppInfoWithSubtyping(application), GraphLense.getIdentityLense());
+ new AppView<>(
+ new AppInfoWithSubtyping(application), GraphLense.getIdentityLense(), options);
ExecutorService executor = Executors.newSingleThreadExecutor();
RootSet rootSet =
diff --git a/src/test/java/com/android/tools/r8/resource/DataResourceTest.java b/src/test/java/com/android/tools/r8/resource/DataResourceTest.java
index a58676d..81b8447 100644
--- a/src/test/java/com/android/tools/r8/resource/DataResourceTest.java
+++ b/src/test/java/com/android/tools/r8/resource/DataResourceTest.java
@@ -59,6 +59,8 @@
Path r8Out = temp.getRoot().toPath().resolve("r8out.jar");
R8Command.Builder builder =
R8Command.builder()
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.addProgramFiles(inputJar)
.addProguardConfiguration(ImmutableList.of("-keepdirectories"), Origin.unknown())
.setProgramConsumer(
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 fff9ac3..4b88194 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
@@ -166,6 +166,8 @@
R8Command command =
ToolHelper.prepareR8CommandBuilder(readClasses(ClassWithAssertions.class))
.setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.addLibraryFiles(ToolHelper.getJava8RuntimeJar())
.setOutput(outputJar, OutputMode.ClassFile)
.build();
@@ -213,6 +215,8 @@
ToolHelper.getClassAsBytes(ChromuimAssertionHookMock.class), Origin.unknown())
.setProgramConsumer(DexIndexedConsumer.emptyConsumer())
.setMode(CompilationMode.DEBUG)
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.build());
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
index fc40d4f..a6c0d57 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
@@ -60,7 +60,7 @@
builder.addLibraryResourceProvider(PreloadedClassFileProvider.fromClassData(
"Lcom/android/tools/r8/rewrite/staticvalues/inlibraries/LibraryClass;",
compileTimeLibrary.buildClasses().get(0)));
- AndroidApp app = compileWithR8(builder.build());
+ AndroidApp app = compileWithR8(builder.build(), "-dontshrink\n-dontobfuscate\n");
// Build the third version of LibraryClass
SmaliBuilder runtimeLibrary = new SmaliBuilder(LibraryClass.class.getCanonicalName());
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
index 4d00bba..24789b0 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
@@ -130,6 +130,8 @@
AndroidApp app =
ToolHelper.runR8(
ToolHelper.prepareR8CommandBuilder(builder.build(), emptyConsumer(backend))
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.addLibraryFiles(runtimeJar(backend))
.build());
@@ -198,6 +200,8 @@
AndroidApp app =
ToolHelper.runR8(
ToolHelper.prepareR8CommandBuilder(builder.build(), emptyConsumer(backend))
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.addLibraryFiles(runtimeJar(backend))
.build());
@@ -275,6 +279,8 @@
AndroidApp app =
ToolHelper.runR8(
ToolHelper.prepareR8CommandBuilder(appBuilder.build(), emptyConsumer(backend))
+ .setDisableTreeShaking(true)
+ .setDisableMinification(true)
.addLibraryFiles(runtimeJar(backend))
.build());
diff --git a/src/test/java/com/android/tools/r8/shaking/EnclosingMethodTest.java b/src/test/java/com/android/tools/r8/shaking/EnclosingMethodTest.java
index c14cef5..4e121a8 100644
--- a/src/test/java/com/android/tools/r8/shaking/EnclosingMethodTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/EnclosingMethodTest.java
@@ -49,6 +49,8 @@
private final boolean enableMinification;
private Collection<Path> classPaths;
private static final String JAVA_OUTPUT = "-Returned-null-" + System.lineSeparator();
+ private static final String OUTPUT_WITH_SHRUNK_ATTRIBUTES =
+ "com.android.tools.r8.shaking.GetNameClass$1" + System.lineSeparator();
private static final Class<?> MAIN = GetNameMain.class;
@Parameterized.Parameters(name = "Backend: {0} minification: {1}")
@@ -88,18 +90,10 @@
.addKeepRules("-keep class **.GetName*")
.addKeepRules("-keepattributes InnerClasses,EnclosingMethod");
if (!enableMinification) {
- builder.addKeepRules("-dontobfuscate");
+ builder.noMinification();
}
R8TestRunResult result = builder.run(MAIN);
- if (backend == Backend.DEX) {
- if (ToolHelper.getDexVm().getVersion().isNewerThan(Version.V4_4_4)
- && ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V6_0_1)) {
- result.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
- return;
- }
- }
-
- result.assertSuccessWithOutput(JAVA_OUTPUT);
+ result.assertSuccessWithOutput(OUTPUT_WITH_SHRUNK_ATTRIBUTES);
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index de10b45..ff84486 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -1851,6 +1851,30 @@
}
@Test
+ public void parse_dump_withoutFile() throws Exception {
+ Path proguardConfig = writeTextToTempFile(
+ "-dump"
+ );
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
+ parser.parse(proguardConfig);
+ checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
+ "Ignoring", "-dump");
+ }
+
+ @Test
+ public void parse_dump_withFile() throws Exception {
+ Path proguardConfig = writeTextToTempFile(
+ "-dump class_files.txt"
+ );
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
+ parser.parse(proguardConfig);
+ checkDiagnostics(handler.warnings, proguardConfig, 1, 1,
+ "Ignoring", "-dump");
+ }
+
+ @Test
public void parse_android() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-android"
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
index 36993e7..279194a 100644
--- a/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/ReflectiveAnnotationUseTest.java
@@ -13,18 +13,20 @@
import com.android.tools.r8.KotlinTestBase;
import com.android.tools.r8.R8TestBuilder;
-import com.android.tools.r8.R8TestRunResult;
-import com.android.tools.r8.TestRunResult;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.graph.DexAnnotationElement;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -34,6 +36,7 @@
private static final String FOLDER = "internal_annotation";
private static final String MAIN_CLASS_NAME = "internal_annotation.MainKt";
private static final String ANNOTATION_NAME = "internal_annotation.Annotation";
+ private static final String IMPL_CLASS_NAME = "internal_annotation.Impl";
private static final String KEEP_ANNOTATIONS = "-keepattributes *Annotation*";
private static final String JAVA_OUTPUT = StringUtils.lines(
@@ -46,6 +49,13 @@
"null"
);
+ private static final Map<String, String> EXPECTED_ANNOTATION_VALUES = ImmutableMap.of(
+ "f1", "2",
+ "f2", "Impl::Annotation::field2",
+ "f3", "3]",
+ "f4", "field4]"
+ );
+
private final Backend backend;
private final boolean minify;
@@ -87,11 +97,9 @@
if (!minify) {
builder.noMinification();
}
- TestRunResult result = builder.run(MAIN_CLASS_NAME);
- result.assertSuccessWithOutput(JAVA_OUTPUT);
-
- CodeInspector inspector = result.inspector();
+ CodeInspector inspector =
+ builder.run(MAIN_CLASS_NAME).assertSuccessWithOutput(JAVA_OUTPUT).inspector();
ClassSubject clazz = inspector.clazz(ANNOTATION_NAME);
assertThat(clazz, isPresent());
assertThat(clazz, not(isRenamed()));
@@ -107,6 +115,12 @@
MethodSubject f4 = clazz.uniqueMethodWithName("f4");
assertThat(f4, isPresent());
assertThat(f4, not(isRenamed()));
+
+ ClassSubject impl = inspector.clazz(IMPL_CLASS_NAME);
+ assertThat(impl, isPresent());
+ AnnotationSubject anno = impl.annotation(ANNOTATION_NAME);
+ assertThat(anno, isPresent());
+ inspectAnnotationInstantiation(anno, ImmutableSet.of("f1", "f2", "f3", "f4"));
}
@Test
@@ -124,24 +138,9 @@
if (!minify) {
builder.noMinification();
}
- R8TestRunResult result = builder.run(MAIN_CLASS_NAME);
- if (backend == Backend.DEX) {
- if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)) {
- result.assertFailureWithErrorThatMatches(
- containsString("failure in processEncodedAnnotation"));
- return;
- }
- if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V6_0_1)) {
- result.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
- return;
- }
- result.assertSuccessWithOutput(OUTPUT_WITHOUT_ANNOTATION);
- } else {
- result.assertSuccessWithOutput(JAVA_OUTPUT);
- }
-
- CodeInspector inspector = result.inspector();
+ CodeInspector inspector =
+ builder.run(MAIN_CLASS_NAME).assertSuccessWithOutput(JAVA_OUTPUT).inspector();
ClassSubject clazz = inspector.clazz(ANNOTATION_NAME);
assertThat(clazz, isPresent());
assertEquals(minify, clazz.isRenamed());
@@ -155,6 +154,12 @@
assertThat(f3, not(isPresent()));
MethodSubject f4 = clazz.uniqueMethodWithName("f4");
assertThat(f4, not(isPresent()));
+
+ ClassSubject impl = inspector.clazz(IMPL_CLASS_NAME);
+ assertThat(impl, isPresent());
+ AnnotationSubject anno = impl.annotation(ANNOTATION_NAME);
+ assertThat(anno, isPresent());
+ inspectAnnotationInstantiation(anno, ImmutableSet.of("f1", "f2"));
}
@Test
@@ -167,24 +172,9 @@
if (!minify) {
builder.noMinification();
}
- R8TestRunResult result = builder.run(MAIN_CLASS_NAME);
- if (backend == Backend.DEX) {
- if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)) {
- result.assertFailureWithErrorThatMatches(
- containsString("failure in processEncodedAnnotation"));
- return;
- }
- if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V6_0_1)) {
- result.assertFailureWithErrorThatMatches(containsString("IncompatibleClassChangeError"));
- return;
- }
- result.assertSuccessWithOutput(OUTPUT_WITHOUT_ANNOTATION);
- } else {
- result.assertSuccessWithOutput(JAVA_OUTPUT);
- }
-
- CodeInspector inspector = result.inspector();
+ CodeInspector inspector =
+ builder.run(MAIN_CLASS_NAME).assertSuccessWithOutput(JAVA_OUTPUT).inspector();
ClassSubject clazz = inspector.clazz(ANNOTATION_NAME);
assertThat(clazz, isPresent());
assertEquals(minify, clazz.isRenamed());
@@ -198,6 +188,12 @@
assertThat(f3, not(isPresent()));
MethodSubject f4 = clazz.uniqueMethodWithName("f4");
assertThat(f4, not(isPresent()));
+
+ ClassSubject impl = inspector.clazz(IMPL_CLASS_NAME);
+ assertThat(impl, isPresent());
+ AnnotationSubject anno = impl.annotation(ANNOTATION_NAME);
+ assertThat(anno, isPresent());
+ inspectAnnotationInstantiation(anno, ImmutableSet.of("f1", "f2"));
}
@Test
@@ -209,10 +205,9 @@
if (!minify) {
builder.noMinification();
}
- TestRunResult result = builder.run(MAIN_CLASS_NAME);
- result.assertSuccessWithOutput(OUTPUT_WITHOUT_ANNOTATION);
- CodeInspector inspector = result.inspector();
+ CodeInspector inspector =
+ builder.run(MAIN_CLASS_NAME).assertSuccessWithOutput(OUTPUT_WITHOUT_ANNOTATION).inspector();
ClassSubject clazz = inspector.clazz(ANNOTATION_NAME);
assertThat(clazz, isPresent());
assertEquals(minify, clazz.isRenamed());
@@ -224,6 +219,25 @@
assertThat(f3, not(isPresent()));
MethodSubject f4 = clazz.uniqueMethodWithName("f4");
assertThat(f4, not(isPresent()));
+
+ ClassSubject impl = inspector.clazz(IMPL_CLASS_NAME);
+ assertThat(impl, isPresent());
+ AnnotationSubject anno = impl.annotation(ANNOTATION_NAME);
+ assertThat(anno, not(isPresent()));
+ }
+
+ private void inspectAnnotationInstantiation(
+ AnnotationSubject annotationSubject, Set<String> expectedFields) {
+ int count = 0;
+ for (DexAnnotationElement element : annotationSubject.getAnnotation().elements) {
+ String fieldName = element.name.toString();
+ if (expectedFields.contains(fieldName)) {
+ count++;
+ String expectedValue = EXPECTED_ANNOTATION_VALUES.get(fieldName);
+ assertThat(element.value.toString(), containsString(expectedValue));
+ }
+ }
+ assertEquals(expectedFields.size(), count);
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index 07a9493..6504169 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -108,7 +108,7 @@
builder.addProguardConfiguration(
ImmutableList.of(keepMainProguardConfiguration(mainClass, true, false)), Origin.unknown());
builder.addProguardConfiguration(
- ImmutableList.of("-keep class " + annotationClass.getCanonicalName() + " { }"),
+ ImmutableList.of("-keep class " + annotationClass.getCanonicalName() + " { *; }"),
Origin.unknown());
if (keepAnnotations) {
builder.addProguardConfiguration(ImmutableList.of("-keepattributes *Annotation*"),
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/applymapping/IfRuleWithApplyMappingTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/applymapping/IfRuleWithApplyMappingTest.java
new file mode 100644
index 0000000..49679c4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/applymapping/IfRuleWithApplyMappingTest.java
@@ -0,0 +1,79 @@
+// 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.shaking.ifrule.applymapping;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class IfRuleWithApplyMappingTest extends TestBase {
+
+ private static Path mappingFile;
+
+ @Before
+ public void setup() throws Exception {
+ // Mapping file that describes that Runnable has been renamed to A.
+ mappingFile = temp.newFolder().toPath().resolve("mapping.txt");
+ FileUtils.writeTextFile(
+ mappingFile, Runnable.class.getTypeName() + " -> " + A.class.getTypeName() + ":");
+ }
+
+ @Test
+ public void testProguard() throws Exception {
+ testForProguard()
+ .addProgramClasses(IfRuleWithApplyMappingTestClass.class)
+ .addKeepMainRule(IfRuleWithApplyMappingTestClass.class)
+ .addKeepRules(
+ "-if class " + IfRuleWithApplyMappingTestClass.class.getTypeName(),
+ "-keep class " + IfRuleWithApplyMappingTestClass.class.getTypeName() + " {",
+ " public void method(" + Runnable.class.getTypeName() + ");",
+ "}",
+ "-applymapping " + mappingFile.toAbsolutePath())
+ .compile()
+ .inspect(this::inspect);
+ }
+
+ @Ignore("b/117403482")
+ @Test
+ public void testR8() throws Exception {
+ testForR8(Backend.DEX)
+ .addProgramClasses(IfRuleWithApplyMappingTestClass.class)
+ .addKeepMainRule(IfRuleWithApplyMappingTestClass.class)
+ .addKeepRules(
+ "-if class " + IfRuleWithApplyMappingTestClass.class.getTypeName(),
+ "-keep class " + IfRuleWithApplyMappingTestClass.class.getTypeName() + " {",
+ " public void method(" + Runnable.class.getTypeName() + ");",
+ "}",
+ "-applymapping " + mappingFile.toAbsolutePath())
+ .compile()
+ .inspect(this::inspect);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ MethodSubject methodSubject =
+ inspector.clazz(IfRuleWithApplyMappingTestClass.class).uniqueMethodWithName("method");
+ assertThat(methodSubject, isPresent());
+ assertEquals(
+ A.class.getTypeName(), methodSubject.getMethod().method.proto.parameters.toSourceString());
+ }
+}
+
+class IfRuleWithApplyMappingTestClass {
+
+ public static void main(String[] args) {}
+
+ public void method(Runnable obj) {}
+}
+
+class A {}
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptMethodTest.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptMethodTest.java
new file mode 100644
index 0000000..35083c7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptMethodTest.java
@@ -0,0 +1,81 @@
+// 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.shaking.keptgraph;
+
+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.NeverInline;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.shaking.CollectingGraphConsumer;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.graphinspector.GraphInspector;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+class Main {
+
+ public static void foo() {
+ bar();
+ }
+
+ @NeverInline
+ public static void bar() {
+ System.out.println("called bar");
+ }
+
+ @NeverInline
+ public static void baz() {
+ System.out.println("called baz");
+ }
+}
+
+@RunWith(Parameterized.class)
+public class KeptMethodTest extends TestBase {
+
+ private final Backend backend;
+
+ @Parameters(name = "{0}")
+ public static Backend[] data() {
+ return Backend.values();
+ }
+
+ public KeptMethodTest(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void testKeptMethod()
+ throws NoSuchMethodException, CompilationFailedException, IOException, ExecutionException {
+ MethodReference fooMethod = Reference.methodFromMethod(Main.class.getMethod("foo"));
+ MethodReference barMethod = Reference.methodFromMethod(Main.class.getMethod("bar"));
+ MethodReference bazMethod = Reference.methodFromMethod(Main.class.getMethod("baz"));
+
+ CollectingGraphConsumer graphConsumer = new CollectingGraphConsumer(null);
+ R8TestBuilder builder =
+ testForR8(backend)
+ .setKeptGraphConsumer(graphConsumer)
+ .enableInliningAnnotations()
+ .addProgramClasses(Main.class)
+ .addKeepMethodRules(fooMethod);
+
+ CodeInspector codeInspector = builder.compile().inspector();
+ GraphInspector graphInspector = new GraphInspector(graphConsumer, codeInspector);
+
+ // The only root should be the method rule.
+ assertEquals(1, graphInspector.getRoots().size());
+ assertTrue(graphInspector.isPresent(fooMethod));
+ assertTrue(graphInspector.isRenamed(barMethod));
+ assertFalse(graphInspector.isPresent(bazMethod));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java b/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
index 48cdb05..95963a0 100644
--- a/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/proxy/MockitoTest.java
@@ -48,7 +48,7 @@
Path flagToKeepTestRunner = Paths.get(ToolHelper.EXAMPLES_DIR, M_I_PKG, "keep-rules.txt");
R8TestBuilder builder = testForR8(backend)
.addProgramFiles(MOCKITO_INTERFACE_JAR)
- .addKeepRules(flagToKeepTestRunner);
+ .addKeepRuleFiles(flagToKeepTestRunner);
if (!minify) {
builder.noMinification();
}
@@ -65,7 +65,7 @@
Paths.get(ToolHelper.EXAMPLES_DIR, M_I_PKG, "keep-rules-conditional-on-mock.txt");
R8TestBuilder builder = testForR8(backend)
.addProgramFiles(MOCKITO_INTERFACE_JAR)
- .addKeepRules(flagToKeepInterfaceConditionally);
+ .addKeepRuleFiles(flagToKeepInterfaceConditionally);
if (!minify) {
builder.noMinification();
}
diff --git a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
index e6de124..867d346 100644
--- a/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/whyareyoukeeping/WhyAreYouKeepingTest.java
@@ -6,6 +6,8 @@
import static org.junit.Assert.assertEquals;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.shaking.WhyAreYouKeepingConsumer;
@@ -20,17 +22,38 @@
class A {
+ public void foo() {
+ bar();
+ }
+
+ @NeverInline
+ public void bar() {
+ baz();
+ }
+
+ @NeverInline
+ public void baz() {
+ System.out.println("called baz");
+ }
}
@RunWith(Parameterized.class)
public class WhyAreYouKeepingTest extends TestBase {
public static final String expected =
- StringUtils.joinLines(
+ StringUtils.lines(
"com.android.tools.r8.shaking.whyareyoukeeping.A",
"|- is referenced in keep rule:",
- "| -keep class com.android.tools.r8.shaking.whyareyoukeeping.A { *; }",
- "");
+ "| -keep class com.android.tools.r8.shaking.whyareyoukeeping.A { foo(); }");
+
+ // TODO(b/120959039): This should be "- is invoked from:\n com.android.....A.bar()" etc.
+ public static final String expectedPathToBaz =
+ StringUtils.lines(
+ "void com.android.tools.r8.shaking.whyareyoukeeping.A.baz()",
+ "|- is reachable from:",
+ "| com.android.tools.r8.shaking.whyareyoukeeping.A",
+ "|- is referenced in keep rule:",
+ "| -keep class com.android.tools.r8.shaking.whyareyoukeeping.A { foo(); }");
@Parameters(name = "{0}")
public static Backend[] parameters() {
@@ -48,11 +71,8 @@
ByteArrayOutputStream baos = new ByteArrayOutputStream();
testForR8(backend)
.addProgramClasses(A.class)
- .addKeepClassAndMembersRules(A.class)
+ .addKeepMethodRules(Reference.methodFromMethod(A.class.getMethod("foo")))
.addKeepRules("-whyareyoukeeping class " + A.class.getTypeName())
- // Clear the default library and ignore missing classes to avoid processing the library.
- .addLibraryFiles()
- .addOptionsModification(o -> o.ignoreMissingClasses = true)
// Redirect the compilers stdout to intercept the '-whyareyoukeeping' output
.redirectStdOut(new PrintStream(baos))
.compile();
@@ -65,10 +85,7 @@
WhyAreYouKeepingConsumer graphConsumer = new WhyAreYouKeepingConsumer(null);
testForR8(backend)
.addProgramClasses(A.class)
- .addKeepClassAndMembersRules(A.class)
- // Clear the default library and ignore missing classes to avoid processing the library.
- .addLibraryFiles()
- .addOptionsModification(o -> o.ignoreMissingClasses = true)
+ .addKeepMethodRules(Reference.methodFromMethod(A.class.getMethod("foo")))
.setKeptGraphConsumer(graphConsumer)
.compile();
@@ -77,4 +94,37 @@
String output = new String(baos.toByteArray(), StandardCharsets.UTF_8);
assertEquals(expected, output);
}
+
+ @Test
+ public void testWhyAreYouKeepingPathViaProguardConfig()
+ throws NoSuchMethodException, CompilationFailedException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ testForR8(backend)
+ .addProgramClasses(A.class)
+ .enableInliningAnnotations()
+ .addKeepRules("-whyareyoukeeping class " + A.class.getTypeName() + " { baz(); }")
+ .addKeepMethodRules(Reference.methodFromMethod(A.class.getMethod("foo")))
+ // Redirect the compilers stdout to intercept the '-whyareyoukeeping' output
+ .redirectStdOut(new PrintStream(baos))
+ .compile();
+ String output = new String(baos.toByteArray(), StandardCharsets.UTF_8);
+ assertEquals(expected + expectedPathToBaz, output);
+ }
+
+ @Test
+ public void testWhyAreYouKeepingPathViaConsumer()
+ throws NoSuchMethodException, CompilationFailedException {
+ WhyAreYouKeepingConsumer graphConsumer = new WhyAreYouKeepingConsumer(null);
+ testForR8(backend)
+ .addProgramClasses(A.class)
+ .setKeptGraphConsumer(graphConsumer)
+ .enableInliningAnnotations()
+ .addKeepMethodRules(Reference.methodFromMethod(A.class.getMethod("foo")))
+ .compile();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ graphConsumer.printWhyAreYouKeeping(
+ Reference.methodFromMethod(A.class.getMethod("baz")), new PrintStream(baos));
+ String output = new String(baos.toByteArray(), StandardCharsets.UTF_8);
+ assertEquals(expectedPathToBaz, output);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 4242ca5..dfab990 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -14,7 +14,6 @@
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.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
diff --git a/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java b/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
index c44d1cf..d578ae2 100644
--- a/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
+++ b/src/test/java/com/android/tools/r8/utils/ArrayUtilsTest.java
@@ -3,100 +3,177 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
import java.util.Map;
+import java.util.function.Function;
import org.junit.Test;
public class ArrayUtilsTest {
- @Test
- public void testCopyWithSparseChanges_identical() throws Exception {
- int size = 3;
+ private Integer[] createInputData(int size) {
Integer[] input = new Integer[size];
for (int i = 0; i < size; i++) {
input[i] = i;
}
+ return input;
+ }
+
+ @Test
+ public void testCopyWithSparseChanges_identical() {
+ int size = 3;
+ Integer[] input = createInputData(size);
Integer[] output =
ArrayUtils.copyWithSparseChanges(Integer[].class, input, new Int2IntArrayMap());
assertNotEquals(input, output);
for (int i = 0; i < size; i++) {
- assertTrue(i == output[i]);
+ assertEquals(i, (int) output[i]);
}
}
@Test
- public void testCopyWithSparseChanges_oneChangeAtBeginning() throws Exception {
+ public void testCopyWithSparseChanges_oneChangeAtBeginning() {
int size = 3;
- Integer[] input = new Integer[size];
- for (int i = 0; i < size; i++) {
- input[i] = i;
- }
+ Integer[] input = createInputData(size);
Map<Integer, Integer> changes = new Int2IntArrayMap();
changes.put(0, size);
Integer[] output = ArrayUtils.copyWithSparseChanges(Integer[].class, input, changes);
assertNotEquals(input, output);
- assertTrue(size == output[0]);
+ assertEquals(size, (int) output[0]);
for (int i = 1; i < size; i++) {
- assertTrue(i == output[i]);
+ assertEquals(i, (int) output[i]);
}
}
@Test
- public void testCopyWithSparseChanges_oneChangeInMiddle() throws Exception {
+ public void testCopyWithSparseChanges_oneChangeInMiddle() {
int size = 3;
- Integer[] input = new Integer[size];
- for (int i = 0; i < size; i++) {
- input[i] = i;
- }
+ Integer[] input = createInputData(size);
Map<Integer, Integer> changes = new Int2IntArrayMap();
changes.put(1, size);
Integer[] output = ArrayUtils.copyWithSparseChanges(Integer[].class, input, changes);
assertNotEquals(input, output);
- assertTrue(size == output[1]);
+ assertEquals(size, (int) output[1]);
for (int i = 0; i < size; i++) {
if (i == 1) {
continue;
}
- assertTrue(i == output[i]);
+ assertEquals(i, (int) output[i]);
}
}
@Test
- public void testCopyWithSparseChanges_oneChangeAtEnd() throws Exception {
+ public void testCopyWithSparseChanges_oneChangeAtEnd() {
int size = 3;
- Integer[] input = new Integer[size];
- for (int i = 0; i < size; i++) {
- input[i] = i;
- }
+ Integer[] input = createInputData(size);
Map<Integer, Integer> changes = new Int2IntArrayMap();
changes.put(2, size);
Integer[] output = ArrayUtils.copyWithSparseChanges(Integer[].class, input, changes);
assertNotEquals(input, output);
- assertTrue(size == output[2]);
+ assertEquals(size, (int) output[2]);
for (int i = 0; i < size - 1; i++) {
- assertTrue(i == output[i]);
+ assertEquals(i, (int) output[i]);
}
}
@Test
- public void testCopyWithSparseChanges_twoChangesAtEnds() throws Exception {
+ public void testCopyWithSparseChanges_twoChangesAtEnds() {
int size = 3;
- Integer[] input = new Integer[size];
- for (int i = 0; i < size; i++) {
- input[i] = i;
- }
+ Integer[] input = createInputData(size);
Map<Integer, Integer> changes = new Int2IntArrayMap();
changes.put(0, size);
changes.put(2, size);
Integer[] output = ArrayUtils.copyWithSparseChanges(Integer[].class, input, changes);
assertNotEquals(input, output);
- assertTrue(size == output[0]);
- assertFalse(size == output[1]);
- assertTrue(size == output[2]);
+ assertEquals(size, (int) output[0]);
+ assertNotEquals(size, (int) output[1]);
+ assertEquals(size, (int) output[2]);
+ }
+
+ @Test
+ public void testFilter_identity() {
+ int size = 3;
+ Integer[] input = createInputData(size);
+ Integer[] output = ArrayUtils.filter(Integer[].class, input, x -> true);
+ assertEquals(input, output);
+ }
+
+ @Test
+ public void testFilter_dropOdd() {
+ int size = 3;
+ Integer[] input = createInputData(size);
+ Integer[] output = ArrayUtils.filter(Integer[].class, input, x -> x % 2 == 0);
+ assertNotEquals(input, output);
+ assertEquals(2, output.length);
+ assertEquals(0, (int) output[0]);
+ assertEquals(2, (int) output[1]);
+ }
+
+ @Test
+ public void testFilter_dropAll() {
+ int size = 3;
+ Integer[] input = createInputData(size);
+ Integer[] output = ArrayUtils.filter(Integer[].class, input, x -> false);
+ assertNotEquals(input, output);
+ assertEquals(0, output.length);
+ }
+
+ @Test
+ public void testMap_identity() {
+ int size = 3;
+ Integer[] input = createInputData(size);
+ Integer[] output = ArrayUtils.map(Integer[].class, input, Function.identity());
+ assertEquals(input, output);
+ }
+
+ @Test
+ public void testMap_dropOdd() {
+ int size = 3;
+ Integer[] input = createInputData(size);
+ Integer[] output = ArrayUtils.map(Integer[].class, input, x -> x % 2 != 0 ? null : x);
+ assertNotEquals(input, output);
+ assertEquals(2, output.length);
+ assertEquals(0, (int) output[0]);
+ assertEquals(2, (int) output[1]);
+ }
+
+ @Test
+ public void testMap_dropAll() {
+ int size = 3;
+ Integer[] input = createInputData(size);
+ Integer[] output = ArrayUtils.map(Integer[].class, input, x -> null);
+ assertNotEquals(input, output);
+ assertEquals(0, output.length);
+ }
+
+ @Test
+ public void testMap_double() {
+ int size = 3;
+ Integer[] input = createInputData(size);
+ Integer[] output = ArrayUtils.map(Integer[].class, input, x -> 2 * x);
+ assertNotEquals(input, output);
+ assertEquals(size, output.length);
+ for (int i = 0; i < size; i++) {
+ assertEquals(i * 2, (int) output[i]);
+ }
+ }
+
+ @Test
+ public void testMap_double_onlyOdd() {
+ int size = 3;
+ Integer[] input = createInputData(size);
+ Integer[] output = ArrayUtils.map(Integer[].class, input, x -> x % 2 != 0 ? 2 * x : x);
+ assertNotEquals(input, output);
+ assertEquals(size, output.length);
+ for (int i = 0; i < size; i++) {
+ if (i % 2 != 0) {
+ assertEquals(i * 2, (int) output[i]);
+ } else {
+ assertEquals(i, (int) output[i]);
+ }
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 1c9fe9f..f5ff639 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -6,7 +6,10 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.utils.ListUtils;
import com.google.common.collect.ImmutableList;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -23,6 +26,13 @@
return builder.build();
}
+ public MethodSubject method(MethodReference method) {
+ return method(
+ (method.getReturnType() == null ? "void" : method.getReturnType().getTypeName()),
+ method.getMethodName(),
+ ListUtils.map(method.getFormalTypes(), TypeReference::getTypeName));
+ }
+
public MethodSubject method(Method method) {
List<String> parameters = new ArrayList<>();
for (Class<?> parameterType : method.getParameterTypes()) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 660156a..0b73966 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -27,6 +27,9 @@
import com.android.tools.r8.naming.signature.GenericSignatureAction;
import com.android.tools.r8.naming.signature.GenericSignatureParser;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
@@ -223,11 +226,17 @@
}
public ClassSubject clazz(Class clazz) {
- return clazz(clazz.getTypeName());
+ return clazz(Reference.classFromClass(clazz));
}
/** Lookup a class by name. This allows both original and obfuscated names. */
public ClassSubject clazz(String name) {
+ return clazz(Reference.classFromTypeName(name));
+ }
+
+ public ClassSubject clazz(ClassReference reference) {
+ String descriptor = reference.getDescriptor();
+ String name = DescriptorUtils.descriptorToJavaType(descriptor);
ClassNamingForNameMapper naming = null;
if (mapping != null) {
String obfuscated = originalToObfuscatedMapping.get(name);
@@ -266,8 +275,14 @@
return builder.build();
}
+
+
public MethodSubject method(Method method) {
- ClassSubject clazz = clazz(method.getDeclaringClass());
+ return method(Reference.methodFromMethod(method));
+ }
+
+ public MethodSubject method(MethodReference method) {
+ ClassSubject clazz = clazz(method.getHolderClass());
if (!clazz.isPresent()) {
return new AbsentMethodSubject();
}
diff --git a/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
new file mode 100644
index 0000000..b138e50
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/graphinspector/GraphInspector.java
@@ -0,0 +1,80 @@
+// 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.graphinspector;
+
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.experimental.graphinfo.ClassGraphNode;
+import com.android.tools.r8.experimental.graphinfo.FieldGraphNode;
+import com.android.tools.r8.experimental.graphinfo.GraphEdgeInfo;
+import com.android.tools.r8.experimental.graphinfo.GraphNode;
+import com.android.tools.r8.experimental.graphinfo.MethodGraphNode;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.FieldReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.shaking.CollectingGraphConsumer;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class GraphInspector {
+
+ private final CollectingGraphConsumer consumer;
+ private final CodeInspector inspector;
+
+ private final Set<GraphNode> roots = new HashSet<>();
+ private final Map<ClassReference, ClassGraphNode> classes;
+ private final Map<MethodReference, MethodGraphNode> methods;
+ private final Map<FieldReference, FieldGraphNode> fields;
+
+ public GraphInspector(CollectingGraphConsumer consumer, CodeInspector inspector) {
+ this.consumer = consumer;
+ this.inspector = inspector;
+
+ Set<GraphNode> targets = consumer.getTargets();
+ classes = new IdentityHashMap<>(targets.size());
+ methods = new IdentityHashMap<>(targets.size());
+ fields = new IdentityHashMap<>(targets.size());
+
+ for (GraphNode target : targets) {
+ if (target instanceof ClassGraphNode) {
+ ClassGraphNode node = (ClassGraphNode) target;
+ classes.put(node.getReference(), node);
+ } else if (target instanceof MethodGraphNode) {
+ MethodGraphNode node = (MethodGraphNode) target;
+ methods.put(node.getReference(), node);
+ } else if (target instanceof FieldGraphNode) {
+ FieldGraphNode node = (FieldGraphNode) target;
+ fields.put(node.getReference(), node);
+ } else {
+ throw new Unimplemented("Incomplet support for graph node type: " + target.getClass());
+ }
+ Map<GraphNode, Set<GraphEdgeInfo>> sources = consumer.getSourcesTargeting(target);
+ for (GraphNode source : sources.keySet()) {
+ if (!targets.contains(source)) {
+ roots.add(source);
+ }
+ }
+ }
+ }
+
+ public boolean isRenamed(MethodReference method) {
+ assert isPresent(method);
+ return inspector.method(method).isRenamed();
+ }
+
+ public boolean isPresent(MethodReference method) {
+ if (methods.containsKey(method)) {
+ assert inspector.method(method).isPresent();
+ return true;
+ }
+ return false;
+ }
+
+ public Set<GraphNode> getRoots() {
+ return Collections.unmodifiableSet(roots);
+ }
+}
diff --git a/src/test/kotlinR8TestResources/minify_enum/main.kt b/src/test/kotlinR8TestResources/minify_enum/main.kt
new file mode 100644
index 0000000..d35ecf5
--- /dev/null
+++ b/src/test/kotlinR8TestResources/minify_enum/main.kt
@@ -0,0 +1,16 @@
+// 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 minify_enum
+
+enum class MinifyEnum(
+ val nullableStr1: String?,
+ val nullableStr2: String?,
+ val number3: String
+) {
+ UNKNOWN(null, null, "")
+}
+
+fun main(args: Array<String>) {
+ val a = MinifyEnum.UNKNOWN
+}
diff --git a/tests/r8_api_usage_sample.jar b/tests/r8_api_usage_sample.jar
index 9b7e3f5..aa8aac2 100644
--- a/tests/r8_api_usage_sample.jar
+++ b/tests/r8_api_usage_sample.jar
Binary files differ
diff --git a/tools/internal_test.py b/tools/internal_test.py
index 7d126b0..4713daf 100755
--- a/tools/internal_test.py
+++ b/tools/internal_test.py
@@ -293,7 +293,8 @@
git_hash = utils.get_HEAD_sha1()
log('Running once with hash %s' % git_hash)
# Run test.py internal testing.
- cmd = ['tools/test.py', '--only_internal']
+ # TODO(mkrogh) Change this to --r8lib when we have it working with dependencies relocated.
+ cmd = ['tools/test.py', '--only_internal', '--r8lib_no_deps']
env = os.environ.copy()
# Bot does not have a lot of memory.
env['R8_GRADLE_CORES_PER_FORK'] = '8'
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 4b73b55..e2f70f0 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -8,7 +8,9 @@
import optparse
import subprocess
import sys
+import time
import utils
+import zipfile
import as_utils
@@ -24,40 +26,55 @@
# 'app_module': ... (default app)
# 'archives_base_name': ... (default same as app_module)
# 'flavor': ... (default no flavor)
+ # 'releaseTarget': ... (default <app_module>:assemble<flavor>Release
# },
'AnExplorer': {
+ 'app_id': 'dev.dworks.apps.anexplorer.pro',
'git_repo': 'https://github.com/1hakr/AnExplorer',
'flavor': 'googleMobilePro',
'signed-apk-name': 'AnExplorer-googleMobileProRelease-4.0.3.apk',
},
'AntennaPod': {
+ 'app_id': 'de.danoeh.antennapod',
'git_repo': 'https://github.com/AntennaPod/AntennaPod.git',
'flavor': 'play',
},
'apps-android-wikipedia': {
+ 'app_id': 'org.wikipedia',
'git_repo': 'https://github.com/wikimedia/apps-android-wikipedia',
'flavor': 'prod',
'signed-apk-name': 'app-prod-universal-release.apk'
},
+ 'friendlyeats-android': {
+ 'app_id': 'com.google.firebase.example.fireeats',
+ 'git_repo': 'https://github.com/firebase/friendlyeats-android.git'
+ },
'KISS': {
+ 'app_id': 'fr.neamar.kiss',
'git_repo': 'https://github.com/Neamar/KISS',
},
'materialistic': {
+ 'app_id': 'io.github.hidroh.materialistic',
'git_repo': 'https://github.com/hidroh/materialistic',
},
'Minimal-Todo': {
+ 'app_id': 'com.avjindersinghsekhon.minimaltodo',
'git_repo': 'https://github.com/avjinder/Minimal-Todo',
},
'NewPipe': {
+ 'app_id': 'org.schabi.newpipe',
'git_repo': 'https://github.com/TeamNewPipe/NewPipe',
},
'Simple-Calendar': {
+ 'app_id': 'com.simplemobiletools.calendar.pro',
'git_repo': 'https://github.com/SimpleMobileTools/Simple-Calendar',
'signed-apk-name': 'calendar-release.apk'
},
'tachiyomi': {
+ 'app_id': 'eu.kanade.tachiyomi',
'git_repo': 'https://github.com/sgjesse/tachiyomi.git',
'flavor': 'standard',
+ 'releaseTarget': 'app:assembleRelease',
},
# This does not build yet.
'muzei': {
@@ -75,6 +92,17 @@
android_build_tools = os.path.join(
android_home, 'build-tools', android_build_tools_version)
+# TODO(christofferqa): Do not rely on 'emulator-5554' name
+emulator_id = 'emulator-5554'
+
+def ComputeSizeOfDexFilesInApk(apk):
+ dex_size = 0
+ z = zipfile.ZipFile(apk, 'r')
+ for filename in z.namelist():
+ if filename.endswith('.dex'):
+ dex_size += z.getinfo(filename).file_size
+ return dex_size
+
def IsBuiltWithR8(apk):
script = os.path.join(utils.TOOLS_DIR, 'extractmarker.py')
return '~~R8' in subprocess.check_output(['python', script, apk]).strip()
@@ -98,6 +126,30 @@
os.remove(apk_dest)
os.rename(apk, apk_dest)
+def InstallApkOnEmulator(apk_dest):
+ subprocess.check_call(
+ ['adb', '-s', emulator_id, 'install', '-r', '-d', apk_dest])
+
+def WaitForEmulator():
+ stdout = subprocess.check_output(['adb', 'devices'])
+ if '{}\tdevice'.format(emulator_id) in stdout:
+ return
+
+ print('Emulator \'{}\' not connected; waiting for connection'.format(
+ emulator_id))
+
+ time_waited = 0
+ while True:
+ time.sleep(10)
+ time_waited += 10
+ stdout = subprocess.check_output(['adb', 'devices'])
+ if '{}\tdevice'.format(emulator_id) not in stdout:
+ print('... still waiting for connection')
+ if time_waited >= 5 * 60:
+ raise Exception('No emulator connected for 5 minutes')
+ else:
+ return
+
def BuildAppWithSelectedShrinkers(app, config, options):
git_repo = config['git_repo']
@@ -116,16 +168,38 @@
else:
as_utils.remove_r8_dependency(checkout_dir)
+ result_per_shrinker = {}
+
with utils.ChangedWorkingDirectory(checkout_dir):
for shrinker in SHRINKERS:
if options.shrinker is not None and shrinker != options.shrinker:
continue
- BuildAppWithShrinker(app, config, shrinker, checkout_dir, options)
+ apk_dest = None
+ result = {}
+ try:
+ apk_dest = BuildAppWithShrinker(
+ app, config, shrinker, checkout_dir, options)
+ dex_size = ComputeSizeOfDexFilesInApk(apk_dest)
+ result['apk_dest'] = apk_dest,
+ result['build_status'] = 'success'
+ result['dex_size'] = dex_size
+ except:
+ warn('Failed to build {} with {}'.format(app, shrinker))
+ result['build_status'] = 'failed'
+
+ if options.monkey:
+ if result.get('build_status') == 'success':
+ result['monkey_status'] = 'success' if RunMonkey(
+ app, config, apk_dest) else 'failed'
+
+ result_per_shrinker[shrinker] = result
if IsTrackedByGit('gradle.properties'):
GitCheckout('gradle.properties')
+ return result_per_shrinker
+
def BuildAppWithShrinker(app, config, shrinker, checkout_dir, options):
print('Building {} with {}'.format(app, shrinker))
@@ -153,8 +227,10 @@
env = os.environ.copy()
env['ANDROID_HOME'] = android_home
env['JAVA_OPTS'] = '-ea'
- releaseTarget = app_module + ':' + 'assemble' + (
- flavor.capitalize() if flavor else '') + 'Release'
+ releaseTarget = config.get('releaseTarget')
+ if not releaseTarget:
+ releaseTarget = app_module + ':' + 'assemble' + (
+ flavor.capitalize() if flavor else '') + 'Release'
cmd = ['./gradlew', '--no-daemon', 'clean', releaseTarget, '--stacktrace']
utils.PrintCmd(cmd)
@@ -197,11 +273,54 @@
assert IsBuiltWithR8(apk_dest) == ('r8' in shrinker), (
'Unexpected marker in generated APK for {}'.format(shrinker))
+ return apk_dest
+
+def RunMonkey(app, config, apk_dest):
+ WaitForEmulator()
+ InstallApkOnEmulator(apk_dest)
+
+ app_id = config.get('app_id')
+ number_of_events_to_generate = 50
+
+ stdout = subprocess.check_output(['adb', 'shell', 'monkey', '-p', app_id,
+ str(number_of_events_to_generate)])
+ return 'Events injected: {}'.format(number_of_events_to_generate) in stdout
+
+def LogResults(result_per_shrinker_per_app, options):
+ for app, result_per_shrinker in result_per_shrinker_per_app.iteritems():
+ print(app + ':')
+ baseline = result_per_shrinker.get('proguard', {}).get('dex_size', -1)
+ for shrinker, result in result_per_shrinker.iteritems():
+ build_status = result.get('build_status')
+ if build_status != 'success':
+ warn(' {}: {}'.format(shrinker, build_status))
+ else:
+ print(' {}:'.format(shrinker))
+ dex_size = result.get('dex_size')
+ if dex_size != baseline and baseline >= 0:
+ if dex_size < baseline:
+ success(' dex size: {} ({})'.format(
+ dex_size, dex_size - baseline))
+ elif dex_size > baseline:
+ warn(' dex size: {} ({})'.format(dex_size, dex_size - baseline))
+ else:
+ print(' dex size: {}'.format(dex_size))
+ if options.monkey:
+ monkey_status = result.get('monkey_status')
+ if monkey_status != 'success':
+ warn(' monkey: {}'.format(monkey_status))
+ else:
+ success(' monkey: {}'.format(monkey_status))
+
def ParseOptions(argv):
result = optparse.OptionParser()
result.add_option('--app',
help='What app to run on',
choices=APPS.keys())
+ result.add_option('--monkey',
+ help='Whether to install and run app(s) with monkey',
+ default=False,
+ action='store_true')
result.add_option('--sign_apks',
help='Whether the APKs should be signed',
default=False,
@@ -210,9 +329,9 @@
help='The shrinker to use (by default, all are run)',
choices=SHRINKERS)
result.add_option('--use_tot',
- help='Whether to use the ToT version of R8',
- default=False,
- action='store_true')
+ help='Whether to disable the use of the ToT version of R8',
+ default=True,
+ action='store_false')
return result.parse_args(argv)
def main(argv):
@@ -220,12 +339,28 @@
assert not options.use_tot or os.path.isfile(utils.R8_JAR), (
'Cannot build from ToT without r8.jar')
+ result_per_shrinker_per_app = {}
+
if options.app:
- BuildAppWithSelectedShrinkers(options.app, APPS.get(options.app), options)
+ result_per_shrinker_per_app[options.app] = BuildAppWithSelectedShrinkers(
+ options.app, APPS.get(options.app), options)
else:
for app, config in APPS.iteritems():
if not config.get('skip', False):
- BuildAppWithSelectedShrinkers(app, config, options)
+ result_per_shrinker_per_app[app] = BuildAppWithSelectedShrinkers(
+ app, config, options)
+
+ LogResults(result_per_shrinker_per_app, options)
+
+def success(message):
+ CGREEN = '\033[32m'
+ CEND = '\033[0m'
+ print(CGREEN + message + CEND)
+
+def warn(message):
+ CRED = '\033[91m'
+ CEND = '\033[0m'
+ print(CRED + message + CEND)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))