Merge "Make the D8 command line allow diplicate classes for classpath"
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index 9b1599f..7b17ec8 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -14,7 +14,6 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -33,6 +32,7 @@
       private Path proguardMapFile = null;
       private boolean useSmali = false;
       private boolean allInfo = false;
+      private boolean useIr;
 
       @Override
       Builder self() {
@@ -63,6 +63,11 @@
         return this;
       }
 
+      public Builder setUseIr(boolean useIr) {
+        this.useIr = useIr;
+        return this;
+      }
+
       @Override
       protected DisassembleCommand makeCommand() {
         // If printing versions ignore everything else.
@@ -74,24 +79,26 @@
             getOutputPath(),
             proguardMapFile == null ? null : StringResource.fromFile(proguardMapFile),
             allInfo,
-            useSmali);
+            useSmali,
+            useIr);
       }
     }
 
-    static final String USAGE_MESSAGE = String.join("\n", ImmutableList.of(
-        "Usage: disasm [options] <input-files>",
-        " where <input-files> are dex files",
-        " and options are:",
-        "  --all                   # Include all information in disassembly.",
-        "  --smali                 # Disassemble using smali syntax.",
-        "  --pg-map <file>         # Proguard map <file> for mapping names.",
-        "  --output                # Specify a file or directory to write to.",
-        "  --version               # Print the version of r8.",
-        "  --help                  # Print this message."));
-
+    static final String USAGE_MESSAGE =
+        "Usage: disasm [options] <input-files>\n"
+            + " where <input-files> are dex files\n"
+            + " and options are:\n"
+            + "  --all                   # Include all information in disassembly.\n"
+            + "  --smali                 # Disassemble using smali syntax.\n"
+            + "  --ir                    # Print IR before and after optimization.\n"
+            + "  --pg-map <file>         # Proguard map <file> for mapping names.\n"
+            + "  --output                # Specify a file or directory to write to.\n"
+            + "  --version               # Print the version of r8.\n"
+            + "  --help                  # Print this message.";
 
     private final boolean allInfo;
     private final boolean useSmali;
+    private final boolean useIr;
 
     public static Builder builder() {
       return new Builder();
@@ -112,10 +119,12 @@
           builder.setPrintHelp(true);
         } else if (arg.equals("--version")) {
           builder.setPrintVersion(true);
-          } else if (arg.equals("--all")) {
+        } else if (arg.equals("--all")) {
           builder.setAllInfo(true);
         } else if (arg.equals("--smali")) {
           builder.setUseSmali(true);
+        } else if (arg.equals("--ir")) {
+          builder.setUseIr(true);
         } else if (arg.equals("--pg-map")) {
           builder.setProguardMapFile(Paths.get(args[++i]));
         } else if (arg.equals("--output")) {
@@ -132,13 +141,18 @@
     }
 
     private DisassembleCommand(
-        AndroidApp inputApp, Path outputPath, StringResource proguardMap,
-        boolean allInfo, boolean useSmali) {
+        AndroidApp inputApp,
+        Path outputPath,
+        StringResource proguardMap,
+        boolean allInfo,
+        boolean useSmali,
+        boolean useIr) {
       super(inputApp);
       this.outputPath = outputPath;
       this.proguardMap = proguardMap;
       this.allInfo = allInfo;
       this.useSmali = useSmali;
+      this.useIr = useIr;
     }
 
     private DisassembleCommand(boolean printHelp, boolean printVersion) {
@@ -147,6 +161,7 @@
       proguardMap = null;
       allInfo = false;
       useSmali = false;
+      useIr = false;
     }
 
     public Path getOutputPath() {
@@ -157,6 +172,10 @@
       return useSmali;
     }
 
+    public boolean useIr() {
+      return useIr;
+    }
+
     @Override
     InternalOptions getInternalOptions() {
       InternalOptions internal = new InternalOptions();
@@ -190,9 +209,10 @@
     try {
       DexApplication application =
           new ApplicationReader(app, options, timing).read(command.proguardMap, executor);
-      DexByteCodeWriter writer = command.useSmali()
-          ? new SmaliWriter(application, options)
-          : new AssemblyWriter(application, options, command.allInfo);
+      DexByteCodeWriter writer =
+          command.useSmali()
+              ? new SmaliWriter(application, options)
+              : new AssemblyWriter(application, options, command.allInfo, command.useIr());
       if (command.getOutputPath() != null) {
         writer.write(command.getOutputPath());
       } else {
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index b1bc7ca..782c91d 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -20,17 +20,32 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
+/**
+ * PrintSeeds prints the classes, interfaces, methods and fields selected by a given ProGuard
+ * configuration &lt;pg-conf.txt&gt; when compiling a given program &lt;r8.jar&gt; alongside a given
+ * library &lt;rt.jar&gt;.
+ *
+ * <p>The output format is identical to what is printed when {@code -printseeds} is specified in
+ * &lt;pg-conf.txt&gt;, but running PrintSeeds can be faster than running R8 with {@code
+ * -printseeds}. See also the {@link PrintUses} program in R8.
+ */
 public class PrintSeeds {
 
   private static final String USAGE =
       "Arguments: <rt.jar> <r8.jar> <pg-conf.txt>\n"
           + "\n"
           + "PrintSeeds prints the classes, interfaces, methods and fields selected by\n"
-          + "<pg-conf.txt> when compiling <r8.jar> alongside <rt.jar>.";
+          + "<pg-conf.txt> when compiling <r8.jar> alongside <rt.jar>.\n"
+          + "\n"
+          + "The output format is identical to what is printed when -printseeds is specified in\n"
+          + "<pg-conf.txt>, but running PrintSeeds can be faster than running R8 with \n"
+          + "-printseeds. See also the "
+          + PrintUses.class.getSimpleName()
+          + " program in R8.";
 
   public static void main(String[] args) throws Exception {
     if (args.length != 3) {
-      System.out.println(USAGE);
+      System.out.println(USAGE.replace("\n", System.lineSeparator()));
       System.exit(1);
     }
     Path rtJar = Paths.get(args[0]);
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 0e69de6..c762182 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -31,6 +31,17 @@
 import java.util.Map;
 import java.util.Set;
 
+/**
+ * PrintUses prints the classes, interfaces, methods and fields used by a given program
+ * &lt;sample.jar&gt;, restricted to classes and interfaces in a given library &lt;r8.jar&gt; that
+ * are not in &lt;sample.jar&gt;.
+ *
+ * <p>The output is in the same format as what is printed when specifying {@code -printseeds} in a
+ * ProGuard configuration file. See also the {@link PrintSeeds} program in R8.
+ *
+ * <p>Note that this tool is not related to the {@code -printusage} option of ProGuard configuration
+ * files.
+ */
 public class PrintUses {
 
   private static final String USAGE =
@@ -38,7 +49,12 @@
           + "\n"
           + "PrintUses prints the classes, interfaces, methods and fields used by <sample.jar>,\n"
           + "restricted to classes and interfaces in <r8.jar> that are not in <sample.jar>.\n"
-          + "<rt.jar> and <r8.jar> should point to libraries used by <sample.jar>.";
+          + "<rt.jar> and <r8.jar> should point to libraries used by <sample.jar>.\n"
+          + "\n"
+          + "The output is in the same format as what is printed when specifying -printseeds in\n"
+          + "a ProGuard configuration file. See also the "
+          + PrintSeeds.class.getSimpleName()
+          + " program in R8.";
 
   private final Set<String> descriptors;
   private final PrintStream out;
@@ -191,7 +207,7 @@
 
   public static void main(String[] args) throws Exception {
     if (args.length != 3) {
-      System.out.println(USAGE);
+      System.out.println(USAGE.replace("\n", System.lineSeparator()));
       return;
     }
     AndroidApp.Builder builder = AndroidApp.builder();
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 89abadd..23dbfbf 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -3,9 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.OptimizationFeedback;
+import com.android.tools.r8.ir.conversion.OptimizationFeedbackIgnore;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
+import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
 import java.io.PrintStream;
 
 public class AssemblyWriter extends DexByteCodeWriter {
@@ -13,12 +19,29 @@
   private final boolean writeAllClassInfo;
   private final boolean writeFields;
   private final boolean writeAnnotations;
+  private final boolean writeIR;
+  private final AppInfoWithSubtyping appInfo;
+  private final Timing timing = new Timing("AssemblyWriter");
+  private final OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
 
-  public AssemblyWriter(DexApplication application, InternalOptions options, boolean allInfo) {
+  public AssemblyWriter(
+      DexApplication application, InternalOptions options, boolean allInfo, boolean writeIR) {
     super(application, options);
     this.writeAllClassInfo = allInfo;
     this.writeFields = allInfo;
     this.writeAnnotations = allInfo;
+    this.writeIR = writeIR;
+    if (writeIR) {
+      this.appInfo = new AppInfoWithSubtyping(application.toDirect());
+      if (options.programConsumer == null) {
+        // Use class-file backend, since the CF frontend for testing does not support desugaring of
+        // synchronized methods for the DEX backend (b/109789541).
+        options.programConsumer = ClassFileConsumer.emptyConsumer();
+      }
+      options.outline.enabled = false;
+    } else {
+      this.appInfo = null;
+    }
   }
 
   @Override
@@ -88,10 +111,21 @@
     ps.println();
     Code code = method.getCode();
     if (code != null) {
-      ps.println(code.toString(method, naming));
+      if (writeIR) {
+        writeIR(method, ps);
+      } else {
+        ps.println(code.toString(method, naming));
+      }
     }
   }
 
+  private void writeIR(DexEncodedMethod method, PrintStream ps) {
+    CfgPrinter printer = new CfgPrinter();
+    new IRConverter(appInfo, options, timing, printer)
+        .processMethod(method, ignoreOptimizationFeedback, null, null, null);
+    ps.println(printer.toString());
+  }
+
   private void writeAnnotations(DexAnnotationSet annotations, PrintStream ps) {
     if (writeAnnotations) {
       if (!annotations.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index ca79b0f..309e1d0 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -367,6 +367,13 @@
     }
     setLocalVariableLists();
     readEndingLocals(builder);
+    if (currentBlockInfo != null && instruction.canThrow()) {
+      Snapshot exceptionTransfer =
+          state.getSnapshot().exceptionTransfer(builder.getFactory().throwableType);
+      for (int target : currentBlockInfo.exceptionalSuccessors) {
+        recordStateForTarget(target, exceptionTransfer);
+      }
+    }
     if (isControlFlow(instruction)) {
       ensureDebugValueLivenessControl(builder);
       instruction.buildIR(builder, state, this);
@@ -376,13 +383,6 @@
       }
       state.clear();
     } else {
-      if (currentBlockInfo != null && instruction.canThrow()) {
-        Snapshot exceptionTransfer =
-            state.getSnapshot().exceptionTransfer(builder.getFactory().throwableType);
-        for (int target : currentBlockInfo.exceptionalSuccessors) {
-          recordStateForTarget(target, exceptionTransfer);
-        }
-      }
       instruction.buildIR(builder, state, this);
       ensureDebugValueLiveness(builder);
       if (builder.getCFG().containsKey(currentInstructionIndex + 1)) {
diff --git a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
index 4e07849..4f749b5 100644
--- a/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/CfFrontendExamplesTest.java
@@ -85,6 +85,11 @@
   }
 
   @Test
+  public void testInlining() throws Exception {
+    runTest("inlining.Inlining");
+  }
+
+  @Test
   public void testInstanceVariable() throws Exception {
     runTest("instancevariable.InstanceVariable");
   }
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 348dbf1..087012c 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -41,6 +41,7 @@
         "filledarray.FilledArray",
         "hello.Hello",
         "ifstatements.IfStatements",
+        "inlining.Inlining",
         "instancevariable.InstanceVariable",
         "instanceofstring.InstanceofString",
         "invoke.Invoke",
diff --git a/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java b/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
index 751ac83..1d1604b 100644
--- a/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
+++ b/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
@@ -82,7 +82,7 @@
     parseSimpleError(
         GenericSignatureParser::parseClassSignature,
         e -> assertTrue(e.getMessage().startsWith("Expected L at position 1")));
-    // TODO(sgjesse): The position 2 reported here is onr off.
+    // TODO(sgjesse): The position 2 reported here is one off.
     parseSimpleError(
         GenericSignatureParser::parseFieldSignature,
         e -> assertTrue(e.getMessage().startsWith("Expected L, [ or T at position 2")));
@@ -104,7 +104,7 @@
           fail("Succesfully parsed " + signature.substring(0, i) + " (position " + i +")");
         }
       } catch (GenericSignatureFormatError e) {
-        assertTrue("" + i + " Was: " + e.getMessage(), e.getMessage().contains("at position " + (i + 1)));
+        assertTrue(e.getMessage().contains("at position " + (i + 1)));
       }
     }
   }
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index ace5c9b..38de25e 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
+import static org.junit.Assert.assertTrue;
+
 import com.android.tools.r8.StringResource;
 import com.android.tools.r8.code.Const4;
 import com.android.tools.r8.code.ConstString;
@@ -53,6 +55,7 @@
 import com.android.tools.r8.code.Throw;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexCode;
@@ -65,6 +68,8 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
@@ -72,6 +77,8 @@
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.naming.signature.GenericSignatureAction;
+import com.android.tools.r8.naming.signature.GenericSignatureParser;
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableList;
@@ -367,6 +374,10 @@
     public abstract boolean isLocalClass();
 
     public abstract boolean isAnonymousClass();
+
+    public abstract String getOriginalSignatureAttribute();
+
+    public abstract String getFinalSignatureAttribute();
   }
 
   private class AbsentClassSubject extends ClassSubject {
@@ -448,6 +459,16 @@
     public boolean isAnonymousClass() {
       return false;
     }
+
+    @Override
+    public String getOriginalSignatureAttribute() {
+      return null;
+    }
+
+    @Override
+    public String getFinalSignatureAttribute() {
+      return null;
+    }
   }
 
   public class FoundClassSubject extends ClassSubject {
@@ -641,6 +662,98 @@
     }
 
     @Override
+    public String getOriginalSignatureAttribute() {
+      // Build the generic signature using the current mapping if any.
+      class GenericSignatureGenerater implements GenericSignatureAction<String> {
+
+        private StringBuilder signature;
+
+        public String getSignature() {
+          return signature.toString();
+        }
+
+        @Override
+        public void parsedSymbol(char symbol) {
+          signature.append(symbol);
+        }
+
+        @Override
+        public void parsedIdentifier(String identifier) {
+          signature.append(identifier);
+        }
+
+        @Override
+        public String parsedTypeName(String name) {
+          String type = name;
+          if (originalToObfuscatedMapping != null) {
+            String original = originalToObfuscatedMapping.inverse().get(name);
+            type = original != null ? original : name;
+          }
+          signature.append(type);
+          return type;
+        }
+
+        @Override
+        public String parsedInnerTypeName(String enclosingType, String name) {
+          if (originalToObfuscatedMapping != null) {
+            // The enclosingType has already been mapped if a mapping is present.
+            String minifiedEnclosing = originalToObfuscatedMapping.get(enclosingType);
+            String type = originalToObfuscatedMapping.inverse().get(minifiedEnclosing + "$" + name);
+            if (type != null) {
+              assert type.startsWith(enclosingType + "$");
+              name = type.substring(enclosingType.length() + 1);
+            }
+            signature.append(name);
+            return type;
+          } else {
+            String type = enclosingType + "$" + name;
+            signature.append(name);
+            return type;
+          }
+        }
+
+        @Override
+        public void start() {
+          signature = new StringBuilder();
+        }
+
+        @Override
+        public void stop() {
+          // nothing to do
+        }
+      }
+
+      String finalSignature = getFinalSignatureAttribute();
+      if (finalSignature == null || mapping == null) {
+        return finalSignature;
+      }
+
+      GenericSignatureGenerater rewriter = new GenericSignatureGenerater();
+      GenericSignatureParser<String> parser = new GenericSignatureParser<>(rewriter);
+      parser.parseClassSignature(finalSignature);
+      return rewriter.getSignature();
+    }
+
+    @Override
+    public String getFinalSignatureAttribute() {
+      DexAnnotation annotation = findAnnotation("dalvik.annotation.Signature");
+      if (annotation == null) {
+        return null;
+      }
+      assert annotation.annotation.elements.length == 1;
+      DexAnnotationElement element = annotation.annotation.elements[0];
+      assert element.value instanceof DexValueArray;
+      StringBuilder builder = new StringBuilder();
+      DexValueArray valueArray = (DexValueArray) element.value;
+      for (DexValue value : valueArray.getValues()) {
+        assertTrue(value instanceof DexValueString);
+        DexValueString s = (DexValueString) value;
+        builder.append(s.getValue());
+      }
+      return builder.toString();
+    }
+
+    @Override
     public String toString() {
       return dexClass.toSourceString();
     }