Version 1.6.44

Cherry pick: Allow R8 to dump all of the input to a zipfile
CL: https://r8-review.googlesource.com/c/r8/+/44880

Cherry pick: Fix dumping of input files
CL: https://r8-review.googlesource.com/c/r8/+/44888

Cherry pick: Dump all input when dumpInputToFile is set
CL: https://r8-review.googlesource.com/c/r8/+/45006

Change-Id: I62eb307daa1f0896a860aa4b806e84fad14a55f7
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumer.java b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
index e124571..18f68ba 100644
--- a/src/main/java/com/android/tools/r8/ClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
@@ -3,14 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
-import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
-
 import com.android.tools.r8.utils.ArchiveBuilder;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.DirectoryBuilder;
 import com.android.tools.r8.utils.OutputBuilder;
 import com.android.tools.r8.utils.ZipUtils;
-import com.google.common.io.ByteStreams;
 import com.google.common.io.Closer;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -19,7 +16,6 @@
 import java.nio.file.StandardOpenOption;
 import java.util.List;
 import java.util.Set;
-import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
 /**
@@ -122,7 +118,7 @@
     @Override
     public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
       super.accept(data, descriptor, handler);
-      outputBuilder.addFile(getClassFileName(descriptor), data, handler);
+      outputBuilder.addFile(DescriptorUtils.getClassFileName(descriptor), data, handler);
     }
 
     @Override
@@ -146,11 +142,6 @@
       return outputBuilder.getPath();
     }
 
-    private static String getClassFileName(String classDescriptor) {
-      assert classDescriptor != null && DescriptorUtils.isClassDescriptor(classDescriptor);
-      return DescriptorUtils.getClassBinaryNameFromDescriptor(classDescriptor) + CLASS_EXTENSION;
-    }
-
     public static void writeResources(
         Path archive, List<ProgramResource> resources, Set<DataEntryResource> dataResources)
         throws IOException, ResourceException {
@@ -158,21 +149,11 @@
           new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
       try (Closer closer = Closer.create()) {
         try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(archive, options))) {
-          for (ProgramResource resource : resources) {
-            assert resource.getClassDescriptors().size() == 1;
-            String className = resource.getClassDescriptors().iterator().next();
-            String entryName = getClassFileName(className);
-            byte[] bytes = ByteStreams.toByteArray(closer.register(resource.getByteStream()));
-            ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
-          }
-          for (DataEntryResource dataResource : dataResources) {
-            String entryName = dataResource.getName();
-            byte[] bytes = ByteStreams.toByteArray(closer.register(dataResource.getByteStream()));
-            ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
-          }
+          ZipUtils.writeResourcesToZip(resources, dataResources, closer, out);
         }
       }
     }
+
   }
 
   /** Directory consumer to write program resources to a directory. */
@@ -208,7 +189,7 @@
     @Override
     public void accept(ByteDataView data, String descriptor, DiagnosticsHandler handler) {
       super.accept(data, descriptor, handler);
-      outputBuilder.addFile(ArchiveConsumer.getClassFileName(descriptor), data, handler);
+      outputBuilder.addFile(DescriptorUtils.getClassFileName(descriptor), data, handler);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index ff5752e..1315918 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.43";
+  public static final String LABEL = "1.6.44";
 
   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 ae946fc..6e95cac 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -7,14 +7,17 @@
 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.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;
@@ -40,18 +43,31 @@
 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.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 {
 
@@ -106,6 +122,10 @@
       ProgramClassConflictResolver resolver)
       throws IOException, ExecutionException {
     assert verifyMainDexOptionsCompatible(inputApp, options);
+    if (options.dumpInputToFile != null) {
+      dumpInputToFile();
+      throw options.reporter.fatalError("Dumped compilation inputs to: " + options.dumpInputToFile);
+    }
     timing.begin("DexApplication.read");
     final LazyLoadedDexApplication.Builder builder =
         DexApplication.builder(options, timing, resolver);
@@ -140,6 +160,143 @@
     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 boolean verifyMainDexOptionsCompatible(
       AndroidApp inputApp, InternalOptions options) {
     if (!options.isGeneratingDex()) {
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index ba9af9d..af056a8 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -444,4 +444,9 @@
     assert isValidJavaType(typeName);
     return typeName.replace(JAVA_PACKAGE_SEPARATOR, DESCRIPTOR_PACKAGE_SEPARATOR) + ".class";
   }
+
+  public static String getClassFileName(String classDescriptor) {
+    assert classDescriptor != null && isClassDescriptor(classDescriptor);
+    return getClassBinaryNameFromDescriptor(classDescriptor) + CLASS_EXTENSION;
+  }
 }
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 faf5a9e..8e93b77 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -66,7 +66,6 @@
   // Set to true to run compilation in a single thread and without randomly shuffling the input.
   // This makes life easier when running R8 in a debugger.
   public static final boolean DETERMINISTIC_DEBUGGING = false;
-
   public enum LineNumberOptimization {
     OFF,
     ON
@@ -174,6 +173,8 @@
   // To print memory one also have to enable printtimes.
   public boolean printMemory = System.getProperty("com.android.tools.r8.printmemory") != null;
 
+  public String dumpInputToFile = System.getProperty("com.android.tools.r8.dumpinputtofile");
+
   // Flag to toggle if DEX code objects should pass-through without IR processing.
   public boolean passthroughDexCode = false;
   // TODO(b/134705306): Currently allow merging dex files resulting from Java 8 library
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
index da87e2b..89ef8f3 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -8,8 +8,12 @@
 import static com.android.tools.r8.utils.FileUtils.MODULE_INFO_CLASS;
 
 import com.android.tools.r8.ByteDataView;
+import com.android.tools.r8.DataEntryResource;
+import com.android.tools.r8.ProgramResource;
+import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.errors.CompilationError;
 import com.google.common.io.ByteStreams;
+import com.google.common.io.Closer;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -19,7 +23,9 @@
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Enumeration;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Predicate;
 import java.util.zip.CRC32;
 import java.util.zip.ZipEntry;
@@ -28,6 +34,27 @@
 
 public class ZipUtils {
 
+  public static void writeResourcesToZip(
+      List<ProgramResource> resources,
+      Set<DataEntryResource> dataResources,
+      Closer closer,
+      ZipOutputStream out)
+      throws IOException, ResourceException {
+    for (ProgramResource resource : resources) {
+      assert resource.getClassDescriptors().size() == 1;
+      Iterator<String> iterator = resource.getClassDescriptors().iterator();
+      String className = iterator.next();
+      String entryName = DescriptorUtils.getClassFileName(className);
+      byte[] bytes = ByteStreams.toByteArray(closer.register(resource.getByteStream()));
+      writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
+    }
+    for (DataEntryResource dataResource : dataResources) {
+      String entryName = dataResource.getName();
+      byte[] bytes = ByteStreams.toByteArray(closer.register(dataResource.getByteStream()));
+      writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
+    }
+  }
+
   public interface OnEntryHandler {
     void onEntry(ZipEntry entry, InputStream input) throws IOException;
   }
diff --git a/src/test/java/com/android/tools/r8/R8EntryPointTests.java b/src/test/java/com/android/tools/r8/R8EntryPointTests.java
index 0bbc7bc..553ff0e 100644
--- a/src/test/java/com/android/tools/r8/R8EntryPointTests.java
+++ b/src/test/java/com/android/tools/r8/R8EntryPointTests.java
@@ -9,13 +9,20 @@
 
 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;
@@ -173,6 +180,53 @@
   }
 
   @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/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 2819cc7..141e3fb 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -1329,6 +1329,12 @@
     return forkJava(dir, R8.class, args);
   }
 
+  public static ProcessResult forkR8WithJavaOptions(
+      Path dir, List<String> javaOptions, String... args) throws IOException {
+    String r8Jar = R8_JAR.toAbsolutePath().toString();
+    return forkJavaWithJarAndJavaOptions(dir, r8Jar, Arrays.asList(args), javaOptions);
+  }
+
   public static ProcessResult forkR8Jar(Path dir, String... args)
       throws IOException, InterruptedException {
     String r8Jar = R8_JAR.toAbsolutePath().toString();
@@ -1355,14 +1361,23 @@
 
   private static ProcessResult forkJavaWithJar(Path dir, String jarPath, List<String> args)
       throws IOException {
-    List<String> command = new ImmutableList.Builder<String>()
-        .add(getJavaExecutable())
-        .add("-jar").add(jarPath)
-        .addAll(args)
-        .build();
+    return forkJavaWithJarAndJavaOptions(dir, jarPath, args, ImmutableList.of());
+  }
+
+  private static ProcessResult forkJavaWithJarAndJavaOptions(
+      Path dir, String jarPath, List<String> args, List<String> javaOptions) throws IOException {
+    List<String> command =
+        new ImmutableList.Builder<String>()
+            .add(getJavaExecutable())
+            .addAll(javaOptions)
+            .add("-jar")
+            .add(jarPath)
+            .addAll(args)
+            .build();
     return runProcess(new ProcessBuilder(command).directory(dir.toFile()));
   }
 
+
   private static ProcessResult forkJava(Path dir, Class clazz, List<String> args)
       throws IOException {
     List<String> command = new ImmutableList.Builder<String>()