Better Proguard configuration parser error for invalid options statring with keepclass

Also add an "arrow" to the exception text pointing to the location
on the erronous line.

R=herhut@google.com

Change-Id: I079dc7fe0400a2df783390abfa90366c1a026551
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 69db32d..965b1c0 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -20,6 +20,7 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.nio.CharBuffer;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.NoSuchFileException;
@@ -373,6 +374,10 @@
         } else if (acceptString("eswithmembernames")) {
           builder.setType(ProguardKeepRuleType.KEEP_CLASSES_WITH_MEMBERS);
           builder.getModifiersBuilder().allowsShrinking = true;
+        } else {
+          // The only path to here is through "-keep" followed by "class".
+          unacceptString("-keepclass");
+          throw parseError("Unknown option");
         }
       } else {
         builder.setType(ProguardKeepRuleType.KEEP);
@@ -903,6 +908,15 @@
       return contents.substring(start, end);
     }
 
+    private void unacceptString(String expected) {
+      assert position >= expected.length();
+      position -= expected.length();
+      for (int i = 0; i < expected.length(); i++) {
+        assert expected.charAt(i) == contents.charAt(position + i);
+      }
+    }
+
+
     private void checkNotNegatedPattern() throws ProguardRuleParserException {
       skipWhitespace();
       if (acceptChar('!')) {
@@ -945,9 +959,11 @@
       for (int lineNumber = 0; lineNumber < lines.length; lineNumber++) {
         String line = lines[lineNumber];
         if (remaining <= line.length() || lineNumber == lines.length - 1) {
-          return path.toString() + ":" + (lineNumber + 1) + ":" + remaining + "\n" + line;
+          String arrow = CharBuffer.allocate(remaining).toString().replace( '\0', ' ' ) + '^';
+          return path.toString() + ":" + (lineNumber + 1) + ":" + remaining + "\n" + line
+              + '\n' + arrow;
         }
-        remaining -= (line.length() + 1); // include newline.
+        remaining -= (line.length() + 1); // Include newline.
       }
       return path.toString();
     }
diff --git a/src/main/java/com/android/tools/r8/utils/FileUtils.java b/src/main/java/com/android/tools/r8/utils/FileUtils.java
index a457e51..3f69c2e 100644
--- a/src/main/java/com/android/tools/r8/utils/FileUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/FileUtils.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.CompilationException;
 import java.io.BufferedReader;
-import java.io.BufferedWriter;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
@@ -67,12 +66,7 @@
   }
 
   public static void writeTextFile(Path file, List<String> lines) throws IOException {
-    try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) {
-      for (String line : lines) {
-        writer.write(line);
-        writer.write("\n");
-      }
-    }
+    Files.write(file, lines);
   }
 
   public static void writeTextFile(Path file, String... lines) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 2362028..b20cb22 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -10,16 +10,18 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.graph.DexAccessFlags;
 import com.android.tools.r8.graph.DexItemFactory;
 import java.io.IOException;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 
-public class ProguardConfigurationParserTest {
+public class ProguardConfigurationParserTest extends TestBase {
 
   private static final String VALID_PROGUARD_DIR = "src/test/proguard/valid/";
   private static final String INVALID_PROGUARD_DIR = "src/test/proguard/invalid/";
@@ -331,4 +333,17 @@
     ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
     parser.parse(Paths.get(PARSE_AND_SKIP_SINGLE_ARGUMENT));
   }
+
+  @Test
+  public void parseInvalidKeepClassOption() throws IOException, ProguardRuleParserException {
+    thrown.expect(ProguardRuleParserException.class);
+    thrown.expectMessage("Unknown option at ");
+    ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+    Path proguardConfig = writeTextToTempFile(
+        "-keepclassx public class * {  ",
+        "  native <methods>;           ",
+        "}                             "
+    );
+    parser.parse(proguardConfig);
+  }
 }