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) {