Merge "Extend docstrings+usages of PrintSeeds+PrintUses"
diff --git a/build.gradle b/build.gradle
index 7720cf3..7f41caa 100644
--- a/build.gradle
+++ b/build.gradle
@@ -61,7 +61,10 @@
'-Xep:MissingDefault:ERROR',
'-Xep:MultipleTopLevelClasses:ERROR',
'-Xep:NarrowingCompoundAssignment:ERROR',
- '-Xep:BoxedPrimitiveConstructor:ERROR']
+ '-Xep:BoxedPrimitiveConstructor:ERROR',
+ '-Xep:LogicalAssignment:ERROR',
+ '-Xep:FloatCast:ERROR',
+ '-Xep:ReturnValueIgnored:ERROR']
apply from: 'copyAdditionalJctfCommonFiles.gradle'
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index 9b1599f..7b17ec8 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -33,6 +32,7 @@
private Path proguardMapFile = null;
private boolean useSmali = false;
private boolean allInfo = false;
+ private boolean useIr;
@Override
Builder self() {
@@ -63,6 +63,11 @@
return this;
}
+ public Builder setUseIr(boolean useIr) {
+ this.useIr = useIr;
+ return this;
+ }
+
@Override
protected DisassembleCommand makeCommand() {
// If printing versions ignore everything else.
@@ -74,24 +79,26 @@
getOutputPath(),
proguardMapFile == null ? null : StringResource.fromFile(proguardMapFile),
allInfo,
- useSmali);
+ useSmali,
+ useIr);
}
}
- static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
- "Usage: disasm [options] <input-files>",
- " where <input-files> are dex files",
- " and options are:",
- " --all # Include all information in disassembly.",
- " --smali # Disassemble using smali syntax.",
- " --pg-map <file> # Proguard map <file> for mapping names.",
- " --output # Specify a file or directory to write to.",
- " --version # Print the version of r8.",
- " --help # Print this message."));
-
+ static final String USAGE_MESSAGE =
+ "Usage: disasm [options] <input-files>\n"
+ + " where <input-files> are dex files\n"
+ + " and options are:\n"
+ + " --all # Include all information in disassembly.\n"
+ + " --smali # Disassemble using smali syntax.\n"
+ + " --ir # Print IR before and after optimization.\n"
+ + " --pg-map <file> # Proguard map <file> for mapping names.\n"
+ + " --output # Specify a file or directory to write to.\n"
+ + " --version # Print the version of r8.\n"
+ + " --help # Print this message.";
private final boolean allInfo;
private final boolean useSmali;
+ private final boolean useIr;
public static Builder builder() {
return new Builder();
@@ -112,10 +119,12 @@
builder.setPrintHelp(true);
} else if (arg.equals("--version")) {
builder.setPrintVersion(true);
- } else if (arg.equals("--all")) {
+ } else if (arg.equals("--all")) {
builder.setAllInfo(true);
} else if (arg.equals("--smali")) {
builder.setUseSmali(true);
+ } else if (arg.equals("--ir")) {
+ builder.setUseIr(true);
} else if (arg.equals("--pg-map")) {
builder.setProguardMapFile(Paths.get(args[++i]));
} else if (arg.equals("--output")) {
@@ -132,13 +141,18 @@
}
private DisassembleCommand(
- AndroidApp inputApp, Path outputPath, StringResource proguardMap,
- boolean allInfo, boolean useSmali) {
+ AndroidApp inputApp,
+ Path outputPath,
+ StringResource proguardMap,
+ boolean allInfo,
+ boolean useSmali,
+ boolean useIr) {
super(inputApp);
this.outputPath = outputPath;
this.proguardMap = proguardMap;
this.allInfo = allInfo;
this.useSmali = useSmali;
+ this.useIr = useIr;
}
private DisassembleCommand(boolean printHelp, boolean printVersion) {
@@ -147,6 +161,7 @@
proguardMap = null;
allInfo = false;
useSmali = false;
+ useIr = false;
}
public Path getOutputPath() {
@@ -157,6 +172,10 @@
return useSmali;
}
+ public boolean useIr() {
+ return useIr;
+ }
+
@Override
InternalOptions getInternalOptions() {
InternalOptions internal = new InternalOptions();
@@ -190,9 +209,10 @@
try {
DexApplication application =
new ApplicationReader(app, options, timing).read(command.proguardMap, executor);
- DexByteCodeWriter writer = command.useSmali()
- ? new SmaliWriter(application, options)
- : new AssemblyWriter(application, options, command.allInfo);
+ DexByteCodeWriter writer =
+ command.useSmali()
+ ? new SmaliWriter(application, options)
+ : new AssemblyWriter(application, options, command.allInfo, command.useIr());
if (command.getOutputPath() != null) {
writer.write(command.getOutputPath());
} else {
diff --git a/src/main/java/com/android/tools/r8/JarSizeCompare.java b/src/main/java/com/android/tools/r8/JarSizeCompare.java
new file mode 100644
index 0000000..aa16b1f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/JarSizeCompare.java
@@ -0,0 +1,482 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
+import com.android.tools.r8.naming.MemberNaming.FieldSignature;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.function.BiConsumer;
+
+/**
+ * JarSizeCompare outputs the class, method, field sizes of the given JAR files. For each input, a
+ * ProGuard map can be passed that is used to resolve minified names.
+ *
+ * <p>By default, only shows methods where R8's DEX output is 5 or more instructions larger than
+ * ProGuard+D8's output. Pass {@code --threshold 0} to display all methods.
+ */
+public class JarSizeCompare {
+
+ private static final String USAGE =
+ "Arguments:\n"
+ + " [--threshold <threshold>]\n"
+ + " [--lib <lib.jar>]\n"
+ + " --input <name1> <input1.jar> [<map1.txt>]\n"
+ + " --input <name2> <input2.jar> [<map2.txt>] ...\n"
+ + "\n"
+ + "JarSizeCompare outputs the class, method, field sizes of the given JAR files.\n"
+ + "For each input, a ProGuard map can be passed that is used to resolve minified names.\n";
+
+ private static final ImmutableMap<String, String> R8_RELOCATIONS =
+ ImmutableMap.<String, String>builder()
+ .put("com.google.common", "com.android.tools.r8.com.google.common")
+ .put("com.google.gson", "com.android.tools.r8.com.google.gson")
+ .put("com.google.thirdparty", "com.android.tools.r8.com.google.thirdparty")
+ .put("joptsimple", "com.android.tools.r8.joptsimple")
+ .put("org.apache.commons", "com.android.tools.r8.org.apache.commons")
+ .put("org.objectweb.asm", "com.android.tools.r8.org.objectweb.asm")
+ .put("it.unimi.dsi.fastutil", "com.android.tools.r8.it.unimi.dsi.fastutil")
+ .build();
+
+ private final List<Path> libraries;
+ private final List<InputParameter> inputParameters;
+ private final int threshold;
+ private final InternalOptions options;
+ private int pgIndex;
+ private int r8Index;
+
+ private JarSizeCompare(
+ List<Path> libraries, List<InputParameter> inputParameters, int threshold) {
+ this.libraries = libraries;
+ this.inputParameters = inputParameters;
+ this.threshold = threshold;
+ options = new InternalOptions();
+ options.enableCfFrontend = true;
+ }
+
+ public void run() throws Exception {
+ List<String> names = new ArrayList<>();
+ List<InputApplication> inputApplicationList = new ArrayList<>();
+ Timing timing = new Timing("JarSizeCompare");
+ for (InputParameter inputParameter : inputParameters) {
+ AndroidApp inputApp = inputParameter.getInputApp(libraries);
+ DexApplication input = inputParameter.getReader(options, inputApp, timing);
+ AndroidAppConsumers appConsumer = new AndroidAppConsumers();
+ D8.run(
+ D8Command.builder(inputApp)
+ .setMinApiLevel(AndroidApiLevel.P.getLevel())
+ .setProgramConsumer(appConsumer.wrapDexIndexedConsumer(null))
+ .build());
+ DexApplication d8Input = inputParameter.getReader(options, appConsumer.build(), timing);
+ InputApplication inputApplication =
+ new InputApplication(input, translateClassNames(input, input.classes()));
+ InputApplication d8Classes =
+ new InputApplication(input, translateClassNames(input, d8Input.classes()));
+ names.add(inputParameter.name + "-input");
+ inputApplicationList.add(inputApplication);
+ names.add(inputParameter.name + "-d8");
+ inputApplicationList.add(d8Classes);
+ }
+ if (threshold != 0) {
+ pgIndex = names.indexOf("pg-d8");
+ r8Index = names.indexOf("r8-d8");
+ }
+ Map<String, InputClass[]> inputClasses = new HashMap<>();
+ for (int i = 0; i < names.size(); i++) {
+ InputApplication classes = inputApplicationList.get(i);
+ for (String className : classes.getClasses()) {
+ inputClasses.computeIfAbsent(className, k -> new InputClass[names.size()])[i] =
+ classes.getInputClass(className);
+ }
+ }
+ for (Entry<String, Map<String, InputClass[]>> library : byLibrary(inputClasses)) {
+ System.out.println("");
+ System.out.println(Strings.repeat("=", 100));
+ String commonPrefix = getCommonPrefix(library.getValue().keySet());
+ if (library.getKey().isEmpty()) {
+ System.out.println("PROGRAM (" + commonPrefix + ")");
+ } else {
+ System.out.println("LIBRARY: " + library.getKey() + " (" + commonPrefix + ")");
+ }
+ printLibrary(library.getValue(), commonPrefix);
+ }
+ }
+
+ private Map<String, DexProgramClass> translateClassNames(
+ DexApplication input, List<DexProgramClass> classes) {
+ Map<String, DexProgramClass> result = new HashMap<>();
+ ClassNameMapper classNameMapper = input.getProguardMap();
+ for (DexProgramClass programClass : classes) {
+ ClassNamingForNameMapper classNaming;
+ if (classNameMapper == null) {
+ classNaming = null;
+ } else {
+ classNaming = classNameMapper.getClassNaming(programClass.type);
+ }
+ String type =
+ classNaming == null ? programClass.type.toSourceString() : classNaming.originalName;
+ result.put(type, programClass);
+ }
+ return result;
+ }
+
+ private String getCommonPrefix(Set<String> classes) {
+ if (classes.size() <= 1) {
+ return "";
+ }
+ String commonPrefix = null;
+ for (String clazz : classes) {
+ if (clazz.equals("r8.GeneratedOutlineSupport")) {
+ continue;
+ }
+ if (commonPrefix == null) {
+ commonPrefix = clazz;
+ } else {
+ int i = 0;
+ while (i < clazz.length()
+ && i < commonPrefix.length()
+ && clazz.charAt(i) == commonPrefix.charAt(i)) {
+ i++;
+ }
+ commonPrefix = commonPrefix.substring(0, i);
+ }
+ }
+ return commonPrefix;
+ }
+
+ private void printLibrary(Map<String, InputClass[]> classMap, String commonPrefix) {
+ List<Entry<String, InputClass[]>> classes = new ArrayList<>(classMap.entrySet());
+ classes.sort(Comparator.comparing(Entry::getKey));
+ for (Entry<String, InputClass[]> clazz : classes) {
+ printClass(
+ clazz.getKey().substring(commonPrefix.length()), new ClassCompare(clazz.getValue()));
+ }
+ }
+
+ private void printClass(String name, ClassCompare inputClasses) {
+ List<MethodSignature> methods = getMethods(inputClasses);
+ List<FieldSignature> fields = getFields(inputClasses);
+ if (methods.isEmpty() && fields.isEmpty()) {
+ return;
+ }
+ System.out.println(name);
+ for (MethodSignature sig : methods) {
+ printSignature(getMethodString(sig), inputClasses.sizes(sig));
+ }
+ for (FieldSignature sig : fields) {
+ printSignature(getFieldString(sig), inputClasses.sizes(sig));
+ }
+ }
+
+ private String getMethodString(MethodSignature sig) {
+ StringBuilder builder = new StringBuilder().append('(');
+ for (int i = 0; i < sig.parameters.length; i++) {
+ builder.append(DescriptorUtils.javaTypeToShorty(sig.parameters[i]));
+ }
+ builder.append(')').append(DescriptorUtils.javaTypeToShorty(sig.type)).append(' ');
+ return builder.append(sig.name).toString();
+ }
+
+ private String getFieldString(FieldSignature sig) {
+ return DescriptorUtils.javaTypeToShorty(sig.type) + ' ' + sig.name;
+ }
+
+ private void printSignature(String key, int[] sizes) {
+ System.out.print(padItem(key));
+ for (int size : sizes) {
+ System.out.print(padValue(size));
+ }
+ System.out.print('\n');
+ }
+
+ private List<MethodSignature> getMethods(ClassCompare inputClasses) {
+ List<MethodSignature> methods = new ArrayList<>();
+ for (MethodSignature methodSignature : inputClasses.getMethods()) {
+ if (threshold == 0 || methodExceedsThreshold(inputClasses, methodSignature)) {
+ methods.add(methodSignature);
+ }
+ }
+ return methods;
+ }
+
+ private boolean methodExceedsThreshold(
+ ClassCompare inputClasses, MethodSignature methodSignature) {
+ assert threshold > 0;
+ assert pgIndex != r8Index;
+ int pgSize = inputClasses.size(methodSignature, pgIndex);
+ int r8Size = inputClasses.size(methodSignature, r8Index);
+ return pgSize != -1 && r8Size != -1 && pgSize + threshold <= r8Size;
+ }
+
+ private List<FieldSignature> getFields(ClassCompare inputClasses) {
+ return threshold == 0 ? inputClasses.getFields() : Collections.emptyList();
+ }
+
+ private String padItem(String s) {
+ return String.format("%-52s", s);
+ }
+
+ private String padValue(int v) {
+ return String.format("%8s", v == -1 ? "---" : v);
+ }
+
+ private List<Map.Entry<String, Map<String, InputClass[]>>> byLibrary(
+ Map<String, InputClass[]> inputClasses) {
+ Map<String, Map<String, InputClass[]>> byLibrary = new HashMap<>();
+ for (Entry<String, InputClass[]> entry : inputClasses.entrySet()) {
+ Map<String, InputClass[]> library =
+ byLibrary.computeIfAbsent(getLibraryName(entry.getKey()), k -> new HashMap<>());
+ library.put(entry.getKey(), entry.getValue());
+ }
+ List<Entry<String, Map<String, InputClass[]>>> list = new ArrayList<>(byLibrary.entrySet());
+ list.sort(Comparator.comparing(Entry::getKey));
+ return list;
+ }
+
+ private String getLibraryName(String className) {
+ for (Entry<String, String> relocation : R8_RELOCATIONS.entrySet()) {
+ if (className.startsWith(relocation.getValue())) {
+ return relocation.getKey();
+ }
+ }
+ return "";
+ }
+
+ static class InputParameter {
+
+ private final String name;
+ private final Path jar;
+ private final Path map;
+
+ InputParameter(String name, Path jar, Path map) {
+ this.name = name;
+ this.jar = jar;
+ this.map = map;
+ }
+
+ DexApplication getReader(InternalOptions options, AndroidApp inputApp, Timing timing)
+ throws Exception {
+ ApplicationReader applicationReader = new ApplicationReader(inputApp, options, timing);
+ return applicationReader.read(map == null ? null : StringResource.fromFile(map)).toDirect();
+ }
+
+ AndroidApp getInputApp(List<Path> libraries) throws Exception {
+ return AndroidApp.builder().addLibraryFiles(libraries).addProgramFiles(jar).build();
+ }
+ }
+
+ static class InputApplication {
+
+ private final DexApplication dexApplication;
+ private final Map<String, DexProgramClass> classMap;
+
+ private InputApplication(DexApplication dexApplication, Map<String, DexProgramClass> classMap) {
+ this.dexApplication = dexApplication;
+ this.classMap = classMap;
+ }
+
+ public Set<String> getClasses() {
+ return classMap.keySet();
+ }
+
+ private InputClass getInputClass(String type) {
+ DexProgramClass inputClass = classMap.get(type);
+ ClassNameMapper proguardMap = dexApplication.getProguardMap();
+ return new InputClass(inputClass, proguardMap);
+ }
+ }
+
+ static class InputClass {
+ private final DexProgramClass programClass;
+ private final ClassNameMapper proguardMap;
+
+ InputClass(DexClass dexClass, ClassNameMapper proguardMap) {
+ this.programClass = dexClass == null ? null : dexClass.asProgramClass();
+ this.proguardMap = proguardMap;
+ }
+
+ void forEachMethod(BiConsumer<MethodSignature, DexEncodedMethod> consumer) {
+ if (programClass == null) {
+ return;
+ }
+ programClass.forEachMethod(
+ dexEncodedMethod -> {
+ MethodSignature originalSignature =
+ proguardMap == null
+ ? null
+ : ((MethodSignature) proguardMap.originalSignatureOf(dexEncodedMethod.method));
+ MethodSignature signature = MethodSignature.fromDexMethod(dexEncodedMethod.method);
+ consumer.accept(
+ originalSignature == null ? signature : originalSignature, dexEncodedMethod);
+ });
+ }
+
+ void forEachField(BiConsumer<FieldSignature, DexEncodedField> consumer) {
+ if (programClass == null) {
+ return;
+ }
+ programClass.forEachField(
+ dexEncodedField -> {
+ FieldSignature originalSignature =
+ proguardMap == null ? null : proguardMap.originalSignatureOf(dexEncodedField.field);
+ FieldSignature signature = FieldSignature.fromDexField(dexEncodedField.field);
+ consumer.accept(
+ originalSignature == null ? signature : originalSignature, dexEncodedField);
+ });
+ }
+ }
+
+ private static class ClassCompare {
+ final Map<MethodSignature, DexEncodedMethod[]> methods = new HashMap<>();
+ final Map<FieldSignature, DexEncodedField[]> fields = new HashMap<>();
+ final int classes;
+
+ ClassCompare(InputClass[] inputs) {
+ for (int i = 0; i < inputs.length; i++) {
+ InputClass inputClass = inputs[i];
+ int finalI = i;
+ if (inputClass == null) {
+ continue;
+ }
+ inputClass.forEachMethod(
+ (sig, m) ->
+ methods.computeIfAbsent(sig, o -> new DexEncodedMethod[inputs.length])[finalI] = m);
+ inputClass.forEachField(
+ (sig, f) ->
+ fields.computeIfAbsent(sig, o -> new DexEncodedField[inputs.length])[finalI] = f);
+ }
+ classes = inputs.length;
+ }
+
+ List<MethodSignature> getMethods() {
+ List<MethodSignature> methods = new ArrayList<>(this.methods.keySet());
+ methods.sort(Comparator.comparing(MethodSignature::toString));
+ return methods;
+ }
+
+ List<FieldSignature> getFields() {
+ List<FieldSignature> fields = new ArrayList<>(this.fields.keySet());
+ fields.sort(Comparator.comparing(FieldSignature::toString));
+ return fields;
+ }
+
+ int size(MethodSignature method, int classIndex) {
+ DexEncodedMethod dexEncodedMethod = methods.get(method)[classIndex];
+ if (dexEncodedMethod == null) {
+ return -1;
+ }
+ Code code = dexEncodedMethod.getCode();
+ if (code == null) {
+ return 0;
+ }
+ if (code.isCfCode()) {
+ return code.asCfCode().getInstructions().size();
+ }
+ if (code.isDexCode()) {
+ return code.asDexCode().instructions.length;
+ }
+ throw new Unreachable();
+ }
+
+ int[] sizes(MethodSignature method) {
+ int[] result = new int[classes];
+ for (int i = 0; i < classes; i++) {
+ result[i] = size(method, i);
+ }
+ return result;
+ }
+
+ int size(FieldSignature field, int classIndex) {
+ return fields.get(field)[classIndex] == null ? -1 : 1;
+ }
+
+ int[] sizes(FieldSignature field) {
+ int[] result = new int[classes];
+ for (int i = 0; i < classes; i++) {
+ result[i] = size(field, i);
+ }
+ return result;
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ JarSizeCompare program = JarSizeCompare.parse(args);
+ if (program == null) {
+ System.out.println(USAGE);
+ } else {
+ program.run();
+ }
+ }
+
+ public static JarSizeCompare parse(String[] args) {
+ int i = 0;
+ int threshold = 0;
+ List<Path> libraries = new ArrayList<>();
+ List<InputParameter> inputs = new ArrayList<>();
+ Set<String> names = new HashSet<>();
+ while (i < args.length) {
+ if (args[i].equals("--threshold") && i + 1 < args.length) {
+ threshold = Integer.parseInt(args[i + 1]);
+ i += 2;
+ } else if (args[i].equals("--lib") && i + 1 < args.length) {
+ libraries.add(Paths.get(args[i + 1]));
+ i += 2;
+ } else if (args[i].equals("--input") && i + 2 < args.length) {
+ String name = args[i + 1];
+ Path jar = Paths.get(args[i + 2]);
+ Path map = null;
+ if (i + 3 < args.length && !args[i + 3].startsWith("-")) {
+ map = Paths.get(args[i + 3]);
+ i += 4;
+ } else {
+ i += 3;
+ }
+ inputs.add(new InputParameter(name, jar, map));
+ if (!names.add(name)) {
+ System.out.println("Duplicate name: " + name);
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+ if (inputs.size() < 2) {
+ return null;
+ }
+ if (threshold != 0 && (!names.contains("r8") || !names.contains("pg"))) {
+ System.out.println(
+ "You must either specify names \"pg\" and \"r8\" for input files "
+ + "or use \"--threshold 0\".");
+ return null;
+ }
+ return new JarSizeCompare(libraries, inputs, threshold);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
index 28e1f93..f54233b 100644
--- a/src/main/java/com/android/tools/r8/SwissArmyKnife.java
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -61,6 +61,9 @@
case "jardiff":
JarDiff.main(shift(args));
break;
+ case "jarsizecompare":
+ JarSizeCompare.main(shift(args));
+ break;
case "maindex":
GenerateMainDexList.main(shift(args));
break;
diff --git a/src/main/java/com/android/tools/r8/UsageInformationConsumer.java b/src/main/java/com/android/tools/r8/UsageInformationConsumer.java
deleted file mode 100644
index a572908..0000000
--- a/src/main/java/com/android/tools/r8/UsageInformationConsumer.java
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8;
-
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.origin.PathOrigin;
-import com.android.tools.r8.utils.ExceptionDiagnostic;
-import com.android.tools.r8.utils.FileUtils;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.file.Path;
-
-/**
- * Interface for receiving usage feedback from R8.
- *
- * The data is in the format defined for Proguard's <code>-printusage</code> flag. The information
- * will be produced if a consumer is provided. A consumer is automatically setup when running R8
- * with the Proguard <code>-printusage</code> flag set.
- */
-public interface UsageInformationConsumer {
-
- /**
- * Callback to receive the usage-information data.
- *
- * <p>The consumer is expected not to throw, but instead report any errors via the diagnostics
- * {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown,
- * then the compiler guaranties to exit with an error.
- *
- * @param data UTF-8 encoded usage information.
- * @param handler Diagnostics handler for reporting.
- */
- void acceptUsageInformation(byte[] data, DiagnosticsHandler handler);
-
- static EmptyConsumer emptyConsumer() {
- return EmptyConsumer.EMPTY_CONSUMER;
- }
-
- /** Empty consumer to request usage information but ignore the result. */
- class EmptyConsumer implements UsageInformationConsumer {
-
- private static final EmptyConsumer EMPTY_CONSUMER = new EmptyConsumer();
-
- private EmptyConsumer() {}
-
- @Override
- public void acceptUsageInformation(byte[] data, DiagnosticsHandler handler) {
- // Ignore content.
- }
- }
-
- /** Forwarding consumer to delegate to an optional existing consumer. */
- class ForwardingConsumer implements UsageInformationConsumer {
-
- private final UsageInformationConsumer consumer;
-
- /** @param consumer Consumer to forward to, if null, nothing will be forwarded. */
- public ForwardingConsumer(UsageInformationConsumer consumer) {
- this.consumer = consumer;
- }
-
- @Override
- public void acceptUsageInformation(byte[] data, DiagnosticsHandler handler) {
- if (consumer != null) {
- consumer.acceptUsageInformation(data, handler);
- }
- }
- }
-
- /** File consumer to write contents to a file-system file. */
- class FileConsumer extends ForwardingConsumer {
-
- private final Path outputPath;
-
- /** Consumer that writes to {@param outputPath}. */
- public FileConsumer(Path outputPath) {
- this(outputPath, null);
- }
-
- /** Consumer that forwards to {@param consumer} and also writes to {@param outputPath}. */
- public FileConsumer(Path outputPath, UsageInformationConsumer consumer) {
- super(consumer);
- this.outputPath = outputPath;
- }
-
- @Override
- public void acceptUsageInformation(byte[] data, DiagnosticsHandler handler) {
- super.acceptUsageInformation(data, handler);
- try {
- FileUtils.writeToFile(outputPath, null, data);
- } catch (IOException e) {
- Origin origin = new PathOrigin(outputPath);
- handler.error(new ExceptionDiagnostic(e, origin));
- }
- }
- }
-
- /**
- * Stream consumer to write contents to an output stream.
- *
- * <p>Note: No close events are given to this stream so it should either be a permanent stream or
- * the closing needs to happen outside of the compilation itself. If the stream is not one of the
- * standard streams, i.e., System.out or System.err, you should likely implement yor own consumer.
- */
- class StreamConsumer extends ForwardingConsumer {
-
- private final Origin origin;
- private final OutputStream outputStream;
-
- /** Consumer that writes to {@param outputStream}. */
- public StreamConsumer(Origin origin, OutputStream outputStream) {
- this(origin, outputStream, null);
- }
-
- /** Consumer that forwards to {@param consumer} and also writes to {@param outputStream}. */
- public StreamConsumer(
- Origin origin, OutputStream outputStream, UsageInformationConsumer consumer) {
- super(consumer);
- this.origin = origin;
- this.outputStream = outputStream;
- }
-
- @Override
- public void acceptUsageInformation(byte[] data, DiagnosticsHandler handler) {
- super.acceptUsageInformation(data, handler);
- try {
- outputStream.write(data);
- } catch (IOException e) {
- handler.error(new ExceptionDiagnostic(e, origin));
- }
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 89abadd..23dbfbf 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -3,9 +3,15 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.OptimizationFeedback;
+import com.android.tools.r8.ir.conversion.OptimizationFeedbackIgnore;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
+import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
import java.io.PrintStream;
public class AssemblyWriter extends DexByteCodeWriter {
@@ -13,12 +19,29 @@
private final boolean writeAllClassInfo;
private final boolean writeFields;
private final boolean writeAnnotations;
+ private final boolean writeIR;
+ private final AppInfoWithSubtyping appInfo;
+ private final Timing timing = new Timing("AssemblyWriter");
+ private final OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
- public AssemblyWriter(DexApplication application, InternalOptions options, boolean allInfo) {
+ public AssemblyWriter(
+ DexApplication application, InternalOptions options, boolean allInfo, boolean writeIR) {
super(application, options);
this.writeAllClassInfo = allInfo;
this.writeFields = allInfo;
this.writeAnnotations = allInfo;
+ this.writeIR = writeIR;
+ if (writeIR) {
+ this.appInfo = new AppInfoWithSubtyping(application.toDirect());
+ if (options.programConsumer == null) {
+ // Use class-file backend, since the CF frontend for testing does not support desugaring of
+ // synchronized methods for the DEX backend (b/109789541).
+ options.programConsumer = ClassFileConsumer.emptyConsumer();
+ }
+ options.outline.enabled = false;
+ } else {
+ this.appInfo = null;
+ }
}
@Override
@@ -88,10 +111,21 @@
ps.println();
Code code = method.getCode();
if (code != null) {
- ps.println(code.toString(method, naming));
+ if (writeIR) {
+ writeIR(method, ps);
+ } else {
+ ps.println(code.toString(method, naming));
+ }
}
}
+ private void writeIR(DexEncodedMethod method, PrintStream ps) {
+ CfgPrinter printer = new CfgPrinter();
+ new IRConverter(appInfo, options, timing, printer)
+ .processMethod(method, ignoreOptimizationFeedback, null, null, null);
+ ps.println(printer.toString());
+ }
+
private void writeAnnotations(DexAnnotationSet annotations, PrintStream ps) {
if (writeAnnotations) {
if (!annotations.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 831edbe..5f0fb83 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -264,9 +264,10 @@
public void setCode(
IRCode ir,
+ GraphLense graphLense,
RegisterAllocator registerAllocator,
InternalOptions options) {
- final DexBuilder builder = new DexBuilder(ir, registerAllocator, options);
+ final DexBuilder builder = new DexBuilder(ir, graphLense, registerAllocator, options);
code = builder.build(method.getArity());
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index ed99d25..2310891 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -557,7 +557,7 @@
@Override
public int hashCode() {
- return (int) value * 19;
+ return (int) (value * 19);
}
@Override
@@ -615,7 +615,7 @@
@Override
public int hashCode() {
- return (int) value * 29;
+ return (int) (value * 29);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index ca79b0f..309e1d0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -367,6 +367,13 @@
}
setLocalVariableLists();
readEndingLocals(builder);
+ if (currentBlockInfo != null && instruction.canThrow()) {
+ Snapshot exceptionTransfer =
+ state.getSnapshot().exceptionTransfer(builder.getFactory().throwableType);
+ for (int target : currentBlockInfo.exceptionalSuccessors) {
+ recordStateForTarget(target, exceptionTransfer);
+ }
+ }
if (isControlFlow(instruction)) {
ensureDebugValueLivenessControl(builder);
instruction.buildIR(builder, state, this);
@@ -376,13 +383,6 @@
}
state.clear();
} else {
- if (currentBlockInfo != null && instruction.canThrow()) {
- Snapshot exceptionTransfer =
- state.getSnapshot().exceptionTransfer(builder.getFactory().throwableType);
- for (int target : currentBlockInfo.exceptionalSuccessors) {
- recordStateForTarget(target, exceptionTransfer);
- }
- }
instruction.buildIR(builder, state, this);
ensureDebugValueLiveness(builder);
if (builder.getCFG().containsKey(currentInstructionIndex + 1)) {
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 8201562..ee7df48 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
@@ -43,6 +43,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.ir.code.Argument;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CatchHandlers;
@@ -81,6 +82,10 @@
// The IR representation of the code to build.
private final IRCode ir;
+ // Graph lense for building the exception handlers. Needed since program classes that inherit
+ // from Throwable may get merged into their subtypes during class merging.
+ private final GraphLense graphLense;
+
// The register allocator providing register assignments for the code to build.
private final RegisterAllocator registerAllocator;
@@ -116,11 +121,14 @@
public DexBuilder(
IRCode ir,
+ GraphLense graphLense,
RegisterAllocator registerAllocator,
InternalOptions options) {
assert ir != null;
+ assert graphLense != null;
assert registerAllocator != null;
this.ir = ir;
+ this.graphLense = graphLense;
this.registerAllocator = registerAllocator;
this.options = options;
}
@@ -706,7 +714,12 @@
assert i == handlerGroup.getGuards().size() - 1;
catchAllOffset = targetOffset;
} else {
- pairs.add(new TypeAddrPair(type, targetOffset));
+ // The type may have changed due to class merging.
+ // TODO(christofferqa): This assumes that the graph lense is context insensitive for the
+ // given type (which is always the case). Consider removing the context-argument from
+ // GraphLense.lookupType, since we do not currently have a use case for it.
+ DexType actualType = graphLense.lookupType(type, null);
+ pairs.add(new TypeAddrPair(actualType, targetOffset));
}
}
TypeAddrPair[] pairsArray = pairs.toArray(new TypeAddrPair[pairs.size()]);
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 81dc436..df4e69c 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
@@ -532,7 +532,7 @@
assert code.isConsistentSSA();
code.traceBlocks();
RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
- method.setCode(code, registerAllocator, options);
+ method.setCode(code, graphLense, registerAllocator, options);
if (Log.ENABLED) {
Log.debug(getClass(), "Resulting dex code for %s:\n%s",
method.toSourceString(), logCode(options, method));
@@ -784,7 +784,7 @@
private void finalizeToDex(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
// Perform register allocation.
RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
- method.setCode(code, registerAllocator, options);
+ method.setCode(code, graphLense, registerAllocator, options);
updateHighestSortingStrings(method);
if (Log.ENABLED) {
Log.debug(getClass(), "Resulting dex code for %s:\n%s",
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 ed9cbdb..c4276dc 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -812,45 +812,45 @@
skipWhitespace();
switch (peekChar()) {
case 'a':
- if (found = acceptString("abstract")) {
+ if ((found = acceptString("abstract"))) {
flags.setAbstract();
}
break;
case 'f':
- if (found = acceptString("final")) {
+ if ((found = acceptString("final"))) {
flags.setFinal();
}
break;
case 'n':
- if (found = acceptString("native")) {
+ if ((found = acceptString("native"))) {
flags.setNative();
}
break;
case 'p':
- if (found = acceptString("public")) {
+ if ((found = acceptString("public"))) {
flags.setPublic();
- } else if (found = acceptString("private")) {
+ } else if ((found = acceptString("private"))) {
flags.setPrivate();
- } else if (found = acceptString("protected")) {
+ } else if ((found = acceptString("protected"))) {
flags.setProtected();
}
break;
case 's':
- if (found = acceptString("synchronized")) {
+ if ((found = acceptString("synchronized"))) {
flags.setSynchronized();
- } else if (found = acceptString("static")) {
+ } else if ((found = acceptString("static"))) {
flags.setStatic();
- } else if (found = acceptString("strictfp")) {
+ } else if ((found = acceptString("strictfp"))) {
flags.setStrict();
}
break;
case 't':
- if (found = acceptString("transient")) {
+ if ((found = acceptString("transient"))) {
flags.setTransient();
}
break;
case 'v':
- if (found = acceptString("volatile")) {
+ if ((found = acceptString("volatile"))) {
flags.setVolatile();
}
break;
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index b938596..f10ff69 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -20,6 +20,7 @@
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.ClassNaming;
+import com.android.tools.r8.naming.ClassNaming.Builder;
import com.android.tools.r8.naming.MemberNaming;
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -187,6 +188,22 @@
}
}
+ // We will be remapping positional debug events and collect them as MappedPositions.
+ private static class MappedPosition {
+ private final DexMethod method;
+ private final int originalLine;
+ private final Position caller;
+ private final int obfuscatedLine;
+
+ private MappedPosition(
+ DexMethod method, int originalLine, Position caller, int obfuscatedLine) {
+ this.method = method;
+ this.originalLine = originalLine;
+ this.caller = caller;
+ this.obfuscatedLine = obfuscatedLine;
+ }
+ }
+
public static ClassNameMapper run(
DexApplication application, NamingLens namingLens, boolean identityMapping) {
IdentityHashMap<DexString, List<DexProgramClass>> classesOfFiles = new IdentityHashMap<>();
@@ -199,25 +216,8 @@
continue;
}
- // Group methods by name
IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByName =
- new IdentityHashMap<>(clazz.directMethods().length + clazz.virtualMethods().length);
- clazz.forEachMethod(
- method -> {
- // Add method only if renamed or contains positions.
- if (namingLens.lookupName(method.method) != method.method.name
- || doesContainPositions(method)) {
- methodsByName.compute(
- method.method.name,
- (name, methods) -> {
- if (methods == null) {
- methods = new ArrayList<>();
- }
- methods.add(method);
- return methods;
- });
- }
- });
+ groupMethodsByName(namingLens, clazz);
// At this point we don't know if we really need to add this class to the builder.
// It depends on whether any methods/fields are renamed or some methods contain positions.
@@ -230,24 +230,11 @@
DescriptorUtils.descriptorToJavaType(renamedClassName.toString()),
clazz.toString()));
- // We do know we need to create a ClassNaming.Builder if the class itself had been renamed.
- if (!clazz.toString().equals(renamedClassName.toString())) {
- // Not using return value, it's registered in classNameMapperBuilder
- onDemandClassNamingBuilder.get();
- }
+ // If the class is renamed add it to the classNamingBuilder.
+ addClassToClassNaming(clazz, renamedClassName, onDemandClassNamingBuilder);
// First transfer renamed fields to classNamingBuilder.
- clazz.forEachField(
- dexEncodedField -> {
- DexField dexField = dexEncodedField.field;
- DexString renamedName = namingLens.lookupName(dexField);
- if (renamedName != dexField.name) {
- FieldSignature signature =
- new FieldSignature(dexField.name.toString(), dexField.type.toString());
- MemberNaming memberNaming = new MemberNaming(signature, renamedName.toString());
- onDemandClassNamingBuilder.get().addMemberEntry(memberNaming);
- }
- });
+ addFieldsToClassNaming(namingLens, clazz, onDemandClassNamingBuilder);
// Then process the methods.
for (List<DexEncodedMethod> methods : methodsByName.values()) {
@@ -256,47 +243,13 @@
// deterministic behaviour: the algorithm will assign new line numbers in this order.
// Methods with different names can share the same line numbers, that's why they don't
// need to be sorted.
- methods.sort(
- (lhs, rhs) -> {
- // Sort by startline, then DexEncodedMethod.slowCompare.
- // Use startLine = 0 if no debuginfo.
- Code lhsCode = lhs.getCode();
- Code rhsCode = rhs.getCode();
- DexCode lhsDexCode =
- lhsCode == null || !lhsCode.isDexCode() ? null : lhsCode.asDexCode();
- DexCode rhsDexCode =
- rhsCode == null || !rhsCode.isDexCode() ? null : rhsCode.asDexCode();
- DexDebugInfo lhsDebugInfo = lhsDexCode == null ? null : lhsDexCode.getDebugInfo();
- DexDebugInfo rhsDebugInfo = rhsDexCode == null ? null : rhsDexCode.getDebugInfo();
- int lhsStartLine = lhsDebugInfo == null ? 0 : lhsDebugInfo.startLine;
- int rhsStartLine = rhsDebugInfo == null ? 0 : rhsDebugInfo.startLine;
- int startLineDiff = lhsStartLine - rhsStartLine;
- if (startLineDiff != 0) return startLineDiff;
- return DexEncodedMethod.slowCompare(lhs, rhs);
- });
+ sortMethods(methods);
}
PositionRemapper positionRemapper =
identityMapping ? new IdentityPositionRemapper() : new OptimizingPositionRemapper();
for (DexEncodedMethod method : methods) {
-
- // We will be remapping positional debug events and collect them as MappedPositions.
- class MappedPosition {
- private final DexMethod method;
- private final int originalLine;
- private final Position caller;
- private final int obfuscatedLine;
-
- private MappedPosition(
- DexMethod method, int originalLine, Position caller, int obfuscatedLine) {
- this.method = method;
- this.originalLine = originalLine;
- this.caller = caller;
- this.obfuscatedLine = obfuscatedLine;
- }
- }
-
List<MappedPosition> mappedPositions = new ArrayList<>();
if (doesContainPositions(method)) {
@@ -429,6 +382,75 @@
return classNameMapperBuilder.build();
}
+ // Sort by startline, then DexEncodedMethod.slowCompare.
+ // Use startLine = 0 if no debuginfo.
+ private static void sortMethods(List<DexEncodedMethod> methods) {
+ methods.sort(
+ (lhs, rhs) -> {
+ Code lhsCode = lhs.getCode();
+ Code rhsCode = rhs.getCode();
+ DexCode lhsDexCode =
+ lhsCode == null || !lhsCode.isDexCode() ? null : lhsCode.asDexCode();
+ DexCode rhsDexCode =
+ rhsCode == null || !rhsCode.isDexCode() ? null : rhsCode.asDexCode();
+ DexDebugInfo lhsDebugInfo = lhsDexCode == null ? null : lhsDexCode.getDebugInfo();
+ DexDebugInfo rhsDebugInfo = rhsDexCode == null ? null : rhsDexCode.getDebugInfo();
+ int lhsStartLine = lhsDebugInfo == null ? 0 : lhsDebugInfo.startLine;
+ int rhsStartLine = rhsDebugInfo == null ? 0 : rhsDebugInfo.startLine;
+ int startLineDiff = lhsStartLine - rhsStartLine;
+ if (startLineDiff != 0) return startLineDiff;
+ return DexEncodedMethod.slowCompare(lhs, rhs);
+ });
+ }
+
+ @SuppressWarnings("ReturnValueIgnored")
+ private static void addClassToClassNaming(DexProgramClass clazz, DexString renamedClassName,
+ Supplier<Builder> onDemandClassNamingBuilder) {
+ // We do know we need to create a ClassNaming.Builder if the class itself had been renamed.
+ if (!clazz.toString().equals(renamedClassName.toString())) {
+ // Not using return value, it's registered in classNameMapperBuilder
+ onDemandClassNamingBuilder.get();
+ }
+ }
+
+ private static void addFieldsToClassNaming(NamingLens namingLens, DexProgramClass clazz,
+ Supplier<Builder> onDemandClassNamingBuilder) {
+ clazz.forEachField(
+ dexEncodedField -> {
+ DexField dexField = dexEncodedField.field;
+ DexString renamedName = namingLens.lookupName(dexField);
+ if (renamedName != dexField.name) {
+ FieldSignature signature =
+ new FieldSignature(dexField.name.toString(), dexField.type.toString());
+ MemberNaming memberNaming = new MemberNaming(signature, renamedName.toString());
+ onDemandClassNamingBuilder.get().addMemberEntry(memberNaming);
+ }
+ });
+ }
+
+ private static IdentityHashMap<DexString, List<DexEncodedMethod>> groupMethodsByName(
+ NamingLens namingLens, DexProgramClass clazz) {
+ IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByName =
+ new IdentityHashMap<>(clazz.directMethods().length + clazz.virtualMethods().length);
+ clazz.forEachMethod(
+ method -> {
+ // Add method only if renamed or contains positions.
+ if (namingLens.lookupName(method.method) != method.method.name
+ || doesContainPositions(method)) {
+ methodsByName.compute(
+ method.method.name,
+ (name, methods) -> {
+ if (methods == null) {
+ methods = new ArrayList<>();
+ }
+ methods.add(method);
+ return methods;
+ });
+ }
+ });
+ return methodsByName;
+ }
+
private static boolean doesContainPositions(DexEncodedMethod method) {
Code code = method.getCode();
if (code == null || !code.isDexCode()) {
diff --git a/src/test/examples/classmerging/ExceptionTest.java b/src/test/examples/classmerging/ExceptionTest.java
new file mode 100644
index 0000000..b365b2a
--- /dev/null
+++ b/src/test/examples/classmerging/ExceptionTest.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 classmerging;
+
+public class ExceptionTest {
+ public static void main(String[] args) {
+ // The following will lead to a catch handler for ExceptionA, which is merged into ExceptionB.
+ try {
+ throw new ExceptionB("Ouch!");
+ } catch (ExceptionA exception) {
+ System.out.println("Caught exception: " + exception.getMessage());
+ }
+ }
+
+ // Will be merged into ExceptionB when class merging is enabled.
+ public static class ExceptionA extends Exception {
+ public ExceptionA(String message) {
+ super(message);
+ }
+ }
+
+ public static class ExceptionB extends ExceptionA {
+ public ExceptionB(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
index 1cc5b87..f9e6d52 100644
--- a/src/test/examples/classmerging/keep-rules.txt
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -7,6 +7,9 @@
-keep public class classmerging.Test {
public static void main(...);
}
+-keep public class classmerging.ExceptionTest {
+ public static void main(...);
+}
# TODO(herhut): Consider supporting merging of inner-class attributes.
# -keepattributes *
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
index 4e07849..4f749b5 100644
--- a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
@@ -85,6 +85,11 @@
}
@Test
+ public void testInlining() throws Exception {
+ runTest("inlining.Inlining");
+ }
+
+ @Test
public void testInstanceVariable() throws Exception {
runTest("instancevariable.InstanceVariable");
}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 348dbf1..087012c 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -41,6 +41,7 @@
"filledarray.FilledArray",
"hello.Hello",
"ifstatements.IfStatements",
+ "inlining.Inlining",
"instancevariable.InstanceVariable",
"instanceofstring.InstanceofString",
"invoke.Invoke",
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index fbf2d02..5200a09 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -151,6 +151,11 @@
return builder.build();
}
+ /** Build an AndroidApp from the specified program files. */
+ protected AndroidApp readProgramFiles(Path... programFiles) throws IOException {
+ return AndroidApp.builder().addProgramFiles(programFiles).build();
+ }
+
/**
* Create a temporary JAR file containing the specified test classes.
*/
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index d5d2c39..69f925c 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -3,29 +3,34 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.classmerging;
+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.OutputMode;
import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-public class ClassMergingTest {
+public class ClassMergingTest extends TestBase {
+ private static final Path CF_DIR =
+ Paths.get(ToolHelper.BUILD_DIR).resolve("classes/examples/classmerging");
private static final Path EXAMPLE_JAR = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR)
.resolve("classmerging.jar");
private static final Path EXAMPLE_KEEP = Paths.get(ToolHelper.EXAMPLES_DIR)
@@ -33,17 +38,14 @@
private static final Path DONT_OPTIMIZE = Paths.get(ToolHelper.EXAMPLES_DIR)
.resolve("classmerging").resolve("keep-rules-dontoptimize.txt");
- @Rule
- public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
-
private void configure(InternalOptions options) {
options.enableClassMerging = true;
options.enableClassInlining = false;
+ options.enableMinification = false;
}
private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
- throws IOException, ProguardRuleParserException, ExecutionException,
- CompilationFailedException {
+ throws IOException, ExecutionException, CompilationFailedException {
ToolHelper.runR8(
R8Command.builder()
.setOutput(Paths.get(temp.getRoot().getCanonicalPath()), OutputMode.DexIndexed)
@@ -73,13 +75,12 @@
assertFalse(inspector.clazz(candidate).isPresent());
}
assertTrue(inspector.clazz("classmerging.GenericInterfaceImpl").isPresent());
- assertTrue(inspector.clazz("classmerging.GenericInterfaceImpl").isPresent());
assertTrue(inspector.clazz("classmerging.Outer$SubClass").isPresent());
assertTrue(inspector.clazz("classmerging.SubClass").isPresent());
}
@Test
- public void testClassesShouldNotMerged() throws Exception {
+ public void testClassesHaveNotBeenMerged() throws Exception {
runR8(DONT_OPTIMIZE, null);
for (String candidate : CAN_BE_MERGED) {
assertTrue(inspector.clazz(candidate).isPresent());
@@ -100,4 +101,39 @@
assertTrue(inspector.clazz("classmerging.SubClassThatReferencesSuperMethod").isPresent());
}
+ // If an exception class A is merged into another exception class B, then all exception tables
+ // should be updated, and class A should be removed entirely.
+ @Test
+ public void testExceptionTables() throws Exception {
+ String main = "classmerging.ExceptionTest";
+ Path[] programFiles =
+ new Path[] {
+ CF_DIR.resolve("ExceptionTest.class"),
+ CF_DIR.resolve("ExceptionTest$ExceptionA.class"),
+ CF_DIR.resolve("ExceptionTest$ExceptionB.class")
+ };
+ Set<String> preservedClassNames =
+ ImmutableSet.of("classmerging.ExceptionTest", "classmerging.ExceptionTest$ExceptionB");
+ runTest(main, programFiles, preservedClassNames);
+ }
+
+ private void runTest(String main, Path[] programFiles, Set<String> preservedClassNames)
+ throws Exception {
+ AndroidApp input = readProgramFiles(programFiles);
+ AndroidApp output = compileWithR8(input, EXAMPLE_KEEP, this::configure);
+ DexInspector inspector = new DexInspector(output);
+ // Check that all classes in [preservedClassNames] are in fact preserved.
+ for (String className : preservedClassNames) {
+ assertTrue(
+ "Class " + className + " should be present", inspector.clazz(className).isPresent());
+ }
+ // Check that all other classes have been removed.
+ for (FoundClassSubject classSubject : inspector.allClasses()) {
+ String className = classSubject.getDexClass().toSourceString();
+ assertTrue(
+ "Class " + className + " should be absent", preservedClassNames.contains(className));
+ }
+ // Check that the R8-generated code produces the same result as D8-generated code.
+ assertEquals(runOnArt(compileWithD8(input), main), runOnArt(output, main));
+ }
}
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 4214a67..d6e3612 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -38,6 +38,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.ir.code.CatchHandlers;
@@ -652,7 +653,7 @@
code);
IRCode ir = code.buildIR(method, null, options, Origin.unknown());
RegisterAllocator allocator = new LinearScanRegisterAllocator(ir, options);
- method.setCode(ir, allocator, options);
+ method.setCode(ir, GraphLense.getIdentityLense(), allocator, options);
directMethods[i] = method;
}
builder.addProgramClass(
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index ace5c9b..38de25e 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import static org.junit.Assert.assertTrue;
+
import com.android.tools.r8.StringResource;
import com.android.tools.r8.code.Const4;
import com.android.tools.r8.code.ConstString;
@@ -53,6 +55,7 @@
import com.android.tools.r8.code.Throw;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexCode;
@@ -65,6 +68,8 @@
import com.android.tools.r8.graph.DexProto;
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.DexValueString;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.ClassNamingForNameMapper;
@@ -72,6 +77,8 @@
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.naming.signature.GenericSignatureAction;
+import com.android.tools.r8.naming.signature.GenericSignatureParser;
import com.android.tools.r8.smali.SmaliBuilder;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableList;
@@ -367,6 +374,10 @@
public abstract boolean isLocalClass();
public abstract boolean isAnonymousClass();
+
+ public abstract String getOriginalSignatureAttribute();
+
+ public abstract String getFinalSignatureAttribute();
}
private class AbsentClassSubject extends ClassSubject {
@@ -448,6 +459,16 @@
public boolean isAnonymousClass() {
return false;
}
+
+ @Override
+ public String getOriginalSignatureAttribute() {
+ return null;
+ }
+
+ @Override
+ public String getFinalSignatureAttribute() {
+ return null;
+ }
}
public class FoundClassSubject extends ClassSubject {
@@ -641,6 +662,98 @@
}
@Override
+ public String getOriginalSignatureAttribute() {
+ // Build the generic signature using the current mapping if any.
+ class GenericSignatureGenerater implements GenericSignatureAction<String> {
+
+ private StringBuilder signature;
+
+ public String getSignature() {
+ return signature.toString();
+ }
+
+ @Override
+ public void parsedSymbol(char symbol) {
+ signature.append(symbol);
+ }
+
+ @Override
+ public void parsedIdentifier(String identifier) {
+ signature.append(identifier);
+ }
+
+ @Override
+ public String parsedTypeName(String name) {
+ String type = name;
+ if (originalToObfuscatedMapping != null) {
+ String original = originalToObfuscatedMapping.inverse().get(name);
+ type = original != null ? original : name;
+ }
+ signature.append(type);
+ return type;
+ }
+
+ @Override
+ public String parsedInnerTypeName(String enclosingType, String name) {
+ if (originalToObfuscatedMapping != null) {
+ // The enclosingType has already been mapped if a mapping is present.
+ String minifiedEnclosing = originalToObfuscatedMapping.get(enclosingType);
+ String type = originalToObfuscatedMapping.inverse().get(minifiedEnclosing + "$" + name);
+ if (type != null) {
+ assert type.startsWith(enclosingType + "$");
+ name = type.substring(enclosingType.length() + 1);
+ }
+ signature.append(name);
+ return type;
+ } else {
+ String type = enclosingType + "$" + name;
+ signature.append(name);
+ return type;
+ }
+ }
+
+ @Override
+ public void start() {
+ signature = new StringBuilder();
+ }
+
+ @Override
+ public void stop() {
+ // nothing to do
+ }
+ }
+
+ String finalSignature = getFinalSignatureAttribute();
+ if (finalSignature == null || mapping == null) {
+ return finalSignature;
+ }
+
+ GenericSignatureGenerater rewriter = new GenericSignatureGenerater();
+ GenericSignatureParser<String> parser = new GenericSignatureParser<>(rewriter);
+ parser.parseClassSignature(finalSignature);
+ return rewriter.getSignature();
+ }
+
+ @Override
+ public String getFinalSignatureAttribute() {
+ DexAnnotation annotation = findAnnotation("dalvik.annotation.Signature");
+ if (annotation == null) {
+ return null;
+ }
+ assert annotation.annotation.elements.length == 1;
+ DexAnnotationElement element = annotation.annotation.elements[0];
+ assert element.value instanceof DexValueArray;
+ StringBuilder builder = new StringBuilder();
+ DexValueArray valueArray = (DexValueArray) element.value;
+ for (DexValue value : valueArray.getValues()) {
+ assertTrue(value instanceof DexValueString);
+ DexValueString s = (DexValueString) value;
+ builder.append(s.getValue());
+ }
+ return builder.toString();
+ }
+
+ @Override
public String toString() {
return dexClass.toSourceString();
}
diff --git a/tools/build_r8lib.py b/tools/build_r8lib.py
index c855bc5..4b7473e 100755
--- a/tools/build_r8lib.py
+++ b/tools/build_r8lib.py
@@ -25,16 +25,21 @@
ANDROID_JAR = 'third_party/android_jar/lib-v%s/android.jar' % API_LEVEL
-def build_r8lib():
+def build_r8lib(output_path=None, output_map=None, **kwargs):
+ if output_path is None:
+ output_path = R8LIB_JAR
+ if output_map is None:
+ output_map = R8LIB_MAP_FILE
toolhelper.run(
'r8',
('--release',
'--classfile',
'--lib', utils.RT_JAR,
utils.R8_JAR,
- '--output', R8LIB_JAR,
+ '--output', output_path,
'--pg-conf', utils.R8LIB_KEEP_RULES,
- '--pg-map-output', R8LIB_MAP_FILE))
+ '--pg-map-output', output_map),
+ **kwargs)
def test_d8sample():
diff --git a/tools/r8lib_size_compare.py b/tools/r8lib_size_compare.py
new file mode 100755
index 0000000..4bb49eb
--- /dev/null
+++ b/tools/r8lib_size_compare.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+# 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.
+
+'''
+Build r8lib.jar with both R8 and ProGuard and print a size comparison.
+
+By default, inlining is disabled in both R8 and ProGuard to make
+method-by-method comparison much easier. Pass --inlining to enable inlining.
+
+By default, only shows methods where R8's DEX output is 5 or more instructions
+larger than ProGuard+D8's output. Pass --threshold 0 to display all methods.
+'''
+
+import argparse
+import build_r8lib
+import os
+import subprocess
+import toolhelper
+import utils
+
+
+parser = argparse.ArgumentParser(description=__doc__.strip(),
+ formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument('-t', '--tmpdir',
+ help='Store auxiliary files in given directory')
+parser.add_argument('-i', '--inlining', action='store_true',
+ help='Enable inlining')
+parser.add_argument('--threshold')
+
+R8_RELOCATIONS = [
+ ('com.google.common', 'com.android.tools.r8.com.google.common'),
+ ('com.google.gson', 'com.android.tools.r8.com.google.gson'),
+ ('com.google.thirdparty', 'com.android.tools.r8.com.google.thirdparty'),
+ ('joptsimple', 'com.android.tools.r8.joptsimple'),
+ ('org.apache.commons', 'com.android.tools.r8.org.apache.commons'),
+ ('org.objectweb.asm', 'com.android.tools.r8.org.objectweb.asm'),
+ ('it.unimi.dsi.fastutil', 'com.android.tools.r8.it.unimi.dsi.fastutil'),
+]
+
+
+def is_output_newer(input, output):
+ if not os.path.exists(output):
+ return False
+ return os.stat(input).st_mtime < os.stat(output).st_mtime
+
+
+def check_call(args, **kwargs):
+ utils.PrintCmd(args)
+ return subprocess.check_call(args, **kwargs)
+
+
+def main(tmpdir=None, inlining=True,
+ run_jarsizecompare=True, threshold=None):
+ if tmpdir is None:
+ with utils.TempDir() as tmpdir:
+ return main(tmpdir, inlining)
+
+ inline_suffix = '-inline' if inlining else '-noinline'
+
+ pg_config = utils.R8LIB_KEEP_RULES
+ r8lib_jar = os.path.join(utils.LIBS, 'r8lib%s.jar' % inline_suffix)
+ r8lib_map = os.path.join(utils.LIBS, 'r8lib%s-map.txt' % inline_suffix)
+ r8lib_args = None
+ if not inlining:
+ r8lib_args = ['-Dcom.android.tools.r8.disableinlining=1']
+ pg_config = os.path.join(tmpdir, 'keep-noinline.txt')
+ with open(pg_config, 'w') as new_config:
+ with open(utils.R8LIB_KEEP_RULES) as old_config:
+ new_config.write(old_config.read().rstrip('\n') +
+ '\n-optimizations !method/inlining/*\n')
+
+ if not is_output_newer(utils.R8_JAR, r8lib_jar):
+ r8lib_memory = os.path.join(tmpdir, 'r8lib%s-memory.txt' % inline_suffix)
+ build_r8lib.build_r8lib(
+ output_path=r8lib_jar, output_map=r8lib_map,
+ extra_args=r8lib_args, track_memory_file=r8lib_memory)
+
+ pg_output = os.path.join(tmpdir, 'r8lib-pg%s.jar' % inline_suffix)
+ pg_memory = os.path.join(tmpdir, 'r8lib-pg%s-memory.txt' % inline_suffix)
+ pg_map = os.path.join(tmpdir, 'r8lib-pg%s-map.txt' % inline_suffix)
+ pg_args = ['tools/track_memory.sh', pg_memory,
+ 'third_party/proguard/proguard6.0.2/bin/proguard.sh',
+ '@' + pg_config,
+ '-lib', utils.RT_JAR,
+ '-injar', utils.R8_JAR,
+ '-printmapping', pg_map,
+ '-outjar', pg_output]
+ for library_name, relocated_package in utils.R8_RELOCATIONS:
+ pg_args.extend(['-dontwarn', relocated_package + '.**',
+ '-dontnote', relocated_package + '.**'])
+ check_call(pg_args)
+ if threshold is None:
+ threshold = 5
+ toolhelper.run('jarsizecompare',
+ ['--threshold', str(threshold),
+ '--lib', utils.RT_JAR,
+ '--input', 'input', utils.R8_JAR,
+ '--input', 'r8', r8lib_jar, r8lib_map,
+ '--input', 'pg', pg_output, pg_map])
+
+
+if __name__ == '__main__':
+ main(**vars(parser.parse_args()))
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index a7f509c..82a2824 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -9,7 +9,7 @@
import utils
def run(tool, args, build=None, debug=True,
- profile=False, track_memory_file=None):
+ profile=False, track_memory_file=None, extra_args=None):
if build is None:
build, args = extract_build_from_args(args)
if build:
@@ -18,6 +18,8 @@
if track_memory_file:
cmd.extend(['tools/track_memory.sh', track_memory_file])
cmd.append('java')
+ if extra_args:
+ cmd.extend(extra_args)
if debug:
cmd.append('-ea')
if profile: