Support reading main dex list files starting with a byte order mark (BOM)

Bug: 135898791
Change-Id: I7b5c1f8601a6274c02a32ec7a94370548d455a7b
diff --git a/src/main/java/com/android/tools/r8/utils/MainDexList.java b/src/main/java/com/android/tools/r8/utils/MainDexList.java
index 74034ef..f1e775b 100644
--- a/src/main/java/com/android/tools/r8/utils/MainDexList.java
+++ b/src/main/java/com/android/tools/r8/utils/MainDexList.java
@@ -43,6 +43,9 @@
       int newLineIndex = lines.indexOf('\n', offset);
       int lineEnd = newLineIndex == -1 ? lines.length() : newLineIndex;
       String line = lines.substring(offset, lineEnd).trim();
+      if (lineNumber == 1) {
+        line = StringUtils.stripBOM(line);
+      }
       if (!line.isEmpty()) {
         try {
           result.add(parseEntry(line, itemFactory));
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 15eec4a..addafe4 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -65,12 +65,14 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.MainDexList;
 import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -343,14 +345,20 @@
         "a/b/c/D.class",
         "a/b/c/D$E.class"
     );
-    DexItemFactory factory = new DexItemFactory();
-    Path mainDexList = temp.getRoot().toPath().resolve("valid.txt");
-    FileUtils.writeTextFile(mainDexList, list);
-    Set<DexType> types = parse(mainDexList, factory);
-    for (String entry : list) {
-      DexType type = factory.createType("L" + entry.replace(".class", "") + ";");
-      assertTrue(types.contains(type));
-      assertSame(type, MainDexList.parseEntry(entry, factory));
+    for (Boolean addBOM : BooleanUtils.values()) {
+      DexItemFactory factory = new DexItemFactory();
+      Path mainDexList = temp.getRoot().toPath().resolve("valid.txt");
+      FileUtils.writeTextFile(
+          mainDexList,
+          list.stream()
+              .map(name -> name.startsWith("A") && addBOM ? StringUtils.BOM + name : name)
+              .collect(Collectors.toList()));
+      Set<DexType> types = parse(mainDexList, factory);
+      for (String entry : list) {
+        DexType type = factory.createType("L" + entry.replace(".class", "") + ";");
+        assertTrue(types.contains(type));
+        assertSame(type, MainDexList.parseEntry(entry, factory));
+      }
     }
   }
 
@@ -376,18 +384,27 @@
     parse(mainDexList, factory);
   }
 
+  enum TestMode {
+    FROM_CLASS_NAMES,
+    FROM_FILE,
+    FROM_FILE_WITH_BOM
+  }
+
   private Path runD8WithMainDexList(
-      CompilationMode mode, Path input, List<String> mainDexClasses, boolean useFile)
+      CompilationMode mode, Path input, List<String> mainDexClasses, TestMode testMode)
       throws Exception {
     Path testDir = temp.newFolder().toPath();
     Path listFile = testDir.resolve("main-dex-list.txt");
-    if (mainDexClasses != null && useFile) {
-      FileUtils.writeTextFile(
-          listFile,
-          mainDexClasses
-              .stream()
+    if (mainDexClasses != null
+        && (testMode == TestMode.FROM_FILE || testMode == TestMode.FROM_FILE_WITH_BOM)) {
+      List<String> lines =
+          mainDexClasses.stream()
               .map(clazz -> clazz.replace('.', '/') + ".class")
-              .collect(Collectors.toList()));
+              .collect(Collectors.toList());
+      if (testMode == TestMode.FROM_FILE_WITH_BOM) {
+        lines.set(0, StringUtils.BOM + lines.get(0));
+      }
+      FileUtils.writeTextFile(listFile, lines);
     }
 
     D8Command.Builder builder =
@@ -396,7 +413,7 @@
             .setMode(mode)
             .setOutput(testDir, OutputMode.DexIndexed);
     if (mainDexClasses != null) {
-      if (useFile) {
+      if (testMode == TestMode.FROM_FILE) {
         builder.addMainDexListFiles(listFile);
       } else {
         builder.addMainDexClasses(mainDexClasses);
@@ -416,21 +433,25 @@
       if (allClasses) {
         // If all classes are passed add a run without a main-dex list as well.
         testDirs.put(
-            runD8WithMainDexList(mode, input, null, true),
+            runD8WithMainDexList(mode, input, null, TestMode.FROM_CLASS_NAMES),
             mode.toString() + ": without a main-dex list");
       }
       testDirs.put(
-          runD8WithMainDexList(mode, input, mainDexClasses, true),
+          runD8WithMainDexList(mode, input, mainDexClasses, TestMode.FROM_FILE),
           mode.toString() + ": main-dex list files");
       testDirs.put(
-          runD8WithMainDexList(mode, input, mainDexClasses, false),
+          runD8WithMainDexList(mode, input, mainDexClasses, TestMode.FROM_FILE_WITH_BOM),
+          mode.toString() + ": main-dex list files (with BOM)");
+      testDirs.put(
+          runD8WithMainDexList(mode, input, mainDexClasses, TestMode.FROM_CLASS_NAMES),
           mode.toString() + ": main-dex classes");
       if (mainDexClasses != null) {
         testDirs.put(
-            runD8WithMainDexList(mode, input, Lists.reverse(mainDexClasses), true),
+            runD8WithMainDexList(mode, input, Lists.reverse(mainDexClasses), TestMode.FROM_FILE),
             mode.toString() + ": main-dex list files (reversed)");
         testDirs.put(
-            runD8WithMainDexList(mode, input, Lists.reverse(mainDexClasses), false),
+            runD8WithMainDexList(
+                mode, input, Lists.reverse(mainDexClasses), TestMode.FROM_CLASS_NAMES),
             mode.toString() + ": main-dex classes (reversed)");
       }