Merge "Add test for object and global Kotlin properties"
diff --git a/build.gradle b/build.gradle
index 94c002f..0f782b2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -514,7 +514,7 @@
classifier = null
version = null
manifest {
- attributes 'Main-Class': 'com.android.tools.r8.R8'
+ attributes 'Main-Class': 'com.android.tools.r8.SwissArmyKnife'
}
// In order to build without dependencies, pass the exclude_deps property using:
// gradle -Pexclude_deps R8
@@ -527,164 +527,27 @@
}
task D8(type: ShadowJar) {
- from consolidatedLicense.outputs.files
- exclude { path ->
- path.getRelativePath().getPathString().startsWith("META-INF")
- }
+ from R8.outputs.files
baseName 'd8'
- classifier = null
- version = null
manifest {
attributes 'Main-Class': 'com.android.tools.r8.D8'
}
- // In order to build without dependencies, pass the exclude_deps property using:
- // gradle -Pexclude_deps D8
- if (!project.hasProperty('exclude_deps')) {
- from repackageSources.outputs.files
- from repackageDeps.outputs.files
- } else {
- from sourceSets.main.output
- }
}
-task CompatDx(type: Jar) {
- from sourceSets.main.output
+task CompatDx(type: ShadowJar) {
+ from R8.outputs.files
baseName 'compatdx'
manifest {
attributes 'Main-Class': 'com.android.tools.r8.compatdx.CompatDx'
}
- // In order to build without dependencies, pass the exclude_deps property using:
- // gradle -Pexclude_deps CompatDx
- if (!project.hasProperty('exclude_deps')) {
- // Also include dependencies
- from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
- }
}
-task DexFileMerger(type: Jar) {
- from sourceSets.main.output
- baseName 'dexfilemerger'
- manifest {
- attributes 'Main-Class': 'com.android.tools.r8.dexfilemerger.DexFileMerger'
- }
- // In order to build without dependencies, pass the exclude_deps property using:
- // gradle -Pexclude_deps CompatDx
- if (!project.hasProperty('exclude_deps')) {
- // Also include dependencies
- from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
- }
-}
-
-task DexSplitter(type: Jar) {
- from sourceSets.main.output
- baseName 'dexsplitter'
- manifest {
- attributes 'Main-Class': 'com.android.tools.r8.dexsplitter.DexSplitter'
- }
- // In order to build without dependencies, pass the exclude_deps property using:
- // gradle -Pexclude_deps CompatDx
- if (!project.hasProperty('exclude_deps')) {
- // Also include dependencies
- from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
- }
-}
-
-task CompatProguard(type: Jar) {
- from sourceSets.main.output
+task CompatProguard(type: ShadowJar) {
+ from R8.outputs.files
baseName 'compatproguard'
manifest {
attributes 'Main-Class': 'com.android.tools.r8.compatproguard.CompatProguard'
}
- // In order to build without dependencies, pass the exclude_deps property using:
- // gradle -Pexclude_deps CompatProguard
- if (!project.hasProperty('exclude_deps')) {
- // Also include dependencies
- from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
- }
-}
-
-task D8Logger(type: Jar) {
- from sourceSets.main.output
- baseName 'd8logger'
- manifest {
- attributes 'Main-Class': 'com.android.tools.r8.D8Logger'
- }
- // In order to build without dependencies, pass the exclude_deps property using:
- // gradle -Pexclude_deps D8Logger
- if (!project.hasProperty('exclude_deps')) {
- // Also include dependencies
- from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
- }
-}
-
-task disasm(type: Jar) {
- from sourceSets.main.output
- baseName 'disasm'
- manifest {
- attributes 'Main-Class': 'com.android.tools.r8.Disassemble'
- }
- // In order to build without dependencies, pass the exclude_deps property using:
- // gradle -Pexclude_deps D8
- if (!project.hasProperty('exclude_deps')) {
- // Also include dependencies
- from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
- }
-}
-
-task bisect(type: Jar) {
- from sourceSets.main.output
- baseName 'bisect'
- manifest {
- attributes 'Main-Class': 'com.android.tools.r8.bisect.Bisect'
- }
- // In order to build without dependencies, pass the exclude_deps property using:
- // gradle -Pexclude_deps R8
- if (!project.hasProperty('exclude_deps')) {
- // Also include dependencies
- from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
- }
-}
-
-task DexSegments(type: Jar) {
- from sourceSets.main.output
- baseName 'dexsegments'
- manifest {
- attributes 'Main-Class': 'com.android.tools.r8.DexSegments'
- }
- // In order to build without dependencies, pass the exclude_deps property using:
- // gradle -Pexclude_deps DexSegments
- if (!project.hasProperty('exclude_deps')) {
- // Also include dependencies
- from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
- }
-}
-
-task maindex(type: Jar) {
- from sourceSets.main.output
- baseName 'maindex'
- manifest {
- attributes 'Main-Class': 'com.android.tools.r8.GenerateMainDexList'
- }
- // In order to build without dependencies, pass the exclude_deps property using:
- // gradle -Pexclude_deps maindex
- if (!project.hasProperty('exclude_deps')) {
- // Also include dependencies
- from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
- }
-}
-
-task ExtractMarker(type: Jar) {
- from sourceSets.main.output
- baseName 'extractmarker'
- manifest {
- attributes 'Main-Class': 'com.android.tools.r8.ExtractMarker'
- }
- // In order to build without dependencies, pass the exclude_deps property using:
- // gradle -Pexclude_deps ExtractMarker
- if (!project.hasProperty('exclude_deps')) {
- // Also include dependencies
- from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
- }
}
task bspatch(type: Jar) {
@@ -694,7 +557,7 @@
attributes 'Main-Class': 'com.android.tools.r8.dex.BSPatch'
}
// In order to build without dependencies, pass the exclude_deps property using:
- // gradle -Pexclude_deps maindex
+ // gradle -Pexclude_deps bspatch
if (!project.hasProperty('exclude_deps')) {
// Also include dependencies
from {
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 3db9699..26097c1 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -59,7 +59,7 @@
// Print -whyareyoukeeping results if any.
if (mainDexRootSet.reasonAsked.size() > 0) {
// Print reasons on the application after pruning, so that we reflect the actual result.
- TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
+ TreePruner pruner = new TreePruner(application, mainDexAppInfo.withLiveness(), options);
application = pruner.run();
ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(mainDexRootSet.reasonAsked);
reasonPrinter.run(application);
diff --git a/src/main/java/com/android/tools/r8/JarDiff.java b/src/main/java/com/android/tools/r8/JarDiff.java
new file mode 100644
index 0000000..dad57d4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/JarDiff.java
@@ -0,0 +1,260 @@
+// 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 static com.google.common.io.ByteStreams.toByteArray;
+
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.graph.ClassKind;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.JarApplicationReader;
+import com.android.tools.r8.graph.JarClassFileReader;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.InternalOptions;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+/**
+ * Command-line program to compare two JARs. Given two JARs as input, the program first outputs a
+ * list of classes only in one of the input JARs. Then, for each common class, the program outputs a
+ * list of methods only in one of the input JARs. For each common method, the output of
+ * CfInstruction.toString() on each instruction is compared to find instruction-level differences. A
+ * simple diffing algorithm is used that simply removes the common prefix and common suffix and
+ * prints everything from the first difference to the last difference in the method code.
+ */
+public class JarDiff {
+
+ private static final String USAGE =
+ "Arguments: <input1.jar> <input2.jar>\n"
+ + "\n"
+ + "JarDiff computes the difference between two JAR files that contain Java classes.\n"
+ + "\n"
+ + "Only method codes are compared. Fields, parameters, annotations, generic\n"
+ + "signatures etc. are ignored.\n"
+ + "\n"
+ + "Note: Jump targets are ignored, so if two methods differ only in what label an\n"
+ + "IF or GOTO instruction jumps to, no difference is output.";
+
+ public static void main(String[] args) throws Exception {
+ JarDiff jarDiff = JarDiff.parse(args);
+ if (jarDiff == null) {
+ System.out.println(USAGE);
+ } else {
+ jarDiff.run();
+ }
+ }
+
+ public static JarDiff parse(String[] args) {
+ int arg = 0;
+ int before = 3;
+ int after = 3;
+ while (arg + 1 < args.length) {
+ if (args[arg].equals("-B")) {
+ before = Integer.parseInt(args[arg + 1]);
+ arg += 2;
+ } else if (args[arg].equals("-A")) {
+ after = Integer.parseInt(args[arg + 1]);
+ arg += 2;
+ } else {
+ break;
+ }
+ }
+ if (args.length != arg + 2) {
+ return null;
+ }
+ return new JarDiff(Paths.get(args[arg]), Paths.get(args[arg + 1]), before, after);
+ }
+
+ private final Path path1;
+ private final Path path2;
+ private final int before;
+ private final int after;
+ private final JarApplicationReader applicationReader;
+ private ArchiveClassFileProvider archive1;
+ private ArchiveClassFileProvider archive2;
+
+ public JarDiff(Path path1, Path path2, int before, int after) {
+ this.path1 = path1;
+ this.path2 = path2;
+ this.before = before;
+ this.after = after;
+ InternalOptions options = new InternalOptions();
+ options.enableCfFrontend = true;
+ applicationReader = new JarApplicationReader(options);
+ }
+
+ public void run() throws Exception {
+ archive1 = new ArchiveClassFileProvider(path1);
+ archive2 = new ArchiveClassFileProvider(path2);
+ for (String descriptor : getCommonDescriptors()) {
+ byte[] bytes1 = getClassAsBytes(archive1, descriptor);
+ byte[] bytes2 = getClassAsBytes(archive2, descriptor);
+ if (Arrays.equals(bytes1, bytes2)) {
+ continue;
+ }
+ DexProgramClass class1 = getDexProgramClass(path1, bytes1);
+ DexProgramClass class2 = getDexProgramClass(path2, bytes2);
+ compareMethods(class1, class2);
+ }
+ }
+
+ private List<String> getCommonDescriptors() {
+ List<String> descriptors1 = getSortedDescriptorList(archive1);
+ List<String> descriptors2 = getSortedDescriptorList(archive2);
+ if (descriptors1.equals(descriptors2)) {
+ return descriptors1;
+ }
+ List<String> only1 = setMinus(descriptors1, descriptors2);
+ List<String> only2 = setMinus(descriptors2, descriptors1);
+ if (!only1.isEmpty()) {
+ System.out.println("Only in " + path1 + ": " + only1);
+ }
+ if (!only2.isEmpty()) {
+ System.out.println("Only in " + path2 + ": " + only2);
+ }
+ return setIntersection(descriptors1, descriptors2);
+ }
+
+ private List<String> getSortedDescriptorList(ArchiveClassFileProvider inputJar) {
+ ArrayList<String> descriptorList = new ArrayList<>(inputJar.getClassDescriptors());
+ Collections.sort(descriptorList);
+ return descriptorList;
+ }
+
+ private byte[] getClassAsBytes(ArchiveClassFileProvider inputJar, String descriptor)
+ throws Exception {
+ return toByteArray(inputJar.getProgramResource(descriptor).getByteStream());
+ }
+
+ private DexProgramClass getDexProgramClass(Path path, byte[] bytes) throws IOException {
+
+ class Collector implements Consumer<DexClass> {
+
+ private DexClass dexClass;
+
+ @Override
+ public void accept(DexClass dexClass) {
+ this.dexClass = dexClass;
+ }
+
+ public DexClass get() {
+ assert dexClass != null;
+ return dexClass;
+ }
+ }
+
+ Collector collector = new Collector();
+ JarClassFileReader reader = new JarClassFileReader(applicationReader, collector);
+ reader.read(new PathOrigin(path), ClassKind.PROGRAM, new ByteArrayInputStream(bytes));
+ return collector.get().asProgramClass();
+ }
+
+ private void compareMethods(DexProgramClass class1, DexProgramClass class2) {
+ class1.forEachMethod(
+ method1 -> {
+ DexEncodedMethod method2 = class2.lookupMethod(method1.method);
+ if (method2 == null) {
+ compareMethods(method1, method2);
+ }
+ });
+ class2.forEachMethod(
+ method2 -> {
+ DexEncodedMethod method1 = class1.lookupMethod(method2.method);
+ compareMethods(method1, method2);
+ });
+ }
+
+ private void compareMethods(DexEncodedMethod m1, DexEncodedMethod m2) {
+ if (m1 == null) {
+ System.out.println("Only in " + path2 + ": " + m2.method.toSourceString());
+ return;
+ }
+ if (m2 == null) {
+ System.out.println("Only in " + path1 + ": " + m1.method.toSourceString());
+ return;
+ }
+ List<String> code1 = getInstructionStrings(m1);
+ List<String> code2 = getInstructionStrings(m2);
+ if (code1.equals(code2)) {
+ return;
+ }
+ int i = getCommonPrefix(code1, code2);
+ int j = getCommonSuffix(code1, code2);
+ int length1 = code1.size() - i - j;
+ int length2 = code2.size() - i - j;
+ int before = Math.min(i, this.before);
+ int after = Math.min(j, this.after);
+ int context = before + after;
+ System.out.println("--- " + path1 + "/" + m1.method.toSmaliString());
+ System.out.println("+++ " + path2 + "/" + m2.method.toSmaliString());
+ System.out.println(
+ "@@ -" + (i - before) + "," + (length1 + context)
+ + " +" + (i - before) + "," + (length2 + context) + " @@ "
+ + m1.method.toSourceString());
+ for (int k = 0; k < before; k++) {
+ System.out.println(" " + code1.get(i - before + k));
+ }
+ for (int k = 0; k < length1; k++) {
+ System.out.println("-" + code1.get(i + k));
+ }
+ for (int k = 0; k < length2; k++) {
+ System.out.println("+" + code2.get(i + k));
+ }
+ for (int k = 0; k < after; k++) {
+ System.out.println(" " + code1.get(i + length1 + k));
+ }
+ }
+
+ private static List<String> getInstructionStrings(DexEncodedMethod method) {
+ List<CfInstruction> instructions = method.getCode().asCfCode().getInstructions();
+ return instructions.stream().map(CfInstruction::toString).collect(Collectors.toList());
+ }
+
+ private static List<String> setIntersection(List<String> set1, List<String> set2) {
+ ArrayList<String> result = new ArrayList<>(set1);
+ result.retainAll(new HashSet<>(set2));
+ return result;
+ }
+
+ private static List<String> setMinus(List<String> set, List<String> toRemove) {
+ ArrayList<String> result = new ArrayList<>(set);
+ result.removeAll(new HashSet<>(toRemove));
+ return result;
+ }
+
+ private static int getCommonPrefix(List<String> code1, List<String> code2) {
+ int i = 0;
+ while (i < code1.size() && i < code2.size()) {
+ if (code1.get(i).equals(code2.get(i))) {
+ i++;
+ } else {
+ break;
+ }
+ }
+ return i;
+ }
+
+ private static int getCommonSuffix(List<String> code1, List<String> code2) {
+ int j = 0;
+ while (j < code1.size() && j < code2.size()) {
+ if (code1.get(code1.size() - j - 1).equals(code2.get(code2.size() - j - 1))) {
+ j++;
+ } else {
+ break;
+ }
+ }
+ return j;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index a52d694..9d6e40d 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -12,9 +12,11 @@
import com.android.tools.r8.shaking.ProguardConfigurationParser;
import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.android.tools.r8.shaking.ProguardConfigurationSource;
+import com.android.tools.r8.shaking.ProguardConfigurationSourceBytes;
import com.android.tools.r8.shaking.ProguardConfigurationSourceFile;
import com.android.tools.r8.shaking.ProguardConfigurationSourceStrings;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
@@ -22,15 +24,17 @@
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Objects;
import java.util.function.Consumer;
/**
- * Immutable command structure for an invocation of the {@link D8} compiler.
+ * Immutable command structure for an invocation of the {@link R8} compiler.
*
* <p>To build a R8 command use the {@link R8Command.Builder} class. For example:
*
@@ -233,7 +237,7 @@
Path path,
OutputMode mode,
boolean consumeDataResources) {
- return super.createProgramOutputConsumer(path, mode, true);
+ return super.createProgramOutputConsumer(path, mode, false);
}
@Override
@@ -282,21 +286,54 @@
mainDexKeepRules = parser.getConfig().getRules();
}
- ProguardConfiguration.Builder configurationBuilder;
- if (proguardConfigs.isEmpty()) {
- configurationBuilder = ProguardConfiguration.builder(factory, reporter);
- } else {
- ProguardConfigurationParser parser =
- new ProguardConfigurationParser(factory, reporter);
+ ProguardConfigurationParser parser = new ProguardConfigurationParser(factory, reporter);
+ if (!proguardConfigs.isEmpty()) {
parser.parse(proguardConfigs);
- configurationBuilder = parser.getConfigurationBuilder();
- configurationBuilder.setForceProguardCompatibility(forceProguardCompatibility);
}
+ ProguardConfiguration.Builder configurationBuilder = parser.getConfigurationBuilder();
+ configurationBuilder.setForceProguardCompatibility(forceProguardCompatibility);
if (proguardConfigurationConsumer != null) {
proguardConfigurationConsumer.accept(configurationBuilder);
}
+ // Process Proguard configurations supplied through data resources in the input.
+ DataResourceProvider.Visitor embeddedProguardConfigurationVisitor =
+ new DataResourceProvider.Visitor() {
+ @Override
+ public void visit(DataDirectoryResource directory) {
+ // Don't do anything.
+ }
+
+ @Override
+ public void visit(DataEntryResource resource) {
+ if (resource.getName().startsWith("META-INF/proguard/")) {
+ try (InputStream in = resource.getByteStream()) {
+ ProguardConfigurationSource source =
+ new ProguardConfigurationSourceBytes(in, resource.getOrigin());
+ parser.parse(source);
+ } catch (ResourceException e) {
+ reporter.error(new StringDiagnostic("Failed to open input: " + e.getMessage(),
+ resource.getOrigin()));
+ } catch (Exception e) {
+ reporter.error(new ExceptionDiagnostic(e, resource.getOrigin()));
+ }
+ }
+ }
+ };
+
+ getAppBuilder().getProgramResourceProviders()
+ .stream()
+ .map(ProgramResourceProvider::getDataResourceProvider)
+ .filter(Objects::nonNull)
+ .forEach(dataResourceProvider -> {
+ try {
+ dataResourceProvider.accept(embeddedProguardConfigurationVisitor);
+ } catch (ResourceException e) {
+ reporter.error(new ExceptionDiagnostic(e));
+ }
+ });
+
if (disableTreeShaking) {
configurationBuilder.disableShrinking();
}
@@ -369,6 +406,11 @@
}
return resources;
}
+
+ @Override
+ public DataResourceProvider getDataResourceProvider() {
+ return provider.getDataResourceProvider();
+ }
}
// Internal state to verify parsing properties not enforced by the builder.
diff --git a/src/main/java/com/android/tools/r8/SwissArmyKnife.java b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
new file mode 100644
index 0000000..0e35847
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/SwissArmyKnife.java
@@ -0,0 +1,83 @@
+// 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.bisect.Bisect;
+import com.android.tools.r8.compatdx.CompatDx;
+import com.android.tools.r8.compatproguard.CompatProguard;
+import com.android.tools.r8.dexfilemerger.DexFileMerger;
+import com.android.tools.r8.dexsplitter.DexSplitter;
+import java.util.Arrays;
+
+/**
+ * Common entry point to everything in the R8 project.
+ *
+ * <p>This class is used as the main class in {@code r8.jar}. It checks the first command-line
+ * argument to find the tool to run, or runs {@link R8} if the first argument is not a recognized
+ * tool name.
+ *
+ * <p>The set of tools recognized by this class is defined by a switch statement in {@link
+ * SwissArmyKnife#main(String[])}.
+ */
+public class SwissArmyKnife {
+
+ public static void main(String[] args) throws Exception {
+ if (args.length == 0) {
+ runDefault(args);
+ return;
+ }
+ switch (args[0]) {
+ case "r8":
+ R8.main(shift(args));
+ break;
+ case "d8":
+ D8.main(shift(args));
+ break;
+ case "compatdx":
+ CompatDx.main(shift(args));
+ break;
+ case "dexfilemerger":
+ DexFileMerger.main(shift(args));
+ break;
+ case "dexsplitter":
+ DexSplitter.main(shift(args));
+ break;
+ case "compatproguard":
+ CompatProguard.main(shift(args));
+ break;
+ case "d8logger":
+ D8Logger.main(shift(args));
+ break;
+ case "disasm":
+ Disassemble.main(shift(args));
+ break;
+ case "bisect":
+ Bisect.main(shift(args));
+ break;
+ case "dexsegments":
+ DexSegments.main(shift(args));
+ break;
+ case "maindex":
+ GenerateMainDexList.main(shift(args));
+ break;
+ case "extractmarker":
+ ExtractMarker.main(shift(args));
+ break;
+ case "jardiff":
+ JarDiff.main(shift(args));
+ break;
+ default:
+ runDefault(args);
+ break;
+ }
+ }
+
+ private static void runDefault(String[] args) {
+ R8.main(args);
+ }
+
+ private static String[] shift(String[] args) {
+ return Arrays.copyOfRange(args, 1, args.length);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 124bccc..98ed5e9 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.2.16-dev";
+ public static final String LABEL = "1.2.17-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 df16622..382bc66 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationDirectory;
import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexDebugInfo;
@@ -30,6 +29,7 @@
import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.ProguardMapSupplier;
import com.android.tools.r8.utils.DescriptorUtils;
@@ -106,7 +106,7 @@
}
@Override
- public boolean add(DexAnnotationSetRefList annotationSetRefList) {
+ public boolean add(ParameterAnnotationsList parameterAnnotationsList) {
return true;
}
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 8042a1a..d762646 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -19,7 +19,6 @@
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationElement;
import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexCode;
@@ -56,6 +55,7 @@
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.OffsetToObjectMapping;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
@@ -340,11 +340,11 @@
return result;
}
- private DexAnnotationSetRefList annotationSetRefListAt(int offset) {
- return (DexAnnotationSetRefList) cacheAt(offset, this::parseAnnotationSetRefList);
+ private ParameterAnnotationsList annotationSetRefListAt(int offset) {
+ return (ParameterAnnotationsList) cacheAt(offset, this::parseAnnotationSetRefList);
}
- private DexAnnotationSetRefList parseAnnotationSetRefList() {
+ private ParameterAnnotationsList parseAnnotationSetRefList() {
int size = dexReader.getUint();
int[] annotationOffsets = new int[size];
for (int i = 0; i < size; i++) {
@@ -354,7 +354,7 @@
for (int i = 0; i < size; i++) {
values[i] = annotationSetAt(annotationOffsets[i]);
}
- return new DexAnnotationSetRefList(values);
+ return new ParameterAnnotationsList(values);
}
private DexParameterAnnotation[] parseParameterAnnotations(int size) {
@@ -373,7 +373,8 @@
DexMethod method = indexedItems.getMethod(methodIndices[i]);
result[i] = new DexParameterAnnotation(
method,
- annotationSetRefListAt(annotationOffsets[i]));
+ annotationSetRefListAt(annotationOffsets[i])
+ .withParameterCount(method.proto.parameters.size()));
}
dexReader.position(saved);
return result;
@@ -589,8 +590,8 @@
int methodIndex = 0;
MemberAnnotationIterator<DexMethod, DexAnnotationSet> annotationIterator =
new MemberAnnotationIterator<>(annotations, DexAnnotationSet::empty);
- MemberAnnotationIterator<DexMethod, DexAnnotationSetRefList> parameterAnnotationsIterator =
- new MemberAnnotationIterator<>(parameters, DexAnnotationSetRefList::empty);
+ MemberAnnotationIterator<DexMethod, ParameterAnnotationsList> parameterAnnotationsIterator =
+ new MemberAnnotationIterator<>(parameters, ParameterAnnotationsList::empty);
for (int i = 0; i < size; i++) {
methodIndex += dexReader.getUleb128();
MethodAccessFlags accessFlags = MethodAccessFlags.fromDexAccessFlags(dexReader.getUleb128());
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index ced33f6..4767c64 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -13,7 +13,6 @@
import com.android.tools.r8.graph.DexAnnotationDirectory;
import com.android.tools.r8.graph.DexAnnotationElement;
import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
@@ -40,6 +39,7 @@
import com.android.tools.r8.graph.IndexedDexItem;
import com.android.tools.r8.graph.KeyedDexItem;
import com.android.tools.r8.graph.ObjectToOffsetMapping;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.graph.ProgramClassVisitor;
import com.android.tools.r8.logging.Log;
@@ -510,12 +510,17 @@
}
}
- private void writeAnnotationSetRefList(DexAnnotationSetRefList setRefList) {
- assert !setRefList.isEmpty();
- mixedSectionOffsets.setOffsetFor(setRefList, dest.align(4));
- dest.putInt(setRefList.values.length);
- for (DexAnnotationSet set : setRefList.values) {
- dest.putInt(mixedSectionOffsets.getOffsetFor(set));
+ private void writeAnnotationSetRefList(ParameterAnnotationsList parameterAnnotationsList) {
+ assert !parameterAnnotationsList.isEmpty();
+ mixedSectionOffsets.setOffsetFor(parameterAnnotationsList, dest.align(4));
+ dest.putInt(parameterAnnotationsList.countNonMissing());
+ for (int i = 0; i < parameterAnnotationsList.size(); i++) {
+ if (parameterAnnotationsList.isMissing(i)) {
+ // b/62300145: Maintain broken ParameterAnnotations attribute by only outputting the
+ // non-missing annotation lists.
+ continue;
+ }
+ dest.putInt(mixedSectionOffsets.getOffsetFor(parameterAnnotationsList.get(i)));
}
}
@@ -541,7 +546,7 @@
writeMemberAnnotations(methodAnnotations,
item -> mixedSectionOffsets.getOffsetFor(item.annotations));
writeMemberAnnotations(parameterAnnotations,
- item -> mixedSectionOffsets.getOffsetFor(item.parameterAnnotations));
+ item -> mixedSectionOffsets.getOffsetFor(item.parameterAnnotationsList));
}
private void writeEncodedFields(DexEncodedField[] fields) {
@@ -990,7 +995,7 @@
private final Reference2IntMap<DexString> stringData = createReference2IntMap();
private final Object2IntMap<DexAnnotation> annotations = createObject2IntMap();
private final Object2IntMap<DexAnnotationSet> annotationSets = createObject2IntMap();
- private final Object2IntMap<DexAnnotationSetRefList> annotationSetRefLists
+ private final Object2IntMap<ParameterAnnotationsList> annotationSetRefLists
= createObject2IntMap();
private final Object2IntMap<DexAnnotationDirectory> annotationDirectories
= createObject2IntMap();
@@ -1072,7 +1077,7 @@
}
@Override
- public boolean add(DexAnnotationSetRefList annotationSetRefList) {
+ public boolean add(ParameterAnnotationsList annotationSetRefList) {
if (annotationSetRefList.isEmpty()) {
return false;
}
@@ -1120,7 +1125,7 @@
return annotationSets.keySet();
}
- public Collection<DexAnnotationSetRefList> getAnnotationSetRefLists() {
+ public Collection<ParameterAnnotationsList> getAnnotationSetRefLists() {
return annotationSetRefLists.keySet();
}
@@ -1200,7 +1205,7 @@
return lookup(annotationSet, annotationSets);
}
- public int getOffsetFor(DexAnnotationSetRefList annotationSetRefList) {
+ public int getOffsetFor(ParameterAnnotationsList annotationSetRefList) {
if (annotationSetRefList.isEmpty()) {
return 0;
}
@@ -1261,7 +1266,7 @@
setOffsetFor(encodedArray, offset, encodedArrays);
}
- void setOffsetFor(DexAnnotationSetRefList annotationSetRefList, int offset) {
+ void setOffsetFor(ParameterAnnotationsList annotationSetRefList, int offset) {
assert offset != 0 && !annotationSetRefList.isEmpty();
setOffsetFor(annotationSetRefList, offset, annotationSetRefLists);
}
diff --git a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
index eb7ae98..dee12fc 100644
--- a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
+++ b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
@@ -6,13 +6,13 @@
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationDirectory;
import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexDebugInfo;
import com.android.tools.r8.graph.DexEncodedArray;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
/**
* Collection of the various components of the mixed section of a dex file.
@@ -86,7 +86,7 @@
*
* @return true if the item was not added before
*/
- public abstract boolean add(DexAnnotationSetRefList annotationSetRefList);
+ public abstract boolean add(ParameterAnnotationsList annotationSetRefList);
/**
* Adds the given annotation to the collection.
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 1ac2246..61595b9 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -5,23 +5,15 @@
import com.android.tools.r8.ApiLevelException;
import com.android.tools.r8.cf.CfPrinter;
-import com.android.tools.r8.cf.code.CfConstNull;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfLabel;
import com.android.tools.r8.cf.code.CfPosition;
import com.android.tools.r8.cf.code.CfReturnVoid;
-import com.android.tools.r8.cf.code.CfThrow;
import com.android.tools.r8.cf.code.CfTryCatch;
import com.android.tools.r8.errors.Unimplemented;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.ConstNumber;
import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Position;
-import com.android.tools.r8.ir.code.Throw;
-import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
-import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.conversion.CfSourceCode;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.naming.ClassNameMapper;
@@ -29,7 +21,6 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
import java.util.Collections;
-import java.util.LinkedList;
import java.util.List;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
@@ -211,21 +202,6 @@
@Override
public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options, Origin origin)
throws ApiLevelException {
- if (instructions.size() == 2
- && instructions.get(0) instanceof CfConstNull
- && instructions.get(1) instanceof CfThrow) {
- BasicBlock block = new BasicBlock();
- block.setNumber(1);
- Value nullValue = new Value(0, ValueType.OBJECT, null);
- block.add(new ConstNumber(nullValue, 0L));
- block.add(new Throw(nullValue));
- block.close(null);
- for (Instruction insn : block.getInstructions()) {
- insn.setPosition(Position.none());
- }
- LinkedList<BasicBlock> blocks = new LinkedList<>(Collections.singleton(block));
- return new IRCode(options, encodedMethod, blocks, null, false);
- }
return internalBuild(encodedMethod, options, null, null, origin);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
index aa5d145..fa5d8a8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
@@ -33,7 +33,7 @@
if (!method.annotations.isEmpty()) {
methodAnnotations.add(method);
}
- if (!method.parameterAnnotations.isEmpty()) {
+ if (!method.parameterAnnotationsList.isEmpty()) {
parameterAnnotations.add(method);
}
}
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 9ee8fba..d093a20 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationSet.java
@@ -5,7 +5,9 @@
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.MixedSectionCollection;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.function.Predicate;
public class DexAnnotationSet extends CachedHashValueDexItem {
@@ -115,4 +117,30 @@
extendedArray[annotations.length] = newAnnotation;
return new DexAnnotationSet(extendedArray);
}
+
+ 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 this;
+ } else if (filtered.isEmpty()) {
+ return DexAnnotationSet.empty();
+ } else {
+ return new DexAnnotationSet(filtered.toArray(new DexAnnotation[filtered.size()]));
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationSetRefList.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationSetRefList.java
deleted file mode 100644
index 4bb1314..0000000
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationSetRefList.java
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.graph;
-
-import com.android.tools.r8.dex.IndexedItemCollection;
-import com.android.tools.r8.dex.MixedSectionCollection;
-import java.util.Arrays;
-
-public class DexAnnotationSetRefList extends DexItem {
-
- private static final DexAnnotationSetRefList theEmptyTypeList = new DexAnnotationSetRefList();
-
- public final DexAnnotationSet[] values;
- private final int missingParameterAnnotations;
-
- public static DexAnnotationSetRefList empty() {
- return theEmptyTypeList;
- }
-
- private DexAnnotationSetRefList() {
- this.values = new DexAnnotationSet[0];
- this.missingParameterAnnotations = 0;
- }
-
- public DexAnnotationSetRefList(DexAnnotationSet[] values) {
- this(values, 0);
- }
-
- public DexAnnotationSetRefList(DexAnnotationSet[] values, int missingParameterAnnotations) {
- assert values != null && values.length > 0;
- this.values = values;
- this.missingParameterAnnotations = missingParameterAnnotations;
- }
-
- @Override
- public int hashCode() {
- return Arrays.hashCode(values);
- }
-
- @Override
- public boolean equals(Object other) {
- if (this == other) {
- return true;
- }
- if (other instanceof DexAnnotationSetRefList) {
- return Arrays.equals(values, ((DexAnnotationSetRefList) other).values);
- }
- return false;
- }
-
- @Override
- public void collectIndexedItems(IndexedItemCollection indexedItems,
- DexMethod method, int instructionOffset) {
- collectAll(indexedItems, values);
- }
-
- @Override
- void collectMixedSectionItems(MixedSectionCollection mixedItems) {
- // Collect values first so that the annotation sets have sorted themselves before adding this.
- collectAll(mixedItems, values);
- mixedItems.add(this);
- }
-
- public boolean isEmpty() {
- return values.length == 0;
- }
-
- public int getMissingParameterAnnotations() {
- return missingParameterAnnotations;
- }
-}
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 7b09cab..23139c5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -159,21 +159,13 @@
for (DexAnnotation annotation : method.annotations.annotations) {
consumer.accept(annotation);
}
- for (DexAnnotationSet parameterAnnotations : method.parameterAnnotations.values) {
- for (DexAnnotation annotation : parameterAnnotations.annotations) {
- consumer.accept(annotation);
- }
- }
+ method.parameterAnnotationsList.forEachAnnotation(consumer);
}
for (DexEncodedMethod method : virtualMethods()) {
for (DexAnnotation annotation : method.annotations.annotations) {
consumer.accept(annotation);
}
- for (DexAnnotationSet parameterAnnotations : method.parameterAnnotations.values) {
- for (DexAnnotation annotation : parameterAnnotations.annotations) {
- consumer.accept(annotation);
- }
- }
+ method.parameterAnnotationsList.forEachAnnotation(consumer);
}
for (DexEncodedField field : instanceFields()) {
for (DexAnnotation annotation : field.annotations.annotations) {
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 d514a9a..0cabaf1 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -93,7 +93,7 @@
public final DexMethod method;
public final MethodAccessFlags accessFlags;
public DexAnnotationSet annotations;
- public DexAnnotationSetRefList parameterAnnotations;
+ public ParameterAnnotationsList parameterAnnotationsList;
private Code code;
private CompilationState compilationState = CompilationState.NOT_PROCESSED;
private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT;
@@ -102,12 +102,12 @@
DexMethod method,
MethodAccessFlags accessFlags,
DexAnnotationSet annotations,
- DexAnnotationSetRefList parameterAnnotations,
+ ParameterAnnotationsList parameterAnnotationsList,
Code code) {
this.method = method;
this.accessFlags = accessFlags;
this.annotations = annotations;
- this.parameterAnnotations = parameterAnnotations;
+ this.parameterAnnotationsList = parameterAnnotationsList;
this.code = code;
assert code == null || !accessFlags.isAbstract();
}
@@ -272,7 +272,7 @@
code.collectIndexedItems(indexedItems, this.method);
}
annotations.collectIndexedItems(indexedItems);
- parameterAnnotations.collectIndexedItems(indexedItems);
+ parameterAnnotationsList.collectIndexedItems(indexedItems);
}
@Override
@@ -281,7 +281,7 @@
code.collectMixedSectionItems(mixedItems);
}
annotations.collectMixedSectionItems(mixedItems);
- parameterAnnotations.collectMixedSectionItems(mixedItems);
+ parameterAnnotationsList.collectMixedSectionItems(mixedItems);
}
public Code getCode() {
@@ -537,7 +537,7 @@
}
public boolean hasAnnotation() {
- return !annotations.isEmpty() || !parameterAnnotations.isEmpty();
+ return !annotations.isEmpty() || !parameterAnnotationsList.isEmpty();
}
public void registerCodeReferences(UseRegistry registry) {
@@ -727,7 +727,7 @@
private DexMethod method;
private final MethodAccessFlags accessFlags;
private final DexAnnotationSet annotations;
- private final DexAnnotationSetRefList parameterAnnotations;
+ private final ParameterAnnotationsList parameterAnnotations;
private Code code;
private CompilationState compilationState = CompilationState.NOT_PROCESSED;
private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT;
@@ -737,7 +737,7 @@
method = from.method;
accessFlags = from.accessFlags.copy();
annotations = from.annotations;
- parameterAnnotations = from.parameterAnnotations;
+ parameterAnnotations = from.parameterAnnotationsList;
code = from.code;
compilationState = from.compilationState;
optimizationInfo = from.optimizationInfo.copy();
diff --git a/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java
index f005588..6cc2fb4 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMemberAnnotation.java
@@ -60,9 +60,9 @@
}
public static class DexParameterAnnotation extends
- DexMemberAnnotation<DexMethod, DexAnnotationSetRefList> {
+ DexMemberAnnotation<DexMethod, ParameterAnnotationsList> {
- public DexParameterAnnotation(DexMethod item, DexAnnotationSetRefList annotations) {
+ public DexParameterAnnotation(DexMethod item, ParameterAnnotationsList annotations) {
super(item, annotations);
}
}
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 e403c14..02f0801 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -446,7 +446,7 @@
private List<DexAnnotation> annotations = null;
private DexValue defaultAnnotation = null;
private int fakeParameterAnnotations = 0;
- private List<List<DexAnnotation>> parameterAnnotations = null;
+ private List<List<DexAnnotation>> parameterAnnotationsLists = null;
private List<DexValue> parameterNames = null;
private List<DexValue> parameterFlags = null;
final DexMethod method;
@@ -508,21 +508,21 @@
// We can iterate through all the parameters twice. Once for visible and once for
// invisible parameter annotations. We only record the number of fake parameter
// annotations once.
- if (parameterAnnotations == null) {
+ if (parameterAnnotationsLists == null) {
fakeParameterAnnotations++;
}
return null;
}
- if (parameterAnnotations == null) {
+ if (parameterAnnotationsLists == null) {
int adjustedParameterCount = parameterCount - fakeParameterAnnotations;
- parameterAnnotations = new ArrayList<>(adjustedParameterCount);
+ parameterAnnotationsLists = new ArrayList<>(adjustedParameterCount);
for (int i = 0; i < adjustedParameterCount; i++) {
- parameterAnnotations.add(new ArrayList<>());
+ parameterAnnotationsLists.add(new ArrayList<>());
}
}
assert mv == null;
return createAnnotationVisitor(desc, visible,
- parameterAnnotations.get(parameter - fakeParameterAnnotations), parent.application);
+ parameterAnnotationsLists.get(parameter - fakeParameterAnnotations), parent.application);
}
@Override
@@ -573,15 +573,15 @@
public void visitEnd() {
assert flags.isAbstract() || flags.isNative() || parent.classKind != ClassKind.PROGRAM
|| code != null;
- DexAnnotationSetRefList parameterAnnotationSets;
- if (parameterAnnotations == null) {
- parameterAnnotationSets = DexAnnotationSetRefList.empty();
+ ParameterAnnotationsList annotationsList;
+ if (parameterAnnotationsLists == null) {
+ annotationsList = ParameterAnnotationsList.empty();
} else {
- DexAnnotationSet[] sets = new DexAnnotationSet[parameterAnnotations.size()];
- for (int i = 0; i < parameterAnnotations.size(); i++) {
- sets[i] = createAnnotationSet(parameterAnnotations.get(i));
+ DexAnnotationSet[] sets = new DexAnnotationSet[parameterAnnotationsLists.size()];
+ for (int i = 0; i < parameterAnnotationsLists.size(); i++) {
+ sets[i] = createAnnotationSet(parameterAnnotationsLists.get(i));
}
- parameterAnnotationSets = new DexAnnotationSetRefList(sets, fakeParameterAnnotations);
+ annotationsList = new ParameterAnnotationsList(sets, fakeParameterAnnotations);
}
InternalOptions internalOptions = parent.application.options;
if (parameterNames != null && internalOptions.canUseParameterNameAnnotations()) {
@@ -596,7 +596,7 @@
parent.application.getFactory()));
}
DexEncodedMethod dexMethod = new DexEncodedMethod(method, flags,
- createAnnotationSet(annotations), parameterAnnotationSets, code);
+ createAnnotationSet(annotations), annotationsList, code);
if (flags.isStatic() || flags.isConstructor() || flags.isPrivate()) {
parent.directMethods.add(dexMethod);
} else {
diff --git a/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
new file mode 100644
index 0000000..23d5a38
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/ParameterAnnotationsList.java
@@ -0,0 +1,184 @@
+// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.dex.IndexedItemCollection;
+import com.android.tools.r8.dex.MixedSectionCollection;
+import java.util.Arrays;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+/**
+ * List of parameter annotations.
+ *
+ * <p>Due to a javac bug that went unfixed for multiple Java versions, the JVM specification does
+ * not require that the number of entries in the ParameterAnnotations attribute of a method matches
+ * the number of parameters in the method prototype; the number of ParameterAnnotations entries may
+ * be less than the number of prototype parameters for methods on inner classes.
+ *
+ * <p>There are two ways of accessing the parameter annotations:
+ *
+ * <ul>
+ * <li>Using {@link ParameterAnnotationsList#forEachAnnotation(Consumer)}
+ * <li>Using {@link ParameterAnnotationsList#size()}, {@link
+ * ParameterAnnotationsList#isMissing(int)} and {@link ParameterAnnotationsList#get(int)}
+ * </ul>
+ *
+ * <p>The {@link ParameterAnnotationsList#forEachAnnotation(Consumer)} method visits all the {@link
+ * DexAnnotation}s specified in the ParameterAnnotations attribute. In contrast, the {@link
+ * ParameterAnnotationsList#size()} and {@link ParameterAnnotationsList#get(int)} methods may be
+ * used to access the annotations on individual parameters; these methods automatically shift
+ * parameter annotations up to mitigate the javac bug. The {@link
+ * ParameterAnnotationsList#isMissing(int)} accessor is used to determine whether a given parameter
+ * is missing in the ParameterAnnotations attribute.
+ */
+public class ParameterAnnotationsList extends DexItem {
+
+ private static final ParameterAnnotationsList EMPTY_PARAMETER_ANNOTATIONS_LIST =
+ new ParameterAnnotationsList();
+
+ private final DexAnnotationSet[] values;
+ private final int missingParameterAnnotations;
+
+ public static ParameterAnnotationsList empty() {
+ return EMPTY_PARAMETER_ANNOTATIONS_LIST;
+ }
+
+ private ParameterAnnotationsList() {
+ this.values = new DexAnnotationSet[0];
+ this.missingParameterAnnotations = 0;
+ }
+
+ public ParameterAnnotationsList(DexAnnotationSet[] values) {
+ this(values, 0);
+ }
+
+ public ParameterAnnotationsList(DexAnnotationSet[] values, int missingParameterAnnotations) {
+ assert values != null && values.length > 0;
+ this.values = values;
+ this.missingParameterAnnotations = missingParameterAnnotations;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(values);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other instanceof ParameterAnnotationsList) {
+ return Arrays.equals(values, ((ParameterAnnotationsList) other).values);
+ }
+ return false;
+ }
+
+ @Override
+ public void collectIndexedItems(
+ IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
+ collectAll(indexedItems, values);
+ }
+
+ @Override
+ void collectMixedSectionItems(MixedSectionCollection mixedItems) {
+ // Collect values first so that the annotation sets have sorted themselves before adding this.
+ collectAll(mixedItems, values);
+ mixedItems.add(this);
+ }
+
+ public boolean isEmpty() {
+ return values.length == 0;
+ }
+
+ /** Iterate over the {@link DexAnnotation}s of all parameters. */
+ public void forEachAnnotation(Consumer<DexAnnotation> consumer) {
+ for (DexAnnotationSet parameterAnnotations : values) {
+ for (DexAnnotation annotation : parameterAnnotations.annotations) {
+ consumer.accept(annotation);
+ }
+ }
+ }
+
+ /**
+ * Return the number of parameters in the method prototype, or zero if the method's parameters
+ * have no annotations.
+ */
+ public int size() {
+ return missingParameterAnnotations + values.length;
+ }
+
+ /**
+ * Return the number of parameters specified in the ParameterAnnotations attribute, that is, the
+ * number of parameters for which {@link ParameterAnnotationsList#isMissing(int)} returns false.
+ */
+ public int countNonMissing() {
+ return values.length;
+ }
+
+ /**
+ * Return true if the ParameterAnnotations attribute is missing an entry for this parameter. This
+ * is sometimes the case for the first parameter in a method on an inner class.
+ *
+ * @param i Index of the parameter in the method prototype.
+ */
+ public boolean isMissing(int i) {
+ assert i >= 0;
+ return i < missingParameterAnnotations;
+ }
+
+ /**
+ * Return the annotations on the {@code i}th parameter (indexed according to the method
+ * prototype). If the parameter's annotation list is missing, or {@code i} is not less than the
+ * number of parameters (see {@link ParameterAnnotationsList#isMissing(int)}), {@link
+ * DexAnnotationSet#empty()} is returned.
+ *
+ * @param i Index of the parameter in the method prototype.
+ */
+ public DexAnnotationSet get(int i) {
+ assert i >= 0;
+ int adjustedIndex = i - missingParameterAnnotations;
+ return (0 <= adjustedIndex && adjustedIndex < values.length)
+ ? values[adjustedIndex]
+ : DexAnnotationSet.empty();
+ }
+
+ /** Return a ParameterAnnotationsList extended to the given number of parameters. */
+ public ParameterAnnotationsList withParameterCount(int parameterCount) {
+ assert parameterCount >= size();
+ if (this == EMPTY_PARAMETER_ANNOTATIONS_LIST || parameterCount == size()) {
+ return this;
+ }
+ return new ParameterAnnotationsList(values, parameterCount - values.length);
+ }
+
+ /**
+ * Return a new ParameterAnnotationsList that keeps only the annotations matched by {@code
+ * filter}.
+ */
+ public ParameterAnnotationsList keepIf(Predicate<DexAnnotation> filter) {
+ DexAnnotationSet[] filtered = null;
+ boolean allEmpty = true;
+ for (int i = 0; i < values.length; i++) {
+ DexAnnotationSet updated = values[i].keepIf(filter);
+ if (updated != values[i]) {
+ if (filtered == null) {
+ filtered = values.clone();
+ }
+ filtered[i] = updated;
+ }
+ if (!updated.isEmpty()) {
+ allEmpty = false;
+ }
+ }
+ if (filtered == null) {
+ return this;
+ }
+ if (allEmpty) {
+ return ParameterAnnotationsList.empty();
+ }
+ return new ParameterAnnotationsList(filtered, missingParameterAnnotations);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 7befc69..c75456d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -111,7 +111,7 @@
// Some debuggers (like IntelliJ) automatically skip synthetic methods on single step.
newFlags.setSynthetic();
return new DexEncodedMethod(newMethod, newFlags,
- defaultMethod.annotations, defaultMethod.parameterAnnotations,
+ defaultMethod.annotations, defaultMethod.parameterAnnotationsList,
new SynthesizedCode(new ForwardMethodSourceCode(
clazz.type, method.proto, /* static method */ null,
rewriter.defaultAsMethodOfCompanionClass(method),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 898a2b2..1e8d57a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -70,7 +70,7 @@
|| (companionMethod.getArity() == dexCode.getDebugInfo().parameters.length);
companionMethods.add(new DexEncodedMethod(companionMethod,
- newFlags, virtual.annotations, virtual.parameterAnnotations, code));
+ newFlags, virtual.annotations, virtual.parameterAnnotationsList, code));
// Make the method abstract.
virtual.accessFlags.setAbstract();
@@ -106,7 +106,7 @@
+ "either be public or private in " + iface.origin;
companionMethods.add(new DexEncodedMethod(
rewriter.staticAsMethodOfCompanionClass(direct.method), newFlags,
- direct.annotations, direct.parameterAnnotations, direct.getCode()));
+ direct.annotations, direct.parameterAnnotationsList, direct.getCode()));
} else {
if (originalFlags.isPrivate()) {
@@ -129,7 +129,7 @@
|| (companionMethod.getArity() == dexCode.getDebugInfo().parameters.length);
companionMethods.add(new DexEncodedMethod(companionMethod,
- newFlags, direct.annotations, direct.parameterAnnotations, code));
+ newFlags, direct.annotations, direct.parameterAnnotationsList, code));
} else {
// Since there are no interface constructors at this point,
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 82958da..d9bd4bd 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
@@ -10,7 +10,6 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedField;
@@ -27,6 +26,7 @@
import com.android.tools.r8.graph.DexValue.DexValueNull;
import com.android.tools.r8.graph.FieldAccessFlags;
import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.origin.SynthesizedOrigin;
@@ -171,7 +171,7 @@
MethodAccessFlags.fromSharedAccessFlags(
Constants.ACC_PUBLIC | Constants.ACC_FINAL, false),
DexAnnotationSet.empty(),
- DexAnnotationSetRefList.empty(),
+ ParameterAnnotationsList.empty(),
new SynthesizedCode(new LambdaMainMethodSourceCode(this, mainMethod)));
// Synthesize bridge methods.
@@ -187,7 +187,7 @@
| Constants.ACC_BRIDGE,
false),
DexAnnotationSet.empty(),
- DexAnnotationSetRefList.empty(),
+ ParameterAnnotationsList.empty(),
new SynthesizedCode(
new LambdaBridgeMethodSourceCode(this, mainMethod, bridgeMethod)));
}
@@ -208,7 +208,7 @@
| Constants.ACC_SYNTHETIC,
true),
DexAnnotationSet.empty(),
- DexAnnotationSetRefList.empty(),
+ ParameterAnnotationsList.empty(),
new SynthesizedCode(new LambdaConstructorSourceCode(this)));
// Class constructor for stateless lambda classes.
@@ -219,7 +219,7 @@
MethodAccessFlags.fromSharedAccessFlags(
Constants.ACC_SYNTHETIC | Constants.ACC_STATIC, true),
DexAnnotationSet.empty(),
- DexAnnotationSetRefList.empty(),
+ ParameterAnnotationsList.empty(),
new SynthesizedCode(new LambdaClassConstructorSourceCode(this)));
}
return methods;
@@ -492,7 +492,7 @@
// relax its accessibility without making it virtual.
DexEncodedMethod newMethod = new DexEncodedMethod(
callTarget, encodedMethod.accessFlags, encodedMethod.annotations,
- encodedMethod.parameterAnnotations, encodedMethod.getCode());
+ encodedMethod.parameterAnnotationsList, encodedMethod.getCode());
// TODO(ager): Should we give the new first parameter an actual name? Maybe 'this'?
encodedMethod.accessFlags.setStatic();
encodedMethod.accessFlags.unsetPrivate();
@@ -532,7 +532,7 @@
Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC,
false);
DexEncodedMethod accessorEncodedMethod = new DexEncodedMethod(
- callTarget, accessorFlags, DexAnnotationSet.empty(), DexAnnotationSetRefList.empty(),
+ callTarget, accessorFlags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(),
new SynthesizedCode(new AccessorMethodSourceCode(LambdaClass.this)));
accessorClass.setDirectMethods(appendMethod(
accessorClass.directMethods(), accessorEncodedMethod));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 5a257df..7af555c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -22,6 +21,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.UseRegistry;
import com.android.tools.r8.ir.code.Add;
import com.android.tools.r8.ir.code.BasicBlock;
@@ -1057,7 +1057,7 @@
DexString methodName = dexItemFactory.createString(OutlineOptions.METHOD_PREFIX + count);
DexMethod method = outline.buildMethod(type, methodName);
direct[count] = new DexEncodedMethod(method, methodAccess, DexAnnotationSet.empty(),
- DexAnnotationSetRefList.empty(), new OutlineCode(outline));
+ ParameterAnnotationsList.empty(), new OutlineCode(outline));
generatedOutlines.put(outline, method);
count++;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 03daf44..466117f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -327,12 +327,12 @@
for (DexEncodedMethod method : clazz.directMethods()) {
lambdaInvalidator.accept(method.annotations);
- lambdaInvalidator.accept(method.parameterAnnotations);
+ lambdaInvalidator.accept(method.parameterAnnotationsList);
lambdaInvalidator.accept(method.method, clazz.type);
}
for (DexEncodedMethod method : clazz.virtualMethods()) {
lambdaInvalidator.accept(method.annotations);
- lambdaInvalidator.accept(method.parameterAnnotations);
+ lambdaInvalidator.accept(method.parameterAnnotationsList);
lambdaInvalidator.accept(method.method, clazz.type);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java
index 4f56306..05c71e6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java
@@ -7,7 +7,6 @@
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationElement;
import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexEncodedAnnotation;
import com.android.tools.r8.graph.DexField;
@@ -24,6 +23,7 @@
import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
import com.android.tools.r8.graph.DexValue.DexValueMethodType;
import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -116,10 +116,8 @@
}
}
- void accept(DexAnnotationSetRefList annotationSetRefList) {
- for (DexAnnotationSet annotationSet : annotationSetRefList.values) {
- accept(annotationSet);
- }
+ void accept(ParameterAnnotationsList parameterAnnotationsList) {
+ parameterAnnotationsList.forEachAnnotation(this::accept);
}
private void accept(DexAnnotation annotation) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
index d441a00..1fe0317 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
@@ -7,7 +7,6 @@
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -19,6 +18,7 @@
import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.ir.optimize.lambda.LambdaGroupClassBuilder;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
@@ -113,7 +113,7 @@
factory.createMethod(group.getGroupClassType(), methodProto, methodName),
accessFlags,
isMainMethod ? id.mainMethodAnnotations : DexAnnotationSet.empty(),
- isMainMethod ? id.mainMethodParamAnnotations : DexAnnotationSetRefList.empty(),
+ isMainMethod ? id.mainMethodParamAnnotations : ParameterAnnotationsList.empty(),
new SynthesizedCode(
new KotlinLambdaVirtualMethodSourceCode(factory, group.getGroupClassType(),
methodProto, group.getLambdaIdField(factory), implMethods))));
@@ -159,7 +159,7 @@
factory.createMethod(groupClassType, initializerProto, factory.constructorMethodName),
CONSTRUCTOR_FLAGS_RELAXED, // always create access-relaxed constructor.
DexAnnotationSet.empty(),
- DexAnnotationSetRefList.empty(),
+ ParameterAnnotationsList.empty(),
new SynthesizedCode(createInstanceInitializerSourceCode(groupClassType, initializerProto)));
// Static class initializer for stateless lambdas.
@@ -170,7 +170,7 @@
factory.classConstructorMethodName),
CLASS_INITIALIZER_FLAGS,
DexAnnotationSet.empty(),
- DexAnnotationSetRefList.empty(),
+ ParameterAnnotationsList.empty(),
new SynthesizedCode(new ClassInitializerSourceCode(factory, group)));
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
index 0540bef..996099b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
@@ -5,13 +5,13 @@
package com.android.tools.r8.ir.optimize.lambda.kotlin;
import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexString;
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.ParameterAnnotationsList;
import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
@@ -41,7 +41,7 @@
final DexString mainMethodName;
final DexProto mainMethodProto;
final DexAnnotationSet mainMethodAnnotations;
- final DexAnnotationSetRefList mainMethodParamAnnotations;
+ final ParameterAnnotationsList mainMethodParamAnnotations;
final EnclosingMethodAttribute enclosing;
@@ -61,7 +61,7 @@
this.mainMethodName = mainMethod.method.name;
this.mainMethodProto = mainMethod.method.proto;
this.mainMethodAnnotations = mainMethod.annotations;
- this.mainMethodParamAnnotations = mainMethod.parameterAnnotations;
+ this.mainMethodParamAnnotations = mainMethod.parameterAnnotationsList;
this.innerClassAccess = inner != null ? inner.getAccess() : MISSING_INNER_CLASS_ATTRIBUTE;
this.enclosing = enclosing;
this.hash = computeHashCode();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
index f5698aa..42e0bb1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
@@ -191,7 +191,7 @@
throw new LambdaStructureError("unexpected method annotations [" +
method.annotations.toSmaliString() + "] on " + method.method.toSourceString());
}
- if (!method.parameterAnnotations.isEmpty()) {
+ if (!method.parameterAnnotationsList.isEmpty()) {
throw new LambdaStructureError("unexpected method parameters annotations [" +
method.annotations.toSmaliString() + "] on " + method.method.toSourceString());
}
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index e49ad85..2970fb4 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -13,7 +13,6 @@
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexAnnotationElement;
import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexEncodedAnnotation;
import com.android.tools.r8.graph.DexEncodedField;
@@ -34,6 +33,7 @@
import com.android.tools.r8.graph.DexValue.UnknownDexValue;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.JarClassFileReader;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.ProguardMapSupplier;
import com.android.tools.r8.utils.ExceptionUtils;
@@ -252,7 +252,7 @@
}
}
writeAnnotations(visitor::visitAnnotation, method.annotations.annotations);
- writeParameterAnnotations(visitor, method.parameterAnnotations);
+ writeParameterAnnotations(visitor, method.parameterAnnotationsList);
if (!method.accessFlags.isAbstract() && !method.accessFlags.isNative()) {
writeCode(method.getCode(), visitor);
}
@@ -260,21 +260,21 @@
}
private void writeParameterAnnotations(
- MethodVisitor visitor, DexAnnotationSetRefList parameterAnnotations) {
- int missingParameterAnnotations = parameterAnnotations.getMissingParameterAnnotations();
- for (int i = 0; i < missingParameterAnnotations; i++) {
- AnnotationVisitor av =
- visitor.visitParameterAnnotation(i, JarClassFileReader.SYNTHETIC_ANNOTATION, false);
- if (av != null) {
- av.visitEnd();
+ MethodVisitor visitor, ParameterAnnotationsList parameterAnnotations) {
+ for (int i = 0; i < parameterAnnotations.size(); i++) {
+ if (parameterAnnotations.isMissing(i)) {
+ AnnotationVisitor av =
+ visitor.visitParameterAnnotation(i, JarClassFileReader.SYNTHETIC_ANNOTATION, false);
+ if (av != null) {
+ av.visitEnd();
+ }
+ } else {
+ int iFinal = i;
+ writeAnnotations(
+ (d, vis) -> visitor.visitParameterAnnotation(iFinal, d, vis),
+ parameterAnnotations.get(i).annotations);
}
}
- for (int i = 0; i < parameterAnnotations.values.length; i++) {
- int parameterIndex = i + missingParameterAnnotations;
- writeAnnotations(
- (d, vis) -> visitor.visitParameterAnnotation(parameterIndex, d, vis),
- parameterAnnotations.values[i].annotations);
- }
}
private interface AnnotationConsumer {
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 871812a..28ca9fc 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -5,8 +5,6 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexAnnotation;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -15,9 +13,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.function.Predicate;
public class AnnotationRemover {
@@ -127,20 +122,20 @@
keep.ensureValid(options.forceProguardCompatibility, compatibility);
for (DexProgramClass clazz : appInfo.classes()) {
stripAttributes(clazz);
- clazz.annotations = stripAnnotations(clazz.annotations, this::filterAnnotations);
+ clazz.annotations = clazz.annotations.keepIf(this::filterAnnotations);
clazz.forEachMethod(this::processMethod);
clazz.forEachField(this::processField);
}
}
private void processMethod(DexEncodedMethod method) {
- method.annotations = stripAnnotations(method.annotations, this::filterAnnotations);
- method.parameterAnnotations = stripAnnotations(method.parameterAnnotations,
- this::filterParameterAnnotations);
+ method.annotations = method.annotations.keepIf(this::filterAnnotations);
+ method.parameterAnnotationsList =
+ method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations);
}
private void processField(DexEncodedField field) {
- field.annotations = stripAnnotations(field.annotations, this::filterAnnotations);
+ field.annotations = field.annotations.keepIf(this::filterAnnotations);
}
private void stripAttributes(DexProgramClass clazz) {
@@ -152,52 +147,4 @@
}
}
- private DexAnnotationSetRefList stripAnnotations(DexAnnotationSetRefList annotations,
- Predicate<DexAnnotation> filter) {
- DexAnnotationSet[] filtered = null;
- for (int i = 0; i < annotations.values.length; i++) {
- DexAnnotationSet updated = stripAnnotations(annotations.values[i], filter);
- if (updated != annotations.values[i]) {
- if (filtered == null) {
- filtered = annotations.values.clone();
- filtered[i] = updated;
- }
- }
- }
- if (filtered == null) {
- return annotations;
- } else {
- if (Arrays.stream(filtered).allMatch(DexAnnotationSet::isEmpty)) {
- return DexAnnotationSetRefList.empty();
- }
- return new DexAnnotationSetRefList(filtered);
- }
- }
-
- private DexAnnotationSet stripAnnotations(DexAnnotationSet annotations,
- Predicate<DexAnnotation> filter) {
- ArrayList<DexAnnotation> filtered = null;
- for (int i = 0; i < annotations.annotations.length; i++) {
- DexAnnotation annotation = annotations.annotations[i];
- if (filter.test(annotation)) {
- if (filtered != null) {
- filtered.add(annotation);
- }
- } else {
- if (filtered == null) {
- filtered = new ArrayList<>(annotations.annotations.length);
- for (int j = 0; j < i; j++) {
- filtered.add(annotations.annotations[j]);
- }
- }
- }
- }
- if (filtered == null) {
- return annotations;
- } else if (filtered.isEmpty()) {
- return DexAnnotationSet.empty();
- } else {
- return new DexAnnotationSet(filtered.toArray(new DexAnnotation[filtered.size()]));
- }
- }
}
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 25df235..c359fc7 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.graph.AppInfoWithSubtyping;
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;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
@@ -552,14 +551,18 @@
private void processAnnotations(DexAnnotation[] annotations) {
for (DexAnnotation annotation : annotations) {
- DexType type = annotation.annotation.type;
- if (liveTypes.contains(type)) {
- // The type of this annotation is already live, so pick up its dependencies.
- handleAnnotationOfLiveType(annotation);
- } else {
- // Remember this annotation for later.
- deferredAnnotations.computeIfAbsent(type, ignore -> new HashSet<>()).add(annotation);
- }
+ processAnnotation(annotation);
+ }
+ }
+
+ private void processAnnotation(DexAnnotation annotation) {
+ DexType type = annotation.annotation.type;
+ if (liveTypes.contains(type)) {
+ // The type of this annotation is already live, so pick up its dependencies.
+ handleAnnotationOfLiveType(annotation);
+ } else {
+ // Remember this annotation for later.
+ deferredAnnotations.computeIfAbsent(type, ignore -> new HashSet<>()).add(annotation);
}
}
@@ -1239,9 +1242,7 @@
}
}
processAnnotations(method.annotations.annotations);
- for (DexAnnotationSet parameterAnnotation : method.parameterAnnotations.values) {
- processAnnotations(parameterAnnotation.annotations);
- }
+ method.parameterAnnotationsList.forEachAnnotation(this::processAnnotation);
if (protoLiteExtension != null && protoLiteExtension.appliesTo(method)) {
protoLiteExtension.processMethod(method, new UseRegistry(method), protoLiteFields);
} else {
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 5f5237b..91cab92 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -137,8 +137,7 @@
parse(ImmutableList.of(new ProguardConfigurationSourceFile(path)));
}
- // package visible for testing
- void parse(ProguardConfigurationSource source) {
+ public void parse(ProguardConfigurationSource source) {
parse(ImmutableList.of(source));
}
@@ -968,6 +967,11 @@
private Path parseFileName() throws ProguardRuleParserException {
TextPosition start = getPosition();
skipWhitespace();
+
+ if (baseDirectory == null) {
+ throw parseError("Options with file names are not supported", start);
+ }
+
String fileName = acceptString(character ->
character != File.pathSeparatorChar
&& !Character.isWhitespace(character)
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceBytes.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceBytes.java
new file mode 100644
index 0000000..3f379a4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceBytes.java
@@ -0,0 +1,47 @@
+// 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.origin.Origin;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+
+public class ProguardConfigurationSourceBytes implements ProguardConfigurationSource {
+ private final byte[] bytes;
+ private final Origin origin;
+
+ public ProguardConfigurationSourceBytes(byte[] bytes, Origin origin) {
+ this.bytes = bytes;
+ this.origin = origin;
+ }
+
+ public ProguardConfigurationSourceBytes(InputStream in, Origin origin) throws IOException {
+ this(ByteStreams.toByteArray(in), origin);
+ }
+
+ @Override
+ public String get() throws IOException {
+ return new String(bytes, StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public Path getBaseDirectory() {
+ // Options with relative names (including -include) is not supported.
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return origin.toString();
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index d8dffea..1b026c0 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -589,5 +589,9 @@
throw new CompilationError("Unsupported source file type", new PathOrigin(file));
}
}
+
+ public List<ProgramResourceProvider> getProgramResourceProviders() {
+ return programResourceProviders;
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java b/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
index 9fd91d9..99149ff 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionDiagnostic.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils;
+import com.android.tools.r8.ResourceException;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
import java.io.FileNotFoundException;
@@ -19,6 +20,10 @@
this.origin = origin;
}
+ public ExceptionDiagnostic(ResourceException e) {
+ this(e, e.getOrigin());
+ }
+
@Override
public Origin getOrigin() {
return origin;
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 3086662..4c40d61 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -4,10 +4,10 @@
package com.android.tools.r8.utils;
import com.android.tools.r8.ClassFileConsumer;
-import com.android.tools.r8.DexFilePerClassFileConsumer;
-import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.DataResourceConsumer;
import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.DexFilePerClassFileConsumer;
+import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.ProgramConsumer;
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.dex.Marker;
@@ -183,6 +183,9 @@
public OffOrAuto interfaceMethodDesugaring = OffOrAuto.Auto;
// Defines try-with-resources rewriter behavior.
public OffOrAuto tryWithResourcesDesugaring = OffOrAuto.Auto;
+ // Flag to turn on/off processing of @dalvik.annotation.codegen.CovariantReturnType and
+ // @dalvik.annotation.codegen.CovariantReturnType$CovariantReturnTypes.
+ public boolean processCovariantReturnTypeAnnotations = true;
// Whether or not to check for valid multi-dex builds.
//
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index 63b95b3..ebb7ce6 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -8,6 +8,8 @@
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
import java.util.ArrayList;
import java.util.Collection;
@@ -80,7 +82,15 @@
if (errorCount != 0) {
AbortException abort;
if (lastError != null && lastError.getDiagnosticMessage() != null) {
- abort = new AbortException("Error: " + lastError.getDiagnosticMessage());
+ StringBuilder builder = new StringBuilder("Error: ");
+ if (lastError.getOrigin() != Origin.unknown()) {
+ builder.append(lastError.getOrigin()).append(", ");
+ }
+ if (lastError.getPosition() != Position.UNKNOWN) {
+ builder.append(lastError.getPosition()).append(", ");
+ }
+ builder.append(lastError.getDiagnosticMessage());
+ abort = new AbortException(builder.toString());
} else {
abort = new AbortException();
}
diff --git a/src/test/debugTestResourcesKotlin/KotlinInline.kt b/src/test/debugTestResourcesKotlin/KotlinInline.kt
index 7f914e4..4b55c57 100644
--- a/src/test/debugTestResourcesKotlin/KotlinInline.kt
+++ b/src/test/debugTestResourcesKotlin/KotlinInline.kt
@@ -47,6 +47,30 @@
emptyMethod(-1)
}
+ // Double inlining
+ fun testNestedInlining() {
+ val l1 = Int.MAX_VALUE
+ val l2 = Int.MIN_VALUE
+ inlinee1(l1, l2)
+ }
+ inline fun inlinee1(a: Int, b: Int) {
+ val c = a - 2
+ inlinee2(1) {
+ val left = a + b
+ val right = a - b
+ foo(left, right)
+ }
+ inlinee2(c) {
+ foo(b, a)
+ }
+ }
+
+ inline fun inlinee2(p: Int, block: () -> Unit) {
+ if (p > 0) {
+ block()
+ }
+ }
+
companion object {
@JvmStatic fun main(args: Array<String>) {
println("Hello world!")
@@ -54,6 +78,7 @@
instance.processObject(instance, instance::printObject)
instance.invokeInlinedFunctions()
instance.singleInline()
+ instance.testNestedInlining()
}
}
}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
index fc22895..2265c3e 100644
--- a/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
+++ b/src/test/java/com/android/tools/r8/DiagnosticsChecker.java
@@ -3,14 +3,23 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.graph.invokesuper.Consumer;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+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.utils.ListUtils;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
// Helper to check that a particular error occurred.
-class DiagnosticsChecker implements DiagnosticsHandler {
+public class DiagnosticsChecker implements DiagnosticsHandler {
public List<Diagnostic> errors = new ArrayList<>();
public List<Diagnostic> warnings = new ArrayList<>();
public List<Diagnostic> infos = new ArrayList<>();
@@ -51,4 +60,64 @@
throw e;
}
}
+
+ public static Diagnostic checkDiagnostic(Diagnostic diagnostic, Consumer<Origin> originChecker,
+ int lineStart, int columnStart, String... messageParts) {
+ if (originChecker != null) {
+ originChecker.accept(diagnostic.getOrigin());
+ }
+ TextPosition position;
+ if (diagnostic.getPosition() instanceof TextRange) {
+ position = ((TextRange) diagnostic.getPosition()).getStart();
+ } else {
+ position = ((TextPosition) diagnostic.getPosition());
+ }
+ assertEquals(lineStart, position.getLine());
+ assertEquals(columnStart, position.getColumn());
+ for (String part : messageParts) {
+ assertTrue(diagnostic.getDiagnosticMessage() + " doesn't contain \"" + part + "\"",
+ diagnostic.getDiagnosticMessage().contains(part));
+ }
+ return diagnostic;
+ }
+
+ public static Diagnostic checkDiagnostic(Diagnostic diagnostic, Consumer<Origin> originChecker,
+ String... messageParts) {
+ if (originChecker != null) {
+ originChecker.accept(diagnostic.getOrigin());
+ }
+ assertEquals(diagnostic.getPosition(), Position.UNKNOWN);
+ for (String part : messageParts) {
+ assertTrue(diagnostic.getDiagnosticMessage() + " doesn't contain \"" + part + "\"",
+ diagnostic.getDiagnosticMessage().contains(part));
+ }
+ return diagnostic;
+ }
+
+ static class PathOriginChecker implements Consumer<Origin> {
+ private final Path path;
+ PathOriginChecker(Path path) {
+ this.path = path;
+ }
+
+ public void accept(Origin origin) {
+ if (path != null) {
+ assertEquals(path, ((PathOrigin) origin).getPath());
+ } else {
+ assertSame(Origin.unknown(), origin);
+ }
+ }
+ }
+
+ public static Diagnostic checkDiagnostic(Diagnostic diagnostic, Path path,
+ int lineStart, int columnStart, String... messageParts) {
+ return checkDiagnostic(diagnostic, new PathOriginChecker(path), lineStart, columnStart,
+ messageParts);
+ }
+
+ public static Diagnostic checkDiagnostics(List<Diagnostic> diagnostics, Path path,
+ int lineStart, int columnStart, String... messageParts) {
+ assertEquals(1, diagnostics.size());
+ return checkDiagnostic(diagnostics.get(0), path, lineStart, columnStart, messageParts);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 2bb8d75..96591d8 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -153,19 +153,26 @@
protected Path jarTestClasses(Class... classes) throws IOException {
Path jar = File.createTempFile("junit", ".jar", temp.getRoot()).toPath();
try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar.toFile()))) {
- for (Class clazz : classes) {
- try (FileInputStream in =
- new FileInputStream(ToolHelper.getClassFileForTestClass(clazz).toFile())) {
- out.putNextEntry(new ZipEntry(ToolHelper.getJarEntryForTestClass(clazz)));
- ByteStreams.copy(in, out);
- out.closeEntry();
- }
- }
+ addTestClassesToJar(out, classes);
}
return jar;
}
/**
+ * Create a temporary JAR file containing the specified test classes.
+ */
+ protected void addTestClassesToJar(JarOutputStream out, Class... classes) throws IOException {
+ for (Class clazz : classes) {
+ try (FileInputStream in =
+ new FileInputStream(ToolHelper.getClassFileForTestClass(clazz).toFile())) {
+ out.putNextEntry(new ZipEntry(ToolHelper.getJarEntryForTestClass(clazz)));
+ ByteStreams.copy(in, out);
+ out.closeEntry();
+ }
+ }
+ }
+
+ /**
* Create a temporary JAR file containing all test classes in a package.
*/
protected Path jarTestClassesInPackage(Package pkg) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java b/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
index 45e3c96..eca0441 100644
--- a/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ContinuousSteppingTest.java
@@ -51,8 +51,9 @@
.add(KotlinD8Config.DEBUGGEE_KOTLIN_JAR, ContinuousSteppingTest::allVersions)
.addAll(findAllJarsIn(Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR)),
ContinuousSteppingTest::fromAndroidN)
- .addAll(findAllJarsIn(Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR)),
- ContinuousSteppingTest::fromAndroidO)
+ // TODO(b/79911828) Investigate timeout issues for Android O examples.
+ // .addAll(findAllJarsIn(Paths.get(ToolHelper.EXAMPLES_ANDROID_O_BUILD_DIR)),
+ // ContinuousSteppingTest::fromAndroidO)
.build();
private static final Map<Path, DebugTestConfig> compiledJarConfig = new HashMap<>();
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index e8d21f2..25356ae 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -390,6 +390,14 @@
return inspect(t -> t.checkLocal(localName));
}
+ protected final JUnit3Wrapper.Command checkLocals(String... localNames) {
+ return inspect(t -> {
+ for (String str : localNames) {
+ t.checkLocal(str);
+ }
+ });
+ }
+
protected final JUnit3Wrapper.Command checkLocal(String localName, Value expectedValue) {
return inspect(t -> t.checkLocal(localName, expectedValue));
}
@@ -398,6 +406,14 @@
return inspect(t -> t.checkNoLocal(localName));
}
+ protected final JUnit3Wrapper.Command checkNoLocals(String... localNames) {
+ return inspect(t -> {
+ for (String str : localNames) {
+ t.checkNoLocal(str);
+ }
+ });
+ }
+
protected final JUnit3Wrapper.Command checkNoLocal() {
return inspect(t -> {
List<String> localNames = t.getLocalNames();
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinDebugTestBase.java b/src/test/java/com/android/tools/r8/debug/KotlinDebugTestBase.java
index 5ddd98c..63bc007 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinDebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinDebugTestBase.java
@@ -4,9 +4,6 @@
package com.android.tools.r8.debug;
-import com.android.tools.r8.ToolHelper;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import org.apache.harmony.jpda.tests.framework.jdwp.Frame.Variable;
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
index cdba21b..3837bb1 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
@@ -6,40 +6,46 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import com.android.tools.r8.ToolHelper;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import org.apache.harmony.jpda.tests.framework.jdwp.Value;
import org.junit.Test;
-// TODO check double-depth inline (an inline in another inline)
public class KotlinInlineTest extends KotlinDebugTestBase {
+ public static final String DEBUGGEE_CLASS = "KotlinInline";
+ public static final String SOURCE_FILE = "KotlinInline.kt";
+
@Test
public void testStepOverInline() throws Throwable {
String methodName = "singleInline";
runDebugTest(
getD8Config(),
- "KotlinInline",
- breakpoint("KotlinInline", methodName),
+ DEBUGGEE_CLASS,
+ breakpoint(DEBUGGEE_CLASS, methodName),
run(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
- assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(SOURCE_FILE, s.getSourceFile());
assertEquals(41, s.getLineNumber());
s.checkLocal("this");
}),
stepOver(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
- assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(SOURCE_FILE, s.getSourceFile());
assertEquals(42, s.getLineNumber());
s.checkLocal("this");
}),
kotlinStepOver(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
- assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(SOURCE_FILE, s.getSourceFile());
assertEquals(43, s.getLineNumber());
s.checkLocal("this");
}),
@@ -51,29 +57,29 @@
String methodName = "singleInline";
runDebugTest(
getD8Config(),
- "KotlinInline",
- breakpoint("KotlinInline", methodName),
+ DEBUGGEE_CLASS,
+ breakpoint(DEBUGGEE_CLASS, methodName),
run(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
- assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(SOURCE_FILE, s.getSourceFile());
assertEquals(41, s.getLineNumber());
s.checkLocal("this");
}),
stepOver(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
- assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(SOURCE_FILE, s.getSourceFile());
assertEquals(42, s.getLineNumber());
s.checkLocal("this");
}),
stepInto(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
- assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(SOURCE_FILE, s.getSourceFile());
// The actual line number (the one encoded in debug information) is different than the
// source file one.
// TODO(shertz) extract original line number from JSR-45's SMAP (only supported on
@@ -89,47 +95,65 @@
String methodName = "singleInline";
runDebugTest(
getD8Config(),
- "KotlinInline",
- breakpoint("KotlinInline", methodName),
+ DEBUGGEE_CLASS,
+ breakpoint(DEBUGGEE_CLASS, methodName),
run(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
- assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(SOURCE_FILE, s.getSourceFile());
assertEquals(41, s.getLineNumber());
s.checkLocal("this");
}),
stepOver(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
- assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(SOURCE_FILE, s.getSourceFile());
assertEquals(42, s.getLineNumber());
s.checkLocal("this");
}),
stepInto(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
}),
kotlinStepOut(),
inspect(s -> {
- assertEquals("KotlinInline", s.getClassName());
+ assertEquals(DEBUGGEE_CLASS, s.getClassName());
assertEquals(methodName, s.getMethodName());
- assertEquals("KotlinInline.kt", s.getSourceFile());
+ assertEquals(SOURCE_FILE, s.getSourceFile());
assertEquals(43, s.getLineNumber());
s.checkLocal("this");
}),
run());
}
+ private static String mangleFunctionNameFromInlineScope(String functionName) {
+ return "$i$f$" + functionName;
+ }
+
+ private static String mangleLambdaNameFromInlineScope(String functionName, int lambdaId) {
+ assert lambdaId > 0;
+ return "$i$a$" + lambdaId + "$" + functionName;
+ }
+
+ private static String mangleLvNameFromInlineScope(String lvName, int inlineDepth) {
+ assert inlineDepth > 0;
+ StringBuilder builder = new StringBuilder(lvName);
+ for (int i = 0; i < inlineDepth; ++i) {
+ builder.append("$iv");
+ }
+ return builder.toString();
+ }
+
@Test
public void testKotlinInline() throws Throwable {
final String inliningMethodName = "invokeInlinedFunctions";
runDebugTest(
getD8Config(),
- "KotlinInline",
- breakpoint("KotlinInline", inliningMethodName),
+ DEBUGGEE_CLASS,
+ breakpoint(DEBUGGEE_CLASS, inliningMethodName),
run(),
inspect(s -> {
assertEquals(inliningMethodName, s.getMethodName());
@@ -157,8 +181,8 @@
s.checkLocal("this");
s.checkLocal("inA", Value.createInt(1));
// This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
- s.checkLocal("$i$f$inlinedA");
- s.checkLocal("$i$a$1$inlinedA");
+ s.checkLocal(mangleFunctionNameFromInlineScope("inlinedA"));
+ s.checkLocal(mangleLambdaNameFromInlineScope("inlinedA", 1));
}),
stepInto(),
inspect(s -> {
@@ -181,10 +205,144 @@
s.checkLocal("this");
s.checkLocal("inB", Value.createInt(2));
// This is a "hidden" lv added by Kotlin (which is neither initialized nor used).
- s.checkLocal("$i$f$inlinedB");
- s.checkLocal("$i$a$1$inlinedB");
+ s.checkLocal(mangleFunctionNameFromInlineScope("inlinedB"));
+ s.checkLocal(mangleLambdaNameFromInlineScope("inlinedB", 1));
}),
run());
}
+ @Test
+ public void testNestedInlining() throws Throwable {
+ // Count the number of lines in the source file. This is needed to check that inlined code
+ // refers to non-existing line numbers.
+ Path sourceFilePath = Paths.get(ToolHelper.TESTS_DIR, "debugTestResourcesKotlin", SOURCE_FILE);
+ assert sourceFilePath.toFile().exists();
+ final int maxLineNumber = Files.readAllLines(sourceFilePath).size();
+ final String inliningMethodName = "testNestedInlining";
+
+ // Local variables that represent the scope (start,end) of function's code that has been
+ // inlined.
+ final String inlinee1_inlineScope = mangleFunctionNameFromInlineScope("inlinee1");
+ final String inlinee2_inlineScope = mangleFunctionNameFromInlineScope("inlinee2");
+
+ // Local variables that represent the scope (start,end) of lambda's code that has been inlined.
+ final String inlinee2_lambda1_inlineScope = mangleLambdaNameFromInlineScope("inlinee2", 1);
+ final String inlinee2_lambda2_inlineScope = mangleLambdaNameFromInlineScope("inlinee2", 2);
+ final String c_mangledLvName = mangleLvNameFromInlineScope("c", 1);
+ final String left_mangledLvName = mangleLvNameFromInlineScope("left", 1);
+ final String right_mangledLvName = mangleLvNameFromInlineScope("right", 1);
+ final String p_mangledLvName = mangleLvNameFromInlineScope("p", 2);
+
+ runDebugTest(
+ getD8Config(),
+ DEBUGGEE_CLASS,
+ breakpoint(DEBUGGEE_CLASS, inliningMethodName),
+ run(),
+ inspect(s -> {
+ assertEquals(inliningMethodName, s.getMethodName());
+ assertEquals(52, s.getLineNumber());
+ s.checkLocal("this");
+ }),
+ checkLocal("this"),
+ checkNoLocals("l1", "l2"),
+ stepOver(),
+ checkLine(SOURCE_FILE, 53),
+ checkLocals("this", "l1"),
+ checkNoLocal("l2"),
+ stepOver(),
+ checkLine(SOURCE_FILE, 54),
+ checkLocals("this", "l1", "l2"),
+ stepInto(),
+ // We jumped into 1st inlinee but the current method is the same
+ checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+ checkLocal(inlinee1_inlineScope),
+ inspect(state -> {
+ assertEquals(SOURCE_FILE, state.getSourceFile());
+ assertTrue(state.getLineNumber() > maxLineNumber);
+ }),
+ checkNoLocal(c_mangledLvName),
+ stepInto(),
+ checkLocal(c_mangledLvName),
+ stepInto(),
+ // We jumped into 2nd inlinee which is nested in the 1st inlinee
+ checkLocal(inlinee2_inlineScope),
+ checkLocal(inlinee1_inlineScope),
+ inspect(state -> {
+ assertEquals(SOURCE_FILE, state.getSourceFile());
+ assertTrue(state.getLineNumber() > maxLineNumber);
+ }),
+ // We must see the local variable "p" with a 2-level inline depth.
+ checkLocal(p_mangledLvName),
+ checkNoLocals(left_mangledLvName, right_mangledLvName),
+ // Enter the if block of inlinee2
+ stepInto(),
+ checkLocal(p_mangledLvName),
+ checkNoLocals(left_mangledLvName, right_mangledLvName),
+ // Enter the inlined lambda
+ stepInto(),
+ checkLocal(p_mangledLvName),
+ checkLocal(inlinee2_lambda1_inlineScope),
+ checkNoLocals(left_mangledLvName, right_mangledLvName),
+ stepInto(),
+ checkLocal(inlinee2_lambda1_inlineScope),
+ checkLocal(left_mangledLvName),
+ checkNoLocal(right_mangledLvName),
+ stepInto(),
+ checkLocals(left_mangledLvName, right_mangledLvName),
+ // Enter "foo"
+ stepInto(),
+ checkMethod(DEBUGGEE_CLASS, "foo"),
+ checkLine(SOURCE_FILE, 34),
+ stepOut(),
+ // We're back to the inline section, at the end of the lambda
+ inspect(state -> {
+ assertEquals(SOURCE_FILE, state.getSourceFile());
+ assertTrue(state.getLineNumber() > maxLineNumber);
+ }),
+ checkLocal(inlinee1_inlineScope),
+ checkLocal(inlinee2_inlineScope),
+ checkLocal(inlinee2_lambda1_inlineScope),
+ checkNoLocal(inlinee2_lambda2_inlineScope),
+ stepInto(),
+ // We're in inlinee2, after the call to the inlined lambda.
+ checkLocal(inlinee1_inlineScope),
+ checkLocal(inlinee2_inlineScope),
+ checkNoLocal(inlinee2_lambda1_inlineScope),
+ checkNoLocal(inlinee2_lambda2_inlineScope),
+ checkLocal(p_mangledLvName),
+ stepInto(),
+ // We're out of inlinee2
+ checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+ checkLocal(inlinee1_inlineScope),
+ checkNoLocal(inlinee2_inlineScope),
+ checkNoLocal(inlinee2_lambda1_inlineScope),
+ // Enter the new call to "inlinee2"
+ stepInto(),
+ checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+ checkLocal(inlinee1_inlineScope),
+ checkLocal(inlinee2_inlineScope),
+ checkNoLocal(inlinee2_lambda1_inlineScope),
+ checkNoLocal(inlinee2_lambda2_inlineScope),
+ checkNoLocal(p_mangledLvName),
+ stepInto(),
+ checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+ checkLocal(inlinee1_inlineScope),
+ checkLocal(inlinee2_inlineScope),
+ checkNoLocal(inlinee2_lambda1_inlineScope),
+ checkNoLocal(inlinee2_lambda2_inlineScope),
+ checkNoLocal(p_mangledLvName),
+ // We enter the 2nd lambda
+ stepInto(),
+ checkMethod(DEBUGGEE_CLASS, inliningMethodName),
+ checkLocal(inlinee1_inlineScope),
+ checkLocal(inlinee2_inlineScope),
+ checkNoLocal(inlinee2_lambda1_inlineScope),
+ checkLocal(inlinee2_lambda2_inlineScope),
+ // Enter the call to "foo"
+ stepInto(),
+ checkMethod(DEBUGGEE_CLASS, "foo"),
+ checkLine(SOURCE_FILE, 34),
+ run());
+ }
+
}
diff --git a/src/test/java/com/android/tools/r8/debug/UnusedCheckCastTargetOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/UnusedCheckCastTargetOptimizationTest.java
new file mode 100644
index 0000000..8fd6cd8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/UnusedCheckCastTargetOptimizationTest.java
@@ -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 com.android.tools.r8.debug;
+
+public class UnusedCheckCastTargetOptimizationTest {
+
+ class Super {}
+
+ class Subclass extends Super {}
+
+ public static void main(String[] args) {
+ Super[] b = new Subclass[10];
+ Subclass[] c = (Subclass[]) b;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/UnusedCheckCastTargetOptimizationTestRunner.java b/src/test/java/com/android/tools/r8/debug/UnusedCheckCastTargetOptimizationTestRunner.java
new file mode 100644
index 0000000..2b93218
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/UnusedCheckCastTargetOptimizationTestRunner.java
@@ -0,0 +1,32 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+import org.junit.Test;
+
+public class UnusedCheckCastTargetOptimizationTestRunner extends DebugTestBase {
+
+ private static final Class MAIN_CLASS = UnusedCheckCastTargetOptimizationTest.class;
+ private static final Class SUPER_CLASS = UnusedCheckCastTargetOptimizationTest.Super.class;
+ private static final Class SUBCLASS_CLASS = UnusedCheckCastTargetOptimizationTest.Subclass.class;
+ private static final String FILE = MAIN_CLASS.getSimpleName() + ".java";
+ private static final String NAME = MAIN_CLASS.getCanonicalName();
+
+ @Test
+ public void test() throws Throwable {
+ runDebugTest(
+ new D8DebugTestConfig().compileAndAddClasses(temp, MAIN_CLASS, SUPER_CLASS, SUBCLASS_CLASS),
+ NAME,
+ breakpoint(NAME, "main", 14),
+ run(),
+ checkLine(FILE, 14),
+ checkLocal("b"),
+ checkNoLocal("c"),
+ stepOver(),
+ checkLine(FILE, 15),
+ checkLocal("b"),
+ checkLocal("c"),
+ run());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index 9edc735..89935a7 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -11,7 +11,6 @@
import com.android.tools.r8.errors.DexOverflowException;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexCode.Try;
@@ -25,6 +24,7 @@
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.origin.SynthesizedOrigin;
import com.android.tools.r8.utils.DefaultDiagnosticsHandler;
@@ -80,7 +80,7 @@
holder, dexItemFactory.createProto(dexItemFactory.voidType), "theMethod"),
MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC, false),
DexAnnotationSet.empty(),
- DexAnnotationSetRefList.empty(),
+ ParameterAnnotationsList.empty(),
code);
}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/A.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/A.java
new file mode 100644
index 0000000..c700163
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/A.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations;
+
+public class A {
+ public A method() {
+ return new A();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/B.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/B.java
new file mode 100644
index 0000000..7d8134f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/B.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations;
+
+public class B extends A {
+ @Override
+ public A method() {
+ return new B();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/C.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/C.java
new file mode 100644
index 0000000..90030d5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/C.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations;
+
+public class C extends B {
+ @Override
+ public A method() {
+ return new C();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/Client.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/Client.java
new file mode 100644
index 0000000..0c6bd44
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/Client.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations;
+
+public class Client {
+ public static void main(String[] args) {
+ A a = new A().method();
+ A b = new B().method();
+ A c = new C().method();
+
+ System.out.println("a=" + a.getClass().getSimpleName());
+ System.out.println("b=" + b.getClass().getSimpleName());
+ System.out.println("c=" + c.getClass().getSimpleName());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnType.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnType.java
new file mode 100644
index 0000000..3d7a7fa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnType.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+// This is a copy of dalvik.annotation.codegen.CovariantReturnType.
+@Repeatable(CovariantReturnType.CovariantReturnTypes.class)
+@Retention(RetentionPolicy.CLASS)
+@Target({ElementType.METHOD})
+public @interface CovariantReturnType {
+ Class<?> returnType();
+
+ int presentAfter();
+
+ @Retention(RetentionPolicy.CLASS)
+ @Target({ElementType.METHOD})
+ @interface CovariantReturnTypes {
+ CovariantReturnType[] value();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
new file mode 100644
index 0000000..9c58d25
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerTest.java
@@ -0,0 +1,191 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import java.util.Collections;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class CovariantReturnTypeAnnotationTransformerTest extends AsmTestBase {
+ public static final String PACKAGE_NAME = "com/android/tools/r8/ir/desugar/annotations";
+ public static final String CRT_NAME = "dalvik/annotation/codegen/CovariantReturnType";
+ public static final String CRTS_SIMPLE_NAME = "CovariantReturnTypes";
+
+ @Test
+ public void testVersion1WithClient1And2() throws Exception {
+ AndroidApp input =
+ buildAndroidApp(
+ ToolHelper.getClassAsBytes(Client.class),
+ ToolHelper.getClassAsBytes(A.class),
+ ToolHelper.getClassAsBytes(B.class),
+ ToolHelper.getClassAsBytes(C.class));
+
+ // Version 1 of the library should always work.
+ succeedsIndependentOfFlag(input, false);
+ }
+
+ @Test
+ public void testVersion1WithClient3() throws Exception {
+ AndroidApp input =
+ buildAndroidApp(
+ com.android.tools.r8.ir.desugar.annotations.version3.ClientDump.dump(),
+ ToolHelper.getClassAsBytes(A.class),
+ ToolHelper.getClassAsBytes(B.class),
+ ToolHelper.getClassAsBytes(C.class));
+
+ // There will be no methods with the signature "L.../B;->method()L.../B;" and
+ // "L.../C;->method()L.../C;".
+ failsIndependentOfFlag(input);
+ }
+
+ @Ignore("b/78618808")
+ @Test
+ public void testVersion2WithClient1And2() throws Exception {
+ AndroidApp input =
+ buildAndroidApp(
+ ToolHelper.getClassAsBytes(Client.class),
+ ToolHelper.getClassAsBytes(A.class),
+ com.android.tools.r8.ir.desugar.annotations.version2.BDump.dump(),
+ com.android.tools.r8.ir.desugar.annotations.version2.CDump.dump());
+
+ // Version 2 of the library should always work.
+ succeedsIndependentOfFlag(input, true);
+ }
+
+ @Ignore("b/78618808")
+ @Test
+ public void testVersion2WithClient3() throws Exception {
+ AndroidApp input =
+ buildAndroidApp(
+ com.android.tools.r8.ir.desugar.annotations.version3.ClientDump.dump(),
+ ToolHelper.getClassAsBytes(A.class),
+ com.android.tools.r8.ir.desugar.annotations.version2.BDump.dump(),
+ com.android.tools.r8.ir.desugar.annotations.version2.CDump.dump());
+
+ // If CovariantReturnType annotations are processed, then synthetic methods with the signatures
+ // "L.../B;->method()L.../B;" and "L.../C;->method()L.../C;" will be added by D8.
+ succeedsWithOption(input, true, true);
+
+ // If CovariantReturnType annotations are ignored, then there will be no methods with the
+ // signatures "L.../B;->method()L.../B;" and "L.../C;->method()L.../C;".
+ failsWithOption(input, false);
+ }
+
+ @Test
+ public void testVersion3WithClient3() throws Exception {
+ AndroidApp input =
+ buildAndroidApp(
+ com.android.tools.r8.ir.desugar.annotations.version3.ClientDump.dump(),
+ ToolHelper.getClassAsBytes(A.class),
+ com.android.tools.r8.ir.desugar.annotations.version3.BDump.dump(),
+ com.android.tools.r8.ir.desugar.annotations.version3.CDump.dump());
+
+ // Version 3 of the library should always work.
+ succeedsIndependentOfFlag(input, false);
+ }
+
+ @Test
+ public void testVersion3WithClient1And2() throws Exception {
+ AndroidApp input =
+ buildAndroidApp(
+ ToolHelper.getClassAsBytes(Client.class),
+ ToolHelper.getClassAsBytes(A.class),
+ com.android.tools.r8.ir.desugar.annotations.version3.BDump.dump(),
+ com.android.tools.r8.ir.desugar.annotations.version3.CDump.dump());
+
+ // Version 3 of the library should always work with client 1.
+ succeedsIndependentOfFlag(input, false);
+ }
+
+ private void succeedsWithOption(
+ AndroidApp input, boolean option, boolean checkPresenceOfSyntheticMethods) throws Exception {
+ AndroidApp output =
+ compileWithD8(input, options -> options.processCovariantReturnTypeAnnotations = option);
+ String stdout = runOnArt(output, Client.class.getCanonicalName());
+ Assert.assertEquals(getExpectedOutput(), stdout);
+
+ if (option && checkPresenceOfSyntheticMethods) {
+ checkPresenceOfSyntheticMethods(output);
+ }
+ }
+
+ private void failsWithOption(AndroidApp input, boolean option) throws Exception {
+ AndroidApp output =
+ compileWithD8(input, options -> options.processCovariantReturnTypeAnnotations = option);
+ ToolHelper.ProcessResult result = runOnArtRaw(output, Client.class.getCanonicalName());
+ assertThat(result.stderr, containsString("java.lang.NoSuchMethodError"));
+ }
+
+ private void succeedsIndependentOfFlag(AndroidApp input, boolean checkPresenceOfSyntheticMethods)
+ throws Exception {
+ succeedsWithOption(input, true, checkPresenceOfSyntheticMethods);
+ succeedsWithOption(input, false, checkPresenceOfSyntheticMethods);
+ }
+
+ private void failsIndependentOfFlag(AndroidApp input) throws Exception {
+ failsWithOption(input, true);
+ failsWithOption(input, false);
+ }
+
+ private void checkPresenceOfSyntheticMethods(AndroidApp output) throws Exception {
+ DexInspector inspector = new DexInspector(output);
+
+ // Get classes A, B, and C.
+ DexInspector.ClassSubject clazzA = inspector.clazz(A.class.getCanonicalName());
+ assertThat(clazzA, isPresent());
+
+ DexInspector.ClassSubject clazzB = inspector.clazz(B.class.getCanonicalName());
+ assertThat(clazzB, isPresent());
+
+ DexInspector.ClassSubject clazzC = inspector.clazz(C.class.getCanonicalName());
+ assertThat(clazzC, isPresent());
+
+ // Check that the original methods are there, and that they are not synthetic.
+ DexInspector.MethodSubject methodA =
+ clazzB.method(A.class.getCanonicalName(), "method", Collections.emptyList());
+ assertThat(methodA, isPresent());
+ Assert.assertTrue(!methodA.getMethod().isSyntheticMethod());
+
+ DexInspector.MethodSubject methodB =
+ clazzB.method(A.class.getCanonicalName(), "method", Collections.emptyList());
+ assertThat(methodB, isPresent());
+ Assert.assertTrue(!methodB.getMethod().isSyntheticMethod());
+
+ DexInspector.MethodSubject methodC =
+ clazzC.method(A.class.getCanonicalName(), "method", Collections.emptyList());
+ assertThat(methodC, isPresent());
+ Assert.assertTrue(!methodC.getMethod().isSyntheticMethod());
+
+ // Check that a synthetic method has been added to class B.
+ DexInspector.MethodSubject methodB2 =
+ clazzB.method(B.class.getCanonicalName(), "method", Collections.emptyList());
+ assertThat(methodB2, isPresent());
+ Assert.assertTrue(methodB2.getMethod().isSyntheticMethod());
+
+ // Check that two synthetic methods have been added to class C.
+ DexInspector.MethodSubject methodC2 =
+ clazzC.method(B.class.getCanonicalName(), "method", Collections.emptyList());
+ assertThat(methodC2, isPresent());
+ Assert.assertTrue(methodC2.getMethod().isSyntheticMethod());
+
+ DexInspector.MethodSubject methodC3 =
+ clazzC.method(C.class.getCanonicalName(), "method", Collections.emptyList());
+ assertThat(methodC3, isPresent());
+ Assert.assertTrue(methodC3.getMethod().isSyntheticMethod());
+ }
+
+ private String getExpectedOutput() {
+ return "a=A\nb=B\nc=C\n";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/B.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/B.java
new file mode 100644
index 0000000..342e3d4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/B.java
@@ -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 com.android.tools.r8.ir.desugar.annotations.version2;
+
+import com.android.tools.r8.ir.desugar.annotations.A;
+import com.android.tools.r8.ir.desugar.annotations.CovariantReturnType;
+
+public class B extends A {
+ @CovariantReturnType(returnType = B.class, presentAfter = 25)
+ @Override
+ public A method() {
+ return new B();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/BDump.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/BDump.java
new file mode 100644
index 0000000..3dadc2b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/BDump.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations.version2;
+
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.CRT_NAME;
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.PACKAGE_NAME;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+// Generated by running tools/asmifier.py on build/classes/test/com/android/tools/r8/ir/desugar/
+// annotations/version2/B.class, removing the subpackage "version2" from all class names, and
+// manually changing the name of the CovariantReturnType annotation to
+// "Ldalvik/annotation/codegen/CovariantReturnType;".
+public class BDump implements Opcodes {
+ public static byte[] dump() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, PACKAGE_NAME + "/B", null, PACKAGE_NAME + "/A", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/A", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "method", "()L" + PACKAGE_NAME + "/A;", null, null);
+ {
+ av0 = mv.visitAnnotation("L" + CRT_NAME + ";", false);
+ av0.visit("returnType", Type.getType("L" + PACKAGE_NAME + "/B;"));
+ av0.visit("presentAfter", new Integer(25));
+ av0.visitEnd();
+ }
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, PACKAGE_NAME + "/B");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/B", "<init>", "()V", false);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/C.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/C.java
new file mode 100644
index 0000000..52fe17f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/C.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations.version2;
+
+import com.android.tools.r8.ir.desugar.annotations.A;
+import com.android.tools.r8.ir.desugar.annotations.CovariantReturnType;
+
+public class C extends B {
+ @CovariantReturnType(returnType = B.class, presentAfter = 25)
+ @CovariantReturnType(returnType = C.class, presentAfter = 28)
+ @Override
+ public A method() {
+ return new C();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.java
new file mode 100644
index 0000000..bc06250
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version2/CDump.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.ir.desugar.annotations.version2;
+
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.*;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+// Generated by running tools/asmifier.py on build/classes/test/com/android/tools/r8/ir/desugar/
+// annotations/version2/C.class, removing the subpackage "version2" from all class names, and
+// manually changing the name of the CovariantReturnType and CovariantReturnTypes annotations to
+// "Ldalvik/annotation/codegen/CovariantReturnType;" and
+// "Ldalvik/annotation/codegen/CovariantReturnType$CovariantReturnTypes;", respectively.
+public class CDump implements Opcodes {
+ public static byte[] dump() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, PACKAGE_NAME + "/C", null, PACKAGE_NAME + "/B", null);
+
+ cw.visitInnerClass(
+ CRT_NAME + "$" + CRTS_SIMPLE_NAME,
+ CRT_NAME,
+ CRTS_SIMPLE_NAME,
+ ACC_PUBLIC + ACC_STATIC + ACC_ANNOTATION + ACC_ABSTRACT + ACC_INTERFACE);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/B", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "method", "()L" + PACKAGE_NAME + "/A;", null, null);
+ {
+ av0 = mv.visitAnnotation("L" + CRT_NAME + "$" + CRTS_SIMPLE_NAME + ";", false);
+ {
+ AnnotationVisitor av1 = av0.visitArray("value");
+ {
+ AnnotationVisitor av2 = av1.visitAnnotation(null, "L" + CRT_NAME + ";");
+ av2.visit("returnType", Type.getType("L" + PACKAGE_NAME + "/B;"));
+ av2.visit("presentAfter", new Integer(25));
+ av2.visitEnd();
+ }
+ {
+ AnnotationVisitor av2 = av1.visitAnnotation(null, "L" + CRT_NAME + ";");
+ av2.visit("returnType", Type.getType("L" + PACKAGE_NAME + "/C;"));
+ av2.visit("presentAfter", new Integer(28));
+ av2.visitEnd();
+ }
+ av1.visitEnd();
+ }
+ av0.visitEnd();
+ }
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, PACKAGE_NAME + "/C");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/C", "<init>", "()V", false);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/B.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/B.java
new file mode 100644
index 0000000..1b1fa1c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/B.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations.version3;
+
+import com.android.tools.r8.ir.desugar.annotations.A;
+
+public class B extends A {
+ @Override
+ public B method() {
+ return new B();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/BDump.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/BDump.java
new file mode 100644
index 0000000..91c16c3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/BDump.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations.version3;
+
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.PACKAGE_NAME;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running tools/asmifier.py on build/classes/test/com/android/tools/r8/ir/desugar/
+// annotations/version3/B.class, and removing the subpackage "version3" from all class names.
+public class BDump implements Opcodes {
+ public static byte[] dump() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(
+ V1_8,
+ ACC_PUBLIC + ACC_SUPER,
+ "" + PACKAGE_NAME + "/B",
+ null,
+ "" + PACKAGE_NAME + "/A",
+ null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "" + PACKAGE_NAME + "/A", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "method", "()L" + PACKAGE_NAME + "/B;", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, "" + PACKAGE_NAME + "/B");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "" + PACKAGE_NAME + "/B", "<init>", "()V", false);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ {
+ mv =
+ cw.visitMethod(
+ ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC,
+ "method",
+ "()L" + PACKAGE_NAME + "/A;",
+ null,
+ null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "" + PACKAGE_NAME + "/B", "method", "()L" + PACKAGE_NAME + "/B;", false);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/C.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/C.java
new file mode 100644
index 0000000..fd3bedc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/C.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations.version3;
+
+public class C extends B {
+ @Override
+ public C method() {
+ return new C();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/CDump.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/CDump.java
new file mode 100644
index 0000000..219c3f1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/CDump.java
@@ -0,0 +1,82 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations.version3;
+
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.PACKAGE_NAME;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running tools/asmifier.py on build/classes/test/com/android/tools/r8/ir/desugar/
+// annotations/version3/C.class, and removing the subpackage "version3" from all class names.
+public class CDump implements Opcodes {
+ public static byte[] dump() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, PACKAGE_NAME + "/C", null, PACKAGE_NAME + "/B", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/B", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "method", "()L" + PACKAGE_NAME + "/C;", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, PACKAGE_NAME + "/C");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/C", "<init>", "()V", false);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(2, 1);
+ mv.visitEnd();
+ }
+ {
+ mv =
+ cw.visitMethod(
+ ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC,
+ "method",
+ "()L" + PACKAGE_NAME + "/B;",
+ null,
+ null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, PACKAGE_NAME + "/C", "method", "()L" + PACKAGE_NAME + "/C;", false);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv =
+ cw.visitMethod(
+ ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC,
+ "method",
+ "()L" + PACKAGE_NAME + "/A;",
+ null,
+ null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, PACKAGE_NAME + "/C", "method", "()L" + PACKAGE_NAME + "/C;", false);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/Client.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/Client.java
new file mode 100644
index 0000000..f2feffa
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/Client.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations.version3;
+
+import com.android.tools.r8.ir.desugar.annotations.A;
+
+public class Client {
+ public static void main(String[] args) {
+ A a = new A().method();
+ B b = new B().method();
+ C c = new C().method();
+
+ System.out.println("a=" + a.getClass().getSimpleName());
+ System.out.println("b=" + b.getClass().getSimpleName());
+ System.out.println("c=" + c.getClass().getSimpleName());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/ClientDump.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/ClientDump.java
new file mode 100644
index 0000000..84971e4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/version3/ClientDump.java
@@ -0,0 +1,144 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.desugar.annotations.version3;
+
+import static com.android.tools.r8.ir.desugar.annotations.CovariantReturnTypeAnnotationTransformerTest.PACKAGE_NAME;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// Generated by running tools/asmifier.py on build/classes/test/com/android/tools/r8/ir/desugar/
+// annotations/version3/Client.class, and removing the subpackage "version3" from all class names.
+public class ClientDump implements Opcodes {
+ public static byte[] dump() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(
+ V1_8, ACC_PUBLIC + ACC_SUPER, PACKAGE_NAME + "/Client", null, "java/lang/Object", null);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ mv.visitCode();
+ mv.visitTypeInsn(NEW, PACKAGE_NAME + "/A");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/A", "<init>", "()V", false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, PACKAGE_NAME + "/A", "method", "()L" + PACKAGE_NAME + "/A;", false);
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitTypeInsn(NEW, PACKAGE_NAME + "/B");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/B", "<init>", "()V", false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, PACKAGE_NAME + "/B", "method", "()L" + PACKAGE_NAME + "/B;", false);
+ mv.visitVarInsn(ASTORE, 2);
+ mv.visitTypeInsn(NEW, PACKAGE_NAME + "/C");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, PACKAGE_NAME + "/C", "<init>", "()V", false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, PACKAGE_NAME + "/C", "method", "()L" + PACKAGE_NAME + "/C;", false);
+ mv.visitVarInsn(ASTORE, 3);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
+ mv.visitLdcInsn("a=");
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/lang/StringBuilder",
+ "append",
+ "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+ false);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Class", "getSimpleName", "()Ljava/lang/String;", false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/lang/StringBuilder",
+ "append",
+ "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+ false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
+ mv.visitLdcInsn("b=");
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/lang/StringBuilder",
+ "append",
+ "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+ false);
+ mv.visitVarInsn(ALOAD, 2);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Class", "getSimpleName", "()Ljava/lang/String;", false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/lang/StringBuilder",
+ "append",
+ "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+ false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
+ mv.visitLdcInsn("c=");
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/lang/StringBuilder",
+ "append",
+ "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+ false);
+ mv.visitVarInsn(ALOAD, 3);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Class", "getSimpleName", "()Ljava/lang/String;", false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/lang/StringBuilder",
+ "append",
+ "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+ false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(3, 4);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
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 2ff2f71..9de9f5a 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -29,7 +29,6 @@
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexAnnotationSetRefList;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
@@ -40,6 +39,7 @@
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Position;
@@ -617,7 +617,7 @@
DexString.EMPTY_ARRAY),
access,
DexAnnotationSet.empty(),
- DexAnnotationSetRefList.empty(),
+ ParameterAnnotationsList.empty(),
code);
IRCode ir = code.buildIR(method, options, Origin.unknown());
RegisterAllocator allocator = new LinearScanRegisterAllocator(ir, options);
diff --git a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
index 490162a..3429c2a 100644
--- a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
+++ b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
@@ -12,7 +12,6 @@
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.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
@@ -30,7 +29,6 @@
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(Utils.class));
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
builder.setMode(CompilationMode.RELEASE);
builder.addProguardConfiguration(
ImmutableList.of(
diff --git a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
index 4a7ba49..1f9c20f 100644
--- a/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
+++ b/src/test/java/com/android/tools/r8/neverreturnsnormally/NeverReturnsNormallyTest.java
@@ -13,7 +13,6 @@
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.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
@@ -31,7 +30,6 @@
R8Command.Builder builder = R8Command.builder();
builder.addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class));
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- builder.setMinApiLevel(AndroidApiLevel.O.getLevel());
builder.setMode(mode);
builder.addProguardConfiguration(
ImmutableList.of(
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 c616810..ba65d92 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
@@ -14,13 +14,16 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class Regress72485384Test extends TestBase {
+ @Rule public ExpectedException thrown = ExpectedException.none();
@Parameters(name = "{0}")
public static Collection<Object[]> getParameters() {
@@ -53,7 +56,13 @@
@Test
public void testSignatureRewrite() throws Exception {
AndroidApp app = compileWithR8(CLASSES, proguardConfig);
+
if (expectedErrorMessage == null) {
+ if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(ToolHelper.DexVm.Version.V6_0_1)) {
+ // Resolution of java.util.function.Function fails.
+ thrown.expect(AssertionError.class);
+ }
+
runOnArt(app, Main.class.getCanonicalName());
} else {
ToolHelper.ProcessResult result = runOnArtRaw(app, Main.class.getCanonicalName());
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 167262f..dbd4e23 100644
--- a/src/test/java/com/android/tools/r8/resource/DataResourceTest.java
+++ b/src/test/java/com/android/tools/r8/resource/DataResourceTest.java
@@ -6,7 +6,7 @@
import com.android.tools.r8.CompilationException;
import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
@@ -35,9 +35,10 @@
ProcessResult referenceResult = ToolHelper.runJava(inputJar, mainClassName);
Path r8Out = temp.getRoot().toPath().resolve("r8out.jar");
- R8Command.Builder builder = R8Command.builder()
- .addProgramFiles(inputJar)
- .setOutput(r8Out, OutputMode.DexIndexed);
+ R8Command.Builder builder =
+ R8Command.builder()
+ .addProgramFiles(inputJar)
+ .setProgramConsumer(new DexIndexedConsumer.ArchiveConsumer(r8Out, true));
ToolHelper.runR8(builder.build());
ProcessResult r8Result = ToolHelper.runArtRaw(r8Out.toString(), mainClassName);
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
new file mode 100644
index 0000000..6ecdb09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryProvidedProguardRulesTest.java
@@ -0,0 +1,201 @@
+// 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 static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DiagnosticsChecker;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.ProgramResourceProvider;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.CharSource;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+import org.junit.Test;
+
+class A {
+ private static String buildClassName(String className) {
+ return A.class.getPackage().getName() + "." + className;
+ }
+
+ public static void main(String[] args) {
+ try {
+ Class bClass = Class.forName(buildClassName("B"));
+ System.out.println("YES");
+ } catch (ClassNotFoundException e) {
+ System.out.println("NO");
+ }
+ }
+}
+
+class B {
+
+}
+
+public class LibraryProvidedProguardRulesTest extends TestBase {
+ private void addTextJarEntry(JarOutputStream out, String name, String content) throws Exception {
+ out.putNextEntry(new ZipEntry(name));
+ ByteStreams.copy(
+ CharSource.wrap(content).asByteSource(StandardCharsets.UTF_8).openBufferedStream(), out);
+ out.closeEntry();
+ }
+
+ private AndroidApp runTest(List<String> rules, DiagnosticsHandler handler) throws Exception {
+ Path jar = temp.newFile("test.jar").toPath();
+ try (JarOutputStream out = new JarOutputStream(new FileOutputStream(jar.toFile()))) {
+ addTestClassesToJar(out, A.class, B.class);
+ for (int i = 0; i < rules.size(); i++) {
+ String name = "META-INF/proguard/jar" + (i == 0 ? "" : i) + ".rules";
+ addTextJarEntry(out, name, rules.get(i));
+ }
+ }
+
+ try {
+ R8Command command = (handler != null ? R8Command.builder(handler) : R8Command.builder())
+ .addProgramFiles(jar)
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .build();
+ return ToolHelper.runR8(command);
+ } catch (CompilationFailedException e) {
+ assertNotNull(handler);
+ return null;
+ }
+ }
+
+ private AndroidApp runTest(String rules, DiagnosticsHandler handler) throws Exception {
+ return runTest(ImmutableList.of(rules), handler);
+ }
+
+
+ @Test
+ public void keepOnlyA() throws Exception {
+ AndroidApp app = runTest("-keep class " + A.class.getTypeName() +" {}", null);
+ DexInspector inspector = new DexInspector(app);
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(B.class), not(isPresent()));
+ }
+
+ @Test
+ public void keepOnlyB() throws Exception {
+ AndroidApp app = runTest("-keep class **B {}", null);
+ DexInspector inspector = new DexInspector(app);
+ assertThat(inspector.clazz(A.class), not(isPresent()));
+ assertThat(inspector.clazz(B.class), isPresent());
+ }
+
+ @Test
+ public void keepBoth() throws Exception {
+ AndroidApp app = runTest("-keep class ** {}", null);
+ DexInspector inspector = new DexInspector(app);
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(B.class), isPresent());
+ }
+
+ @Test
+ public void multipleFiles() throws Exception {
+ AndroidApp app = runTest(ImmutableList.of("-keep class **A {}", "-keep class **B {}"), null);
+ DexInspector inspector = new DexInspector(app);
+ assertThat(inspector.clazz(A.class), isPresent());
+ assertThat(inspector.clazz(B.class), isPresent());
+ }
+
+ private void checkOrigin(Origin origin) {
+ assertTrue(origin instanceof ArchiveEntryOrigin);
+ assertEquals(origin.part(), "META-INF/proguard/jar.rules");
+ assertTrue(origin.parent() instanceof PathOrigin);
+ }
+
+ @Test
+ public void syntaxError() throws Exception {
+ DiagnosticsChecker checker = new DiagnosticsChecker();
+ AndroidApp app = runTest("error", checker);
+ assertNull(app);
+ DiagnosticsChecker.checkDiagnostic(
+ checker.errors.get(0), this::checkOrigin, 1, 1, "Expected char '-'");
+ }
+
+ @Test
+ public void includeError() throws Exception {
+ DiagnosticsChecker checker = new DiagnosticsChecker();
+ AndroidApp app = runTest("-include other.rules", checker);
+ assertNull(app);
+ DiagnosticsChecker.checkDiagnostic(checker.errors.get(0), this::checkOrigin, 1, 10,
+ "Options with file names are not supported");
+ }
+
+ class TestProvider implements ProgramResourceProvider, DataResourceProvider {
+
+ @Override
+ public Collection<ProgramResource> getProgramResources() throws ResourceException {
+ byte[] bytes;
+ try {
+ bytes = ByteStreams.toByteArray(A.class.getResourceAsStream("A.class"));
+ } catch (IOException e) {
+ throw new ResourceException(Origin.unknown(), "Unexpected");
+ }
+ return ImmutableList.of(
+ ProgramResource.fromBytes(Origin.unknown(), Kind.CF, bytes,
+ Collections.singleton(DescriptorUtils.javaTypeToDescriptor(A.class.getTypeName()))));
+ }
+
+ @Override
+ public DataResourceProvider getDataResourceProvider() {
+ return this;
+ }
+
+ @Override
+ public void accept(Visitor visitor) throws ResourceException {
+ throw new ResourceException(Origin.unknown(), "Cannot provide data resources after all");
+ }
+ }
+
+ @Test
+ public void throwingDataResourceProvider() throws Exception {
+ DiagnosticsChecker checker = new DiagnosticsChecker();
+ try {
+ R8Command command = R8Command.builder(checker)
+ .addProgramResourceProvider(new TestProvider())
+ .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+ .build();
+ fail("Should not succeed");
+ } catch (CompilationFailedException e) {
+ DiagnosticsChecker.checkDiagnostic(
+ checker.errors.get(0),
+ origin -> assertSame(origin, Origin.unknown()),
+ "Cannot provide data resources after all");
+ }
+ }
+}
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 b7277cb..1012421 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -1718,6 +1718,7 @@
assertEquals(0, handler.errors.size());
}
+ // TODO(sgjesse): Change to use DiagnosticsChecker.
private Diagnostic checkDiagnostic(List<Diagnostic> diagnostics, Path path, int lineStart,
int columnStart, String... messageParts) {
assertEquals(1, diagnostics.size());
diff --git a/tools/archive.py b/tools/archive.py
index 815e416..07f138a 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -3,22 +3,25 @@
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
-import gradle
import create_maven_release
-import d8
+import gradle
import os
-import r8
+import shutil
import subprocess
import sys
+import toolhelper
import utils
-import shutil
import zipfile
ARCHIVE_BUCKET = 'r8-releases'
+def GetToolVersion(jar_path):
+ output = subprocess.check_output(['java', '-jar', jar_path, '--version'])
+ return output.splitlines()[0].strip()
+
def GetVersion():
- r8_version = r8.run(['--version'], build = False).splitlines()[0].strip()
- d8_version = d8.run(['--version'], build = False).splitlines()[0].strip()
+ r8_version = GetToolVersion(utils.R8_JAR)
+ d8_version = GetToolVersion(utils.D8_JAR)
# The version printed is "D8 vVERSION_NUMBER" and "R8 vVERSION_NUMBER"
# Sanity check that versions match.
if d8_version.split()[1] != r8_version.split()[1]:
@@ -49,21 +52,30 @@
'in a commit that have -dev in version')
return True
-def GetStorageDestination(storage_prefix, version, file_name, is_master):
+def GetStorageDestination(storage_prefix,
+ version_or_path,
+ file_name,
+ is_master):
# We archive master commits under raw/master instead of directly under raw
- version_dir = GetVersionDestination(storage_prefix, version, is_master)
+ version_dir = GetVersionDestination(storage_prefix,
+ version_or_path,
+ is_master)
return '%s/%s' % (version_dir, file_name)
-def GetVersionDestination(storage_prefix, version, is_master):
+def GetVersionDestination(storage_prefix, version_or_path, is_master):
archive_dir = 'raw/master' if is_master else 'raw'
- return '%s%s/%s/%s' % (storage_prefix, ARCHIVE_BUCKET, archive_dir, version)
+ return '%s%s/%s/%s' % (storage_prefix, ARCHIVE_BUCKET,
+ archive_dir, version_or_path)
-def GetUploadDestination(version, file_name, is_master):
- return GetStorageDestination('gs://', version, file_name, is_master)
+def GetUploadDestination(version_or_path, file_name, is_master):
+ return GetStorageDestination('gs://', version_or_path, file_name, is_master)
-def GetUrl(version, file_name, is_master):
+def GetUrl(version_or_path, file_name, is_master):
return GetStorageDestination('http://storage.googleapis.com/',
- version, file_name, is_master)
+ version_or_path, file_name, is_master)
+
+def GetMavenUrl(is_master):
+ return GetVersionDestination('http://storage.googleapis.com/', '', is_master)
def Main():
if not 'BUILDBOT_BUILDERNAME' in os.environ:
@@ -113,14 +125,13 @@
print('Uploading %s to %s' % (tagged_jar, destination))
utils.upload_file_to_cloud_storage(tagged_jar, destination)
print('File available at: %s' % GetUrl(version, file_name, is_master))
- # Upload extracted maven directory for easy testing in studio.
- zip_ref = zipfile.ZipFile(utils.MAVEN_ZIP, 'r')
- zip_ref.extractall(temp)
- zip_ref.close()
- utils.upload_dir_to_cloud_storage(
- os.path.join(temp, 'com'),
- GetUploadDestination(version, 'com', is_master))
- print('Maven repo root available at: %s' % GetUrl(version, '', is_master))
+ if file == utils.R8_JAR:
+ # Upload R8 to a maven compatible location.
+ maven_dst = GetUploadDestination(utils.get_maven_path(version),
+ 'r8-%s.jar' % version, is_master)
+ utils.upload_file_to_cloud_storage(tagged_jar, maven_dst)
+ print('Maven repo root available at: %s' % GetMavenUrl(is_master))
+
if __name__ == '__main__':
sys.exit(Main())
diff --git a/tools/bisect.py b/tools/bisect.py
index c8d1501..74c880e 100755
--- a/tools/bisect.py
+++ b/tools/bisect.py
@@ -3,33 +3,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.
-import gradle
-import os
-import subprocess
import sys
-import utils
-
-JAR = os.path.join(utils.REPO_ROOT, 'build', 'libs', 'bisect.jar')
-
-def run(args, build, debug):
- if build:
- gradle.RunGradle(['bisect'])
- cmd = ['java']
- if debug:
- cmd.append('-ea')
- cmd.extend(['-jar', JAR])
- cmd.extend(args)
- subprocess.check_call(cmd)
-
-def main():
- build = True
- args = []
- for arg in sys.argv[1:]:
- if arg in ("--build", "--no-build"):
- build = arg == "--build"
- else:
- args.append(arg)
- run(args, build, True)
+import toolhelper
if __name__ == '__main__':
- sys.exit(main())
+ sys.exit(toolhelper.run('bisect', sys.argv[1:]))
diff --git a/tools/compatdx.py b/tools/compatdx.py
index 25a59db..c4cb320 100755
--- a/tools/compatdx.py
+++ b/tools/compatdx.py
@@ -3,42 +3,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.
-import gradle
-import os
-import subprocess
import sys
-import utils
-
-def run(args, build = True, debug = True, profile = False, track_memory_file=None):
- if build:
- gradle.RunGradle(['CompatDX'])
- cmd = []
- if track_memory_file:
- cmd.extend(['tools/track_memory.sh', track_memory_file])
- cmd.append('java')
- if debug:
- cmd.append('-ea')
- if profile:
- cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
- cmd.extend(['-jar', utils.COMPATDX_JAR])
- cmd.extend(args)
- subprocess.check_call(cmd)
-
-def main():
- build = True
- args = []
- for arg in sys.argv[1:]:
- if arg in ("--build", "--no-build"):
- build = arg == "--build"
- else:
- args.append(arg)
- try:
- run(args, build)
- except subprocess.CalledProcessError as e:
- # In case anything relevant was printed to stdout, normally this is already
- # on stderr.
- print(e.output)
- return e.returncode
+import toolhelper
if __name__ == '__main__':
- sys.exit(main())
+ sys.exit(toolhelper.run('compatdx', sys.argv[1:]))
diff --git a/tools/compatproguard.py b/tools/compatproguard.py
new file mode 100755
index 0000000..10542e3
--- /dev/null
+++ b/tools/compatproguard.py
@@ -0,0 +1,10 @@
+#!/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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+ sys.exit(toolhelper.run('compatproguard', sys.argv[1:]))
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index 0a7f122..662d53e 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -210,7 +210,7 @@
# Create directory structure for this version.
version = determine_version()
with utils.TempDir() as tmp_dir:
- version_dir = join(tmp_dir, 'com', 'android', 'tools', 'r8', version)
+ version_dir = join(tmp_dir, utils.get_maven_path(version))
makedirs(version_dir)
# Write the pom file.
pom_file = join(version_dir, 'r8-' + version + '.pom')
diff --git a/tools/d8.py b/tools/d8.py
index bf97845..18a4a67 100755
--- a/tools/d8.py
+++ b/tools/d8.py
@@ -3,46 +3,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.
-import gradle
-import os
-import subprocess
import sys
-import utils
-
-def run(args, build=True, debug=True, profile=False,
- track_memory_file=None):
- if build:
- gradle.RunGradle(['D8'])
- cmd = []
- if track_memory_file:
- cmd.extend(['tools/track_memory.sh', track_memory_file])
- cmd.append('java')
- if debug:
- cmd.append('-ea')
- if profile:
- cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
- cmd.extend(['-jar', utils.D8_JAR])
- cmd.extend(args)
- utils.PrintCmd(cmd)
- result = subprocess.check_output(cmd)
- print(result)
- return result
-
-def main():
- build = True
- args = []
- for arg in sys.argv[1:]:
- if arg in ("--build", "--no-build"):
- build = arg == "--build"
- else:
- args.append(arg)
- try:
- run(args, build)
- except subprocess.CalledProcessError as e:
- # In case anything relevant was printed to stdout, normally this is already
- # on stderr.
- print(e.output)
- return e.returncode
+import toolhelper
if __name__ == '__main__':
- sys.exit(main())
+ sys.exit(toolhelper.run('d8', sys.argv[1:]))
diff --git a/tools/d8logger.py b/tools/d8logger.py
new file mode 100755
index 0000000..9fb3508
--- /dev/null
+++ b/tools/d8logger.py
@@ -0,0 +1,10 @@
+#!/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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+ sys.exit(toolhelper.run('d8logger', sys.argv[1:]))
diff --git a/tools/dexfilemerger.py b/tools/dexfilemerger.py
new file mode 100755
index 0000000..7bdfc22
--- /dev/null
+++ b/tools/dexfilemerger.py
@@ -0,0 +1,10 @@
+#!/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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+ sys.exit(toolhelper.run('dexfilemerger', sys.argv[1:]))
diff --git a/tools/dexsegments.py b/tools/dexsegments.py
new file mode 100755
index 0000000..f984063
--- /dev/null
+++ b/tools/dexsegments.py
@@ -0,0 +1,10 @@
+#!/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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+ sys.exit(toolhelper.run('dexsegments', sys.argv[1:]))
diff --git a/tools/dexsplitter.py b/tools/dexsplitter.py
new file mode 100755
index 0000000..415b149
--- /dev/null
+++ b/tools/dexsplitter.py
@@ -0,0 +1,10 @@
+#!/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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+ sys.exit(toolhelper.run('dexsplitter', sys.argv[1:]))
diff --git a/tools/disasm.py b/tools/disasm.py
new file mode 100755
index 0000000..0d2599a
--- /dev/null
+++ b/tools/disasm.py
@@ -0,0 +1,10 @@
+#!/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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+ sys.exit(toolhelper.run('disasm', sys.argv[1:]))
diff --git a/tools/extractmarker.py b/tools/extractmarker.py
new file mode 100755
index 0000000..36a9c88
--- /dev/null
+++ b/tools/extractmarker.py
@@ -0,0 +1,10 @@
+#!/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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+ sys.exit(toolhelper.run('extractmarker', sys.argv[1:]))
diff --git a/tools/golem_build.py b/tools/golem_build.py
new file mode 100755
index 0000000..6d3a9b7
--- /dev/null
+++ b/tools/golem_build.py
@@ -0,0 +1,19 @@
+#!/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.
+
+# Utility script to make it easier to update what golem builds.
+
+import gradle
+import sys
+
+GRADLE_ARGS = ['--no-daemon']
+BUILD_TARGETS = ['R8', 'D8', 'buildExampleJars', 'CompatDx',
+ 'downloadAndroidCts', 'downloadDx']
+
+def Main():
+ gradle.RunGradle(GRADLE_ARGS + BUILD_TARGETS)
+
+if __name__ == '__main__':
+ sys.exit(Main())
diff --git a/tools/jardiff.py b/tools/jardiff.py
new file mode 100755
index 0000000..894131c
--- /dev/null
+++ b/tools/jardiff.py
@@ -0,0 +1,10 @@
+#!/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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+ sys.exit(toolhelper.run('jardiff', sys.argv[1:]))
diff --git a/tools/maindex.py b/tools/maindex.py
new file mode 100755
index 0000000..ff5329a
--- /dev/null
+++ b/tools/maindex.py
@@ -0,0 +1,10 @@
+#!/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.
+
+import sys
+import toolhelper
+
+if __name__ == '__main__':
+ sys.exit(toolhelper.run('maindex', sys.argv[1:]))
diff --git a/tools/minify_tool.py b/tools/minify_tool.py
new file mode 100755
index 0000000..08b2ada
--- /dev/null
+++ b/tools/minify_tool.py
@@ -0,0 +1,113 @@
+#!/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.
+
+'''
+Run R8 (with the class-file backend) to optimize a command-line program.
+
+Given an input JAR (default: r8.jar) and a main-class, generates a new input JAR
+with the given main-class in the manifest along with a -keep rule for keeping
+just the main entrypoint, and runs R8 in release+classfile mode on the JAR.
+'''
+
+import argparse
+import os
+import re
+import sys
+import time
+import toolhelper
+import utils
+import zipfile
+
+KEEP = '-keep public class %s { public static void main(...); }\n'
+MANIFEST_PATH = 'META-INF/MANIFEST.MF'
+MANIFEST = 'Manifest-Version: 1.0\nMain-Class: %s\n\n'
+MANIFEST_PATTERN = r'Main-Class:\s*(\S+)'
+RT = os.path.join(utils.REPO_ROOT, 'third_party/openjdk/openjdk-rt-1.8/rt.jar')
+
+parser = argparse.ArgumentParser(description=__doc__.strip(),
+ formatter_class=argparse.RawTextHelpFormatter)
+parser.add_argument(
+ '-i', '--input-jar', default=utils.R8_JAR,
+ help='Input JAR to use (default: build/libs/r8.jar)')
+parser.add_argument(
+ '-o', '--output-jar',
+ help='Path to output JAR (default: build/libs/<MainClass>-min.jar)')
+parser.add_argument(
+ '-l', '--lib', default=RT,
+ help='Path to rt.jar to use instead of OpenJDK 1.8')
+parser.add_argument(
+ '-m', '--mainclass',
+ help='Create/overwrite MANIFEST.MF with the given Main-Class')
+parser.add_argument(
+ '-O', '--no-debug', dest='debug', action='store_false',
+ help='Disable assertions when running R8')
+parser.add_argument(
+ '--print-runtimeraw', metavar='BENCHMARKNAME',
+ help='Print "<BENCHMARKNAME>(RunTimeRaw): <elapsed> ms" at the end')
+
+def generate_output_name(input_jar, mainclass):
+ if not mainclass:
+ input_base, input_ext = os.path.splitext(input_jar)
+ return '%s-min%s' % (input_base, input_ext)
+ base = mainclass[mainclass.rindex('.')+1:] if '.' in mainclass else mainclass
+ return os.path.join(utils.LIBS, '%s-min.jar' % base)
+
+def repackage(input_jar, output_jar, mainclass):
+ print("Repackaging %s to %s with Main-Class: %s..." %
+ (input_jar, output_jar, mainclass))
+ manifest = MANIFEST % mainclass
+ with zipfile.ZipFile(input_jar, 'r') as input_zf:
+ with zipfile.ZipFile(output_jar, 'w') as output_zf:
+ for zipinfo in input_zf.infolist():
+ if zipinfo.filename.upper() == MANIFEST_PATH:
+ assert manifest is not None
+ output_zf.writestr(MANIFEST_PATH, manifest)
+ manifest = None
+ else:
+ output_zf.writestr(zipinfo, input_zf.read(zipinfo))
+ if manifest is not None:
+ output_zf.writestr(MANIFEST_PATH, manifest)
+
+def extract_mainclass(input_jar):
+ with zipfile.ZipFile(input_jar, 'r') as input_zf:
+ try:
+ manifest = input_zf.getinfo(MANIFEST_PATH)
+ except KeyError:
+ raise SystemExit('No --mainclass specified and no manifest in input JAR.')
+ mo = re.search(MANIFEST_PATTERN, input_zf.read(manifest))
+ if not mo:
+ raise SystemExit(
+ 'No --mainclass specified and no Main-Class in input JAR manifest.')
+ return mo.group(1)
+
+def minify_tool(mainclass=None, input_jar=utils.R8_JAR, output_jar=None, lib=RT,
+ debug=True, build=True, print_runtimeraw=None):
+ if output_jar is None:
+ output_jar = generate_output_name(input_jar, mainclass)
+ with utils.TempDir() as path:
+ if mainclass:
+ tmp_input_path = os.path.join(path, 'input.jar')
+ repackage(input_jar, tmp_input_path, mainclass)
+ else:
+ tmp_input_path = input_jar
+ mainclass = extract_mainclass(input_jar)
+ keep_path = os.path.join(path, 'keep.txt')
+ with open(keep_path, 'w') as fp:
+ fp.write(KEEP % mainclass)
+ args = ('--lib', lib,
+ '--classfile',
+ '--output', output_jar,
+ '--pg-conf', keep_path,
+ '--release',
+ tmp_input_path)
+ start_time = time.time()
+ return_code = toolhelper.run('r8', args, debug=debug, build=build)
+ if print_runtimeraw:
+ elapsed_ms = 1000 * (time.time() - start_time)
+ print('%s-Total(RunTimeRaw): %s ms' % (print_runtimeraw, elapsed_ms))
+ return return_code
+
+if __name__ == '__main__':
+ sys.exit(minify_tool(**vars(parser.parse_args())))
diff --git a/tools/r8.py b/tools/r8.py
index 3ae4da7..60c60a0 100755
--- a/tools/r8.py
+++ b/tools/r8.py
@@ -3,46 +3,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.
-import gradle
-import os
-import subprocess
import sys
-import utils
-
-def run(args, build=True, debug=True, profile=False,
- track_memory_file=None):
- if build:
- gradle.RunGradle(['r8'])
- cmd = []
- if track_memory_file:
- cmd.extend(['tools/track_memory.sh', track_memory_file])
- cmd.append('java')
- if debug:
- cmd.append('-ea')
- if profile:
- cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
- cmd.extend(['-jar', utils.R8_JAR])
- cmd.extend(args)
- utils.PrintCmd(cmd)
- result = subprocess.check_output(cmd)
- print(result)
- return result
-
-def main():
- build = True
- args = []
- for arg in sys.argv[1:]:
- if arg in ("--build", "--no-build"):
- build = arg == "--build"
- else:
- args.append(arg)
- try:
- run(args, build)
- except subprocess.CalledProcessError as e:
- # In case anything relevant was printed to stdout, normally this is already
- # on stderr.
- print(e.output)
- return e.returncode
+import toolhelper
if __name__ == '__main__':
- sys.exit(main())
+ sys.exit(toolhelper.run('r8', sys.argv[1:]))
diff --git a/tools/run-d8-on-gmscore.py b/tools/run-d8-on-gmscore.py
index 63865d0..b1bdc6e 100755
--- a/tools/run-d8-on-gmscore.py
+++ b/tools/run-d8-on-gmscore.py
@@ -3,11 +3,11 @@
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
-import d8
import gmscore_data
import optparse
import os
import sys
+import toolhelper
def ParseOptions():
result = optparse.OptionParser()
@@ -70,8 +70,13 @@
with open(options.dump_args_file, 'w') as args_file:
args_file.writelines([arg + os.linesep for arg in args])
else:
- d8.run(args, not options.no_build, not options.no_debug, options.profile,
- options.track_memory_to_file)
+ toolhelper.run(
+ 'd8',
+ args,
+ build=not options.no_build,
+ debug=not options.no_debug,
+ profile=options.profile,
+ track_memory_to_file=options.track_memory_to_file)
if __name__ == '__main__':
sys.exit(main())
diff --git a/tools/run_bootstrap_benchmark.py b/tools/run_bootstrap_benchmark.py
new file mode 100755
index 0000000..0c608f9
--- /dev/null
+++ b/tools/run_bootstrap_benchmark.py
@@ -0,0 +1,23 @@
+#!/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.
+
+import argparse
+import minify_tool
+import os
+import sys
+import utils
+
+
+PINNED_R8_JAR = os.path.join(utils.REPO_ROOT, 'third_party/r8/r8.jar')
+
+parser = argparse.ArgumentParser()
+parser.add_argument(
+ '--print-runtimeraw', metavar='BENCHMARKNAME',
+ help='Print "<BENCHMARKNAME>(RunTimeRaw): <elapsed> ms" at the end')
+
+
+if __name__ == '__main__':
+ sys.exit(minify_tool.minify_tool(input_jar=PINNED_R8_JAR, debug=False,
+ build=False, **vars(parser.parse_args())))
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index b9bcc60..571d96d 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -10,10 +10,9 @@
import sys
import time
-import d8
import gmail_data
import gmscore_data
-import r8
+import toolhelper
import utils
import youtube_data
@@ -191,22 +190,20 @@
if options.print_memoryuse and not options.track_memory_to_file:
options.track_memory_to_file = os.path.join(temp,
utils.MEMORY_USE_TMP_FILE)
- if options.compiler == 'd8':
- d8.run(args, not options.no_build, not options.no_debug,
- options.profile, options.track_memory_to_file)
- else:
- if app_provided_pg_conf:
- # Ensure that output of -printmapping and -printseeds go to the output
- # location and not where the app Proguard configuration places them.
- if outdir.endswith('.zip') or outdir.endswith('.jar'):
- pg_outdir = os.path.dirname(outdir)
- else:
- pg_outdir = outdir
- additional_pg_conf = GenerateAdditionalProguardConfiguration(
- temp, os.path.abspath(pg_outdir))
- args.extend(['--pg-conf', additional_pg_conf])
- r8.run(args, not options.no_build, not options.no_debug,
- options.profile, options.track_memory_to_file)
+ if options.compiler == 'r8' and app_provided_pg_conf:
+ # Ensure that output of -printmapping and -printseeds go to the output
+ # location and not where the app Proguard configuration places them.
+ if outdir.endswith('.zip') or outdir.endswith('.jar'):
+ pg_outdir = os.path.dirname(outdir)
+ else:
+ pg_outdir = outdir
+ additional_pg_conf = GenerateAdditionalProguardConfiguration(
+ temp, os.path.abspath(pg_outdir))
+ args.extend(['--pg-conf', additional_pg_conf])
+ toolhelper.run(options.compiler, args, build=not options.no_build,
+ debug=not options.no_debug,
+ profile=options.profile,
+ track_memory_to_file=options.track_memory_to_file)
if options.print_memoryuse:
print('{}(MemoryUse): {}'
.format(options.print_memoryuse,
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
new file mode 100644
index 0000000..a7f509c
--- /dev/null
+++ b/tools/toolhelper.py
@@ -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.
+
+import gradle
+import os
+import subprocess
+import sys
+import utils
+
+def run(tool, args, build=None, debug=True,
+ profile=False, track_memory_file=None):
+ if build is None:
+ build, args = extract_build_from_args(args)
+ if build:
+ gradle.RunGradle(['r8'])
+ cmd = []
+ if track_memory_file:
+ cmd.extend(['tools/track_memory.sh', track_memory_file])
+ cmd.append('java')
+ if debug:
+ cmd.append('-ea')
+ if profile:
+ cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
+ cmd.extend(['-jar', utils.R8_JAR, tool])
+ cmd.extend(args)
+ utils.PrintCmd(cmd)
+ return subprocess.call(cmd)
+
+def extract_build_from_args(input_args):
+ build = True
+ args = []
+ for arg in input_args:
+ if arg in ("--build", "--no-build"):
+ build = arg == "--build"
+ else:
+ args.append(arg)
+ return build, args
diff --git a/tools/utils.py b/tools/utils.py
index a138461..9f6959f 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -15,8 +15,6 @@
TOOLS_DIR = os.path.abspath(os.path.normpath(os.path.join(__file__, '..')))
REPO_ROOT = os.path.realpath(os.path.join(TOOLS_DIR, '..'))
MEMORY_USE_TMP_FILE = 'memory_use.tmp'
-DEX_SEGMENTS_JAR = os.path.join(REPO_ROOT, 'build', 'libs',
- 'dexsegments.jar')
DEX_SEGMENTS_RESULT_PATTERN = re.compile('- ([^:]+): ([0-9]+)')
BUILD = os.path.join(REPO_ROOT, 'build')
LIBS = os.path.join(BUILD, 'libs')
@@ -189,7 +187,7 @@
# Return a dictionary: {segment_name -> segments_size}
def getDexSegmentSizes(dex_files):
assert len(dex_files) > 0
- cmd = ['java', '-jar', DEX_SEGMENTS_JAR]
+ cmd = ['java', '-jar', R8_JAR, 'dexsegments']
cmd.extend(dex_files)
PrintCmd(cmd)
output = subprocess.check_output(cmd)
@@ -207,6 +205,9 @@
return result
+def get_maven_path(version):
+ return os.path.join('com', 'android', 'tools', 'r8', version)
+
def print_dexsegments(prefix, dex_files):
for segment_name, size in getDexSegmentSizes(dex_files).items():
print('{}-{}(CodeSize): {}'