Version 1.6.48
Cherry pick: Revert changes to JasminBuilder
CL: https://r8-review.googlesource.com/c/r8/+/45191
Cherry pick: Add support for dumping to a directory and continuing compilation.
CL: https://r8-review.googlesource.com/c/r8/+/45175
Cherry pick: Add tests and support for data resources to the dump file.
CL: https://r8-review.googlesource.com/c/r8/+/45167
Cherry pick: Add duplicate entries to the dump test.
CL: https://r8-review.googlesource.com/c/r8/+/45163
Cherry pick: Add support for reading a dump file.
CL: https://r8-review.googlesource.com/c/r8/+/45162
Cherry pick: Move dump utilities into AndroidApp.
CL: https://r8-review.googlesource.com/c/r8/+/45101
Change-Id: Ia22756fe323429b2275122ce7bc2f5055ab95550
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index e374f53..26b8f8e 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.6.47";
+ public static final String LABEL = "1.6.48";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 6e95cac..122e1c8 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -7,17 +7,15 @@
import static com.android.tools.r8.graph.ClassKind.LIBRARY;
import static com.android.tools.r8.graph.ClassKind.PROGRAM;
import static com.android.tools.r8.utils.ExceptionUtils.unwrapExecutionException;
-import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
import com.android.tools.r8.ClassFileResourceProvider;
-import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.DataResourceProvider;
+import com.android.tools.r8.Diagnostic;
import com.android.tools.r8.ProgramResource;
import com.android.tools.r8.ProgramResource.Kind;
import com.android.tools.r8.ProgramResourceProvider;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.StringResource;
-import com.android.tools.r8.Version;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.ClassKind;
import com.android.tools.r8.graph.DexApplication;
@@ -43,31 +41,21 @@
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
-import com.android.tools.r8.utils.ZipUtils;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.io.ByteStreams;
-import com.google.common.io.Closer;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
-import java.nio.file.OpenOption;
+import java.nio.file.Path;
import java.nio.file.Paths;
-import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Queue;
-import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
-import org.objectweb.asm.ClassVisitor;
public class ApplicationReader {
@@ -122,9 +110,30 @@
ProgramClassConflictResolver resolver)
throws IOException, ExecutionException {
assert verifyMainDexOptionsCompatible(inputApp, options);
+ Path dumpOutput = null;
+ boolean cleanDump = false;
if (options.dumpInputToFile != null) {
- dumpInputToFile();
- throw options.reporter.fatalError("Dumped compilation inputs to: " + options.dumpInputToFile);
+ dumpOutput = Paths.get(options.dumpInputToFile);
+ } else if (options.dumpInputToDirectory != null) {
+ dumpOutput =
+ Paths.get(options.dumpInputToDirectory).resolve("dump" + System.nanoTime() + ".zip");
+ } else if (options.testing.dumpAll) {
+ cleanDump = true;
+ dumpOutput = Paths.get("/tmp").resolve("dump" + System.nanoTime() + ".zip");
+ }
+ if (dumpOutput != null) {
+ timing.begin("ApplicationReader.dump");
+ dumpInputToFile(inputApp, dumpOutput, options);
+ if (cleanDump) {
+ Files.delete(dumpOutput);
+ }
+ timing.end();
+ Diagnostic message = new StringDiagnostic("Dumped compilation inputs to: " + dumpOutput);
+ if (options.dumpInputToFile != null) {
+ throw options.reporter.fatalError(message);
+ } else if (!cleanDump) {
+ options.reporter.info(message);
+ }
}
timing.begin("DexApplication.read");
final LazyLoadedDexApplication.Builder builder =
@@ -160,141 +169,8 @@
return builder.build();
}
- private void dumpInputToFile() throws IOException {
- try {
- List<ProgramResourceProvider> programResourceProviders =
- inputApp.getProgramResourceProviders();
- Set<DataEntryResource> dataEntryResources = inputApp.getDataEntryResourcesForTesting();
- List<ProgramResource> programResourcesWithDescriptors = new ArrayList<>();
- for (ProgramResourceProvider programResourceProvider : programResourceProviders) {
- addProgramResourcesWithDescriptor(
- programResourcesWithDescriptors, programResourceProvider.getProgramResources());
- }
-
- List<ProgramResource> libraryProgramResourcesWithDescriptors =
- getProgramResourcesWithDescriptors(inputApp.getLibraryResourceProviders());
-
- List<ProgramResource> classpathProgramResourcesWithDescriptors =
- getProgramResourcesWithDescriptors(inputApp.getClasspathResourceProviders());
-
- OpenOption[] openOptions =
- new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
- try (Closer closer = Closer.create()) {
- try (ZipOutputStream out =
- new ZipOutputStream(
- Files.newOutputStream(Paths.get(options.dumpInputToFile), openOptions))) {
- writeToZip(
- dataEntryResources, programResourcesWithDescriptors, closer, out, "program.jar");
- writeToZip(
- ImmutableSet.of(),
- libraryProgramResourcesWithDescriptors,
- closer,
- out,
- "library.jar");
- writeToZip(
- ImmutableSet.of(),
- classpathProgramResourcesWithDescriptors,
- closer,
- out,
- "classpath.jar");
- if (options.hasProguardConfiguration()) {
- String proguardConfig = options.getProguardConfiguration().getParsedConfiguration();
- ZipUtils.writeToZipStream(
- out, "proguard.config", proguardConfig.getBytes(), ZipEntry.DEFLATED);
- }
-
- ZipUtils.writeToZipStream(
- out, "r8-version", Version.getVersionString().getBytes(), ZipEntry.DEFLATED);
- }
- }
- } catch (ResourceException e) {
- options.reporter.fatalError("Failed to write input:" + e.getMessage());
- }
- }
-
- private static void writeToZip(
- Set<DataEntryResource> dataEntryResources,
- List<ProgramResource> programResourcesWithDescriptors,
- Closer closer,
- ZipOutputStream out,
- String entry)
- throws IOException, ResourceException {
- try (ByteArrayOutputStream programByteStream = new ByteArrayOutputStream()) {
- try (ZipOutputStream archiveOutputStream = new ZipOutputStream(programByteStream)) {
- ZipUtils.writeResourcesToZip(
- programResourcesWithDescriptors, dataEntryResources, closer, archiveOutputStream);
- }
- ZipUtils.writeToZipStream(out, entry, programByteStream.toByteArray(), ZipEntry.DEFLATED);
- }
- }
-
- private static List<ProgramResource> getProgramResourcesWithDescriptors(
- List<ClassFileResourceProvider> classFileResourceProviders)
- throws IOException, ResourceException {
- ArrayList<ProgramResource> programResourcesWithDescriptors = new ArrayList<>();
- for (ClassFileResourceProvider libraryResourceProvider : classFileResourceProviders) {
- List<ProgramResource> programResources = new ArrayList<>();
- for (String classDescriptor : libraryResourceProvider.getClassDescriptors()) {
- programResources.add(libraryResourceProvider.getProgramResource(classDescriptor));
- }
- addProgramResourcesWithDescriptor(programResourcesWithDescriptors, programResources);
- }
- return programResourcesWithDescriptors;
- }
-
- private static void addProgramResourcesWithDescriptor(
- List<ProgramResource> programResourcesWithDescriptors,
- Collection<ProgramResource> programResources)
- throws IOException, ResourceException {
- for (ProgramResource programResource : programResources) {
- if (programResource.getKind() != Kind.CF) {
- continue;
- }
- try (InputStream inputStream = programResource.getByteStream()) {
- byte[] bytes = ByteStreams.toByteArray(inputStream);
- String descriptor = extractClassInternalType(bytes);
- programResourcesWithDescriptors.add(
- ProgramResource.fromBytes(
- programResource.getOrigin(),
- programResource.getKind(),
- bytes,
- ImmutableSet.of(descriptor)));
- }
- }
- }
-
- private static String extractClassInternalType(byte[] bytes) throws IOException {
- class ClassNameExtractor extends ClassVisitor {
- private String className;
-
- private ClassNameExtractor() {
- super(ASM_VERSION);
- }
-
- @Override
- public void visit(
- int version,
- int access,
- String name,
- String signature,
- String superName,
- String[] interfaces) {
- className = name;
- }
-
- String getDescriptor() {
- return "L" + className + ";";
- }
- }
-
- org.objectweb.asm.ClassReader reader = new org.objectweb.asm.ClassReader(bytes);
- ClassNameExtractor extractor = new ClassNameExtractor();
- reader.accept(
- extractor,
- org.objectweb.asm.ClassReader.SKIP_CODE
- | org.objectweb.asm.ClassReader.SKIP_DEBUG
- | org.objectweb.asm.ClassReader.SKIP_FRAMES);
- return extractor.getDescriptor();
+ private static void dumpInputToFile(AndroidApp app, Path output, InternalOptions options) {
+ app.dump(output, options.getProguardConfiguration(), options.reporter);
}
private static boolean verifyMainDexOptionsCompatible(
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 1c3af7a..c4a37c2 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -7,6 +7,8 @@
import static com.android.tools.r8.utils.FileUtils.isArchive;
import static com.android.tools.r8.utils.FileUtils.isClassFile;
import static com.android.tools.r8.utils.FileUtils.isDexFile;
+import static com.android.tools.r8.utils.InternalOptions.ASM_VERSION;
+import static com.android.tools.r8.utils.ZipUtils.writeToZipStream;
import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.ClassFileResourceProvider;
@@ -25,28 +27,44 @@
import com.android.tools.r8.Resource;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.StringResource;
+import com.android.tools.r8.Version;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.errors.Unreachable;
+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.shaking.FilteredClassPath;
+import com.android.tools.r8.shaking.ProguardConfiguration;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
+import java.nio.file.OpenOption;
import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
+import java.util.function.Consumer;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+import org.objectweb.asm.ClassVisitor;
/**
* Collection of program files needed for processing.
@@ -55,6 +73,12 @@
*/
public class AndroidApp {
+ private static final String dumpVersionFileName = "r8-version";
+ private static final String dumpProgramFileName = "program.jar";
+ private static final String dumpClasspathFileName = "classpath.jar";
+ private static final String dumpLibraryFileName = "library.jar";
+ private static final String dumpConfigFileName = "proguard.config";
+
private final ImmutableList<ProgramResourceProvider> programResourceProviders;
private final ImmutableMap<Resource, String> programResourcesMainDescriptor;
private final ImmutableList<ClassFileResourceProvider> classpathResourceProviders;
@@ -382,6 +406,173 @@
return programResourcesMainDescriptor.get(resource);
}
+ public void dump(Path output, ProguardConfiguration configuration, Reporter reporter) {
+ int nextDexIndex = 0;
+ OpenOption[] openOptions =
+ new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
+ try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(output, openOptions))) {
+ writeToZipStream(
+ out, dumpVersionFileName, Version.getVersionString().getBytes(), ZipEntry.DEFLATED);
+ if (configuration != null) {
+ String proguardConfig = configuration.getParsedConfiguration();
+ writeToZipStream(out, dumpConfigFileName, proguardConfig.getBytes(), ZipEntry.DEFLATED);
+ }
+ nextDexIndex = dumpProgramResources(dumpProgramFileName, nextDexIndex, out);
+ nextDexIndex = dumpClasspathResources(nextDexIndex, out);
+ nextDexIndex = dumpLibraryResources(nextDexIndex, out);
+ } catch (IOException | ResourceException e) {
+ reporter.fatalError(new StringDiagnostic("Failed to dump inputs"), e);
+ }
+ }
+
+ private int dumpLibraryResources(int nextDexIndex, ZipOutputStream out)
+ throws IOException, ResourceException {
+ nextDexIndex =
+ dumpClassFileResources(dumpLibraryFileName, nextDexIndex, out, libraryResourceProviders);
+ return nextDexIndex;
+ }
+
+ private int dumpClasspathResources(int nextDexIndex, ZipOutputStream out)
+ throws IOException, ResourceException {
+ nextDexIndex =
+ dumpClassFileResources(
+ dumpClasspathFileName, nextDexIndex, out, classpathResourceProviders);
+ return nextDexIndex;
+ }
+
+ private static ClassFileResourceProvider createClassFileResourceProvider(
+ Map<String, ProgramResource> classPathResources) {
+ return new ClassFileResourceProvider() {
+ @Override
+ public Set<String> getClassDescriptors() {
+ return classPathResources.keySet();
+ }
+
+ @Override
+ public ProgramResource getProgramResource(String descriptor) {
+ return classPathResources.get(descriptor);
+ }
+ };
+ }
+
+ private Consumer<ProgramResource> createClassFileResourceConsumer(
+ Map<String, ProgramResource> classPathResources) {
+ return programResource -> {
+ assert programResource.getClassDescriptors().size() == 1;
+ String descriptor = programResource.getClassDescriptors().iterator().next();
+ classPathResources.put(descriptor, programResource);
+ };
+ }
+
+ private int dumpProgramResources(String archiveName, int nextDexIndex, ZipOutputStream out)
+ throws IOException, ResourceException {
+ try (ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream()) {
+ try (ZipOutputStream archiveOutputStream = new ZipOutputStream(archiveByteStream)) {
+ Object2IntMap<String> seen = new Object2IntOpenHashMap<>();
+ Set<DataEntryResource> dataEntries = getDataEntryResourcesForTesting();
+ for (DataEntryResource dataResource : dataEntries) {
+ String entryName = dataResource.getName();
+ try (InputStream dataStream = dataResource.getByteStream()) {
+ byte[] bytes = ByteStreams.toByteArray(dataStream);
+ writeToZipStream(archiveOutputStream, entryName, bytes, ZipEntry.DEFLATED);
+ }
+ }
+ for (ProgramResourceProvider provider : programResourceProviders) {
+ for (ProgramResource programResource : provider.getProgramResources()) {
+ nextDexIndex =
+ dumpProgramResource(seen, nextDexIndex, archiveOutputStream, programResource);
+ }
+ }
+ }
+ writeToZipStream(out, archiveName, archiveByteStream.toByteArray(), ZipEntry.DEFLATED);
+ }
+ return nextDexIndex;
+ }
+
+ private static int dumpClassFileResources(
+ String archiveName,
+ int nextDexIndex,
+ ZipOutputStream out,
+ ImmutableList<ClassFileResourceProvider> classpathResourceProviders)
+ throws IOException, ResourceException {
+ try (ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream()) {
+ try (ZipOutputStream archiveOutputStream = new ZipOutputStream(archiveByteStream)) {
+ Object2IntMap<String> seen = new Object2IntOpenHashMap<>();
+ for (ClassFileResourceProvider provider : classpathResourceProviders) {
+ for (String descriptor : provider.getClassDescriptors()) {
+ ProgramResource programResource = provider.getProgramResource(descriptor);
+ int oldDexIndex = nextDexIndex;
+ nextDexIndex =
+ dumpProgramResource(seen, nextDexIndex, archiveOutputStream, programResource);
+ assert nextDexIndex == oldDexIndex;
+ }
+ }
+ }
+ writeToZipStream(out, archiveName, archiveByteStream.toByteArray(), ZipEntry.DEFLATED);
+ }
+ return nextDexIndex;
+ }
+
+ private static int dumpProgramResource(
+ Object2IntMap<String> seen,
+ int nextDexIndex,
+ ZipOutputStream archiveOutputStream,
+ ProgramResource programResource)
+ throws ResourceException, IOException {
+ byte[] bytes = ByteStreams.toByteArray(programResource.getByteStream());
+ String entryName;
+ if (programResource.getKind() == Kind.CF) {
+ Set<String> classDescriptors = programResource.getClassDescriptors();
+ String classDescriptor =
+ (classDescriptors == null || classDescriptors.size() != 1)
+ ? extractClassDescriptor(bytes)
+ : classDescriptors.iterator().next();
+ String classFileName = DescriptorUtils.getClassFileName(classDescriptor);
+ int dupCount = seen.getOrDefault(classDescriptor, 0);
+ seen.put(classDescriptor, dupCount + 1);
+ entryName = dupCount == 0 ? classFileName : (classFileName + "." + dupCount + ".dup");
+ } else {
+ assert programResource.getKind() == Kind.DEX;
+ entryName = "classes" + nextDexIndex++ + ".dex";
+ }
+ writeToZipStream(archiveOutputStream, entryName, bytes, ZipEntry.DEFLATED);
+ return nextDexIndex;
+ }
+
+ private static String extractClassDescriptor(byte[] bytes) {
+ class ClassNameExtractor extends ClassVisitor {
+ private String className;
+
+ private ClassNameExtractor() {
+ super(ASM_VERSION);
+ }
+
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ className = name;
+ }
+
+ String getDescriptor() {
+ return "L" + className + ";";
+ }
+ }
+
+ org.objectweb.asm.ClassReader reader = new org.objectweb.asm.ClassReader(bytes);
+ ClassNameExtractor extractor = new ClassNameExtractor();
+ reader.accept(
+ extractor,
+ org.objectweb.asm.ClassReader.SKIP_CODE
+ | org.objectweb.asm.ClassReader.SKIP_DEBUG
+ | org.objectweb.asm.ClassReader.SKIP_FRAMES);
+ return extractor.getDescriptor();
+ }
+
/**
* Builder interface for constructing an AndroidApp.
*/
@@ -424,6 +615,118 @@
return reporter;
}
+ public Builder addDump(Path dumpFile) throws IOException {
+ System.out.println("Reading dump from file: " + dumpFile);
+ Origin origin = new PathOrigin(dumpFile);
+ ZipUtils.iter(
+ dumpFile.toString(),
+ (entry, input) -> {
+ String name = entry.getName();
+ if (name.equals(dumpVersionFileName)) {
+ String content = new String(ByteStreams.toByteArray(input), StandardCharsets.UTF_8);
+ System.out.println("Dump produced by R8 version: " + content);
+ } else if (name.equals(dumpProgramFileName)) {
+ readProgramDump(origin, input);
+ } else if (name.equals(dumpClasspathFileName)) {
+ readClassFileDump(origin, input, this::addClasspathResourceProvider, "classpath");
+ } else if (name.equals(dumpLibraryFileName)) {
+ readClassFileDump(origin, input, this::addLibraryResourceProvider, "library");
+ } else {
+ System.out.println("WARNING: Unexpected dump file entry: " + entry.getName());
+ }
+ });
+ return this;
+ }
+
+ private void readClassFileDump(
+ Origin origin,
+ InputStream input,
+ Consumer<ClassFileResourceProvider> addProvider,
+ String inputType)
+ throws IOException {
+ Map<String, ProgramResource> resources = new HashMap<>();
+ try (ZipInputStream stream = new ZipInputStream(input)) {
+ ZipEntry entry;
+ while (null != (entry = stream.getNextEntry())) {
+ String name = entry.getName();
+ if (ZipUtils.isClassFile(name)) {
+ Origin entryOrigin = new ArchiveEntryOrigin(name, origin);
+ String descriptor = DescriptorUtils.guessTypeDescriptor(name);
+ ProgramResource resource =
+ OneShotByteResource.create(
+ Kind.CF,
+ entryOrigin,
+ ByteStreams.toByteArray(stream),
+ Collections.singleton(descriptor));
+ resources.put(descriptor, resource);
+ } else if (name.endsWith(".dup")) {
+ System.out.println("WARNING: Duplicate " + inputType + " resource: " + name);
+ } else {
+ System.out.println("WARNING: Unexpected " + inputType + " resource: " + name);
+ }
+ }
+ }
+ if (!resources.isEmpty()) {
+ addProvider.accept(createClassFileResourceProvider(resources));
+ }
+ }
+
+ private void readProgramDump(Origin origin, InputStream input) throws IOException {
+ List<ProgramResource> programResources = new ArrayList<>();
+ List<DataEntryResource> dataResources = new ArrayList<>();
+ try (ZipInputStream stream = new ZipInputStream(input)) {
+ ZipEntry entry;
+ while (null != (entry = stream.getNextEntry())) {
+ String name = entry.getName();
+ if (ZipUtils.isClassFile(name)) {
+ Origin entryOrigin = new ArchiveEntryOrigin(name, origin);
+ String descriptor = DescriptorUtils.guessTypeDescriptor(name);
+ ProgramResource resource =
+ OneShotByteResource.create(
+ Kind.CF,
+ entryOrigin,
+ ByteStreams.toByteArray(stream),
+ Collections.singleton(descriptor));
+ programResources.add(resource);
+ } else if (ZipUtils.isDexFile(name)) {
+ Origin entryOrigin = new ArchiveEntryOrigin(name, origin);
+ ProgramResource resource =
+ OneShotByteResource.create(
+ Kind.DEX, entryOrigin, ByteStreams.toByteArray(stream), null);
+ programResources.add(resource);
+ } else if (name.endsWith(".dup")) {
+ System.out.println("WARNING: Duplicate program resource: " + name);
+ } else {
+ dataResources.add(
+ DataEntryResource.fromBytes(ByteStreams.toByteArray(stream), name, origin));
+ }
+ }
+ }
+ if (!programResources.isEmpty() || !dataResources.isEmpty()) {
+ addProgramResourceProvider(
+ new ProgramResourceProvider() {
+ @Override
+ public Collection<ProgramResource> getProgramResources() throws ResourceException {
+ return programResources;
+ }
+
+ @Override
+ public DataResourceProvider getDataResourceProvider() {
+ return dataResources.isEmpty()
+ ? null
+ : new DataResourceProvider() {
+ @Override
+ public void accept(Visitor visitor) throws ResourceException {
+ for (DataEntryResource dataResource : dataResources) {
+ visitor.visit(dataResource);
+ }
+ }
+ };
+ }
+ });
+ }
+ }
+
/** Add program file resources. */
public Builder addProgramFiles(Path... files) {
return addProgramFiles(Arrays.asList(files));
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 8e93b77..218baef 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -174,6 +174,8 @@
public boolean printMemory = System.getProperty("com.android.tools.r8.printmemory") != null;
public String dumpInputToFile = System.getProperty("com.android.tools.r8.dumpinputtofile");
+ public String dumpInputToDirectory =
+ System.getProperty("com.android.tools.r8.dumpinputtodirectory");
// Flag to toggle if DEX code objects should pass-through without IR processing.
public boolean passthroughDexCode = false;
@@ -965,6 +967,10 @@
// Flag to turn on/off JDK11+ nest-access control even when not required (Cf backend)
public boolean enableForceNestBasedAccessDesugaringForTest = false;
+ // Force each call of application read to dump its inputs to a file, which is subsequently
+ // deleted. Useful to check that our dump functionality does not cause compilation failure.
+ public boolean dumpAll = false;
+
public boolean desugarLambdasThroughLensCodeRewriter() {
return enableStatefulLambdaCreateInstanceMethod;
}
diff --git a/src/main/java/com/android/tools/r8/utils/OneShotByteResource.java b/src/main/java/com/android/tools/r8/utils/OneShotByteResource.java
index da958c3..1dfbc64 100644
--- a/src/main/java/com/android/tools/r8/utils/OneShotByteResource.java
+++ b/src/main/java/com/android/tools/r8/utils/OneShotByteResource.java
@@ -17,7 +17,7 @@
private byte[] bytes;
private final Set<String> classDescriptors;
- static ProgramResource create(
+ public static OneShotByteResource create(
Kind kind, Origin origin, byte[] bytes, Set<String> classDescriptors) {
return new OneShotByteResource(origin, kind, bytes, classDescriptors);
}
diff --git a/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
new file mode 100644
index 0000000..04c4bf2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/AndroidAppDumpsTest.java
@@ -0,0 +1,183 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.DataResourceProvider.Visitor;
+import com.android.tools.r8.ProgramResource.Kind;
+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.Box;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.Reporter;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class AndroidAppDumpsTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ public AndroidAppDumpsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ Reporter reporter = new Reporter();
+
+ String dataResourceName = "my-resource.bin";
+ byte[] dataResourceData = new byte[] {1, 2, 3};
+
+ Path dexForB =
+ testForD8().addProgramClasses(B.class).setMinApi(AndroidApiLevel.B).compile().writeToZip();
+
+ AndroidApp appIn =
+ AndroidApp.builder(reporter)
+ .addClassProgramData(ToolHelper.getClassAsBytes(A.class), origin("A"))
+ .addClassProgramData(ToolHelper.getClassAsBytes(A.class), origin("A"))
+ .addClassProgramData(ToolHelper.getClassAsBytes(A.class), origin("A"))
+ .addProgramFile(dexForB)
+ .addClasspathResourceProvider(provider(B.class))
+ .addClasspathResourceProvider(provider(B.class))
+ .addClasspathResourceProvider(provider(B.class))
+ .addLibraryResourceProvider(provider(C.class))
+ .addLibraryResourceProvider(provider(C.class))
+ .addLibraryResourceProvider(provider(C.class))
+ .addProgramResourceProvider(
+ createDataResourceProvider(
+ dataResourceName, dataResourceData, origin(dataResourceName)))
+ .build();
+
+ Path dumpFile = temp.newFolder().toPath().resolve("dump.zip");
+ appIn.dump(dumpFile, null, reporter);
+
+ AndroidApp appOut = AndroidApp.builder(reporter).addDump(dumpFile).build();
+ assertEquals(1, appOut.getClassProgramResourcesForTesting().size());
+ assertEquals(
+ DescriptorUtils.javaTypeToDescriptor(A.class.getTypeName()),
+ appOut.getClassProgramResourcesForTesting().get(0).getClassDescriptors().iterator().next());
+
+ assertEquals(1, appOut.getDexProgramResourcesForTesting().size());
+
+ assertEquals(1, appOut.getClasspathResourceProviders().size());
+ assertEquals(
+ DescriptorUtils.javaTypeToDescriptor(B.class.getTypeName()),
+ appOut.getClasspathResourceProviders().get(0).getClassDescriptors().iterator().next());
+
+ assertEquals(1, appOut.getLibraryResourceProviders().size());
+ assertEquals(
+ DescriptorUtils.javaTypeToDescriptor(C.class.getTypeName()),
+ appOut.getLibraryResourceProviders().get(0).getClassDescriptors().iterator().next());
+
+ Box<Boolean> foundData = new Box<>(false);
+ for (ProgramResourceProvider provider : appOut.getProgramResourceProviders()) {
+ DataResourceProvider dataProvider = provider.getDataResourceProvider();
+ if (dataProvider != null) {
+ dataProvider.accept(
+ new Visitor() {
+ @Override
+ public void visit(DataDirectoryResource directory) {}
+
+ @Override
+ public void visit(DataEntryResource file) {
+ if (file.getName().equals(dataResourceName)) {
+ foundData.set(true);
+ }
+ }
+ });
+ }
+ }
+ assertTrue(foundData.get());
+ }
+
+ private ProgramResourceProvider createDataResourceProvider(
+ String name, byte[] content, Origin origin) {
+ return new ProgramResourceProvider() {
+ @Override
+ public Collection<ProgramResource> getProgramResources() throws ResourceException {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public DataResourceProvider getDataResourceProvider() {
+ return new DataResourceProvider() {
+ @Override
+ public void accept(Visitor visitor) throws ResourceException {
+ visitor.visit(
+ new DataEntryResource() {
+ @Override
+ public InputStream getByteStream() throws ResourceException {
+ return new ByteArrayInputStream(content);
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public Origin getOrigin() {
+ return origin;
+ }
+ });
+ }
+ };
+ }
+ };
+ }
+
+ private Origin origin(String name) {
+ return new Origin(Origin.root()) {
+ @Override
+ public String part() {
+ return name;
+ }
+ };
+ }
+
+ private ClassFileResourceProvider provider(Class<?> clazz) {
+ return new ClassFileResourceProvider() {
+ @Override
+ public Set<String> getClassDescriptors() {
+ return Collections.singleton(DescriptorUtils.javaTypeToDescriptor(clazz.getTypeName()));
+ }
+
+ @Override
+ public ProgramResource getProgramResource(String descriptor) {
+ try {
+ return ProgramResource.fromBytes(
+ origin(clazz.getTypeName()),
+ Kind.CF,
+ ToolHelper.getClassAsBytes(clazz),
+ getClassDescriptors());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+ }
+
+ public static class A {}
+
+ public static class B {}
+
+ public static class C {}
+}
diff --git a/src/test/java/com/android/tools/r8/DumpInputsTest.java b/src/test/java/com/android/tools/r8/DumpInputsTest.java
new file mode 100644
index 0000000..b8b3a04
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/DumpInputsTest.java
@@ -0,0 +1,109 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DumpInputsTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("Hello, world");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withCfRuntime(CfVm.JDK9).build();
+ }
+
+ public DumpInputsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testDumpToFile() throws Exception {
+ Path dump = temp.newFolder().toPath().resolve("dump.zip");
+ try {
+ testForExternalR8(parameters.getBackend())
+ .useExternalJDK(parameters.getRuntime().asCf().getVm())
+ .addJvmFlag("-Dcom.android.tools.r8.dumpinputtofile=" + dump)
+ .addProgramClasses(TestClass.class)
+ .compile();
+ } catch (AssertionError e) {
+ verifyDump(dump, false, true);
+ return;
+ }
+ fail("Expected external compilation to exit");
+ }
+
+ @Test
+ public void testDumpToDirectory() throws Exception {
+ Path dumpDir = temp.newFolder().toPath();
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ // Setting a directory will allow compilation to continue.
+ // Ensure the compilation and run can actually succeed.
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+ .addKeepMainRule(TestClass.class)
+ .addOptionsModification(options -> options.dumpInputToDirectory = dumpDir.toString())
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello, world");
+ assertTrue(Files.isDirectory(dumpDir));
+ List<Path> paths = Files.walk(dumpDir, 1).collect(Collectors.toList());
+ boolean hasVerified = false;
+ for (Path path : paths) {
+ if (!path.equals(dumpDir)) {
+ // The non-external run here results in assert code calling application read.
+ verifyDump(path, false, false);
+ hasVerified = true;
+ }
+ }
+ assertTrue(hasVerified);
+ }
+
+ private void verifyDump(Path dumpFile, boolean hasClasspath, boolean hasProguardConfig)
+ throws IOException {
+ assertTrue(Files.exists(dumpFile));
+ Path unzipped = temp.newFolder().toPath();
+ ZipUtils.unzip(dumpFile.toString(), unzipped.toFile());
+ assertTrue(Files.exists(unzipped.resolve("r8-version")));
+ assertTrue(Files.exists(unzipped.resolve("program.jar")));
+ assertTrue(Files.exists(unzipped.resolve("library.jar")));
+ if (hasClasspath) {
+ assertTrue(Files.exists(unzipped.resolve("classpath.jar")));
+ }
+ if (hasProguardConfig) {
+ assertTrue(Files.exists(unzipped.resolve("proguard.config")));
+ }
+ Set<String> entries = new HashSet<>();
+ ZipUtils.iter(
+ unzipped.resolve("program.jar").toString(), (entry, input) -> entries.add(entry.getName()));
+ assertTrue(
+ entries.contains(
+ DescriptorUtils.getClassFileName(
+ DescriptorUtils.javaTypeToDescriptor(TestClass.class.getTypeName()))));
+ }
+
+ static class TestClass {
+ public static void main(String[] args) {
+ System.out.println("Hello, world");
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
index 0328184..ae4a36c 100644
--- a/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/ExternalR8TestBuilder.java
@@ -57,6 +57,8 @@
private boolean addR8ExternalDeps = false;
+ private List<String> jvmFlags = new ArrayList<>();
+
private ExternalR8TestBuilder(TestState state, Builder builder, Backend backend) {
super(state, builder, backend);
}
@@ -72,7 +74,7 @@
public ExternalR8TestBuilder useExternalJDK(CfVm externalJDK) {
this.externalJDK = externalJDK;
- return this;
+ return self();
}
private String getJDKToRun() {
@@ -82,6 +84,11 @@
return getJavaExecutable(externalJDK);
}
+ public ExternalR8TestBuilder addJvmFlag(String flag) {
+ jvmFlags.add(flag);
+ return self();
+ }
+
@Override
ExternalR8TestCompileResult internalCompile(
Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
@@ -99,9 +106,12 @@
: r8jar.toAbsolutePath().toString();
List<String> command = new ArrayList<>();
+ Collections.addAll(command, getJDKToRun());
+
+ command.addAll(jvmFlags);
+
Collections.addAll(
command,
- getJDKToRun(),
"-ea",
"-cp",
classPath,
diff --git a/src/test/java/com/android/tools/r8/R8EntryPointTests.java b/src/test/java/com/android/tools/r8/R8EntryPointTests.java
index 553ff0e..0bbc7bc 100644
--- a/src/test/java/com/android/tools/r8/R8EntryPointTests.java
+++ b/src/test/java/com/android/tools/r8/R8EntryPointTests.java
@@ -9,20 +9,13 @@
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.ZipUtils;
-import com.android.tools.r8.utils.ZipUtils.OnEntryHandler;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
-import java.io.InputStream;
import java.nio.file.Files;
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.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.zip.ZipEntry;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -180,53 +173,6 @@
}
@Test
- public void testDumpInputs() throws IOException {
- Path out = temp.newFile("dex.zip").toPath();
- Path dump = temp.newFile("dump.zip").toPath();
- ProcessResult r8 =
- ToolHelper.forkR8WithJavaOptions(
- Paths.get("."),
- ImmutableList.of("-Dcom.android.tools.r8.dumpinputtofile=" + dump.toString()),
- "--lib",
- ToolHelper.getDefaultAndroidJar().toString(),
- "--dex",
- "--output",
- out.toString(),
- "--pg-conf",
- PROGUARD_FLAGS.toString(),
- "--pg-conf",
- testFlags.toString(),
- INPUT_JAR.toString());
-
- List<ZipEntry> entries = new ArrayList<>();
- ZipUtils.iter(
- dump.toString(),
- new OnEntryHandler() {
- @Override
- public void onEntry(ZipEntry entry, InputStream input) throws IOException {
- entries.add(entry);
- }
- });
- Assert.assertTrue(hasEntry(entries, "program.jar"));
- Assert.assertTrue(hasEntry(entries, "library.jar"));
- Assert.assertTrue(hasEntry(entries, "classpath.jar"));
- Assert.assertTrue(hasEntry(entries, "proguard.config"));
- Assert.assertTrue(hasEntry(entries, "r8-version"));
- // When dumping the inputs we throw an error in the program to exit early.
- Assert.assertNotEquals(0, r8.exitCode);
- }
-
- private boolean hasEntry(Collection<ZipEntry> entries, String name) {
- for (ZipEntry entry : entries) {
- if (entry.getName().equals(name)) {
- return true;
- }
- }
-
- return false;
- }
-
- @Test
public void testSpecifyDexAndClassfileNotAllowed() throws IOException, InterruptedException {
Path out = temp.newFile("dex.zip").toPath();
ProcessResult r8 =
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 99b2271..26f2030 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -98,9 +98,7 @@
}
private ClassBuilder(String name, String superName) {
- this.name = name;
- this.superName = superName;
- this.interfaces = ImmutableList.of();
+ this(name , superName, new String[0]);
}
private ClassBuilder(String name, String superName, String... interfaces) {