Proguard config parser: allow spaces and quotes in class name list.

Bug: 124181032
Change-Id: Iee3877252efed525cdc5fe3f08842518141533c7
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 e3a8273..5acd2a8 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -1433,11 +1433,27 @@
     }
 
     private IdentifierPatternWithWildcards acceptIdentifierWithBackreference(IdentifierType kind) {
+      IdentifierPatternWithWildcardsAndNegation pattern =
+          acceptIdentifierWithBackreference(kind, false);
+      if (pattern == null) {
+        return null;
+      }
+      assert !pattern.negated;
+      return pattern.patternWithWildcards;
+    }
+
+    private IdentifierPatternWithWildcardsAndNegation acceptIdentifierWithBackreference(
+        IdentifierType kind, boolean allowNegation) {
       ImmutableList.Builder<ProguardWildcard> wildcardsCollector = ImmutableList.builder();
       StringBuilder currentAsterisks = null;
       int asteriskCount = 0;
       StringBuilder currentBackreference = null;
       skipWhitespace();
+
+      final char quote = acceptQuoteIfPresent();
+      final boolean quoted = isQuote(quote);
+      final boolean negated = allowNegation ? acceptChar('!') : false;
+
       int start = position;
       int end = position;
       while (!eof(end)) {
@@ -1516,9 +1532,17 @@
           currentBackreference = new StringBuilder();
           end += Character.charCount(current);
         } else {
+          if (quoted && quote != current) {
+            throw reporter.fatalError(
+                new StringDiagnostic(
+                    "Invalid character '" + (char) current + "', expected end-quote.",
+                    origin,
+                    getPosition()));
+          }
           break;
         }
       }
+      position = quoted ? end + 1 : end;
       if (currentAsterisks != null) {
         wildcardsCollector.add(new ProguardWildcard.Pattern(currentAsterisks.toString()));
       }
@@ -1530,10 +1554,8 @@
       if (start == end) {
         return null;
       }
-      position = end;
-      return new IdentifierPatternWithWildcards(
-          contents.substring(start, end),
-          wildcardsCollector.build());
+      return new IdentifierPatternWithWildcardsAndNegation(
+          contents.substring(start, end), wildcardsCollector.build(), negated);
     }
 
     private String acceptFieldNameOrIntegerForReturn() {
@@ -1627,20 +1649,20 @@
       }
     }
 
+    private void parseClassNameAddToBuilder(ProguardClassNameList.Builder builder)
+        throws ProguardRuleParserException {
+      IdentifierPatternWithWildcardsAndNegation name = parseClassName(true);
+      builder.addClassName(
+          name.negated,
+          ProguardTypeMatcher.create(name.patternWithWildcards, ClassOrType.CLASS, dexItemFactory));
+      skipWhitespace();
+    }
+
     private ProguardClassNameList parseClassNames() throws ProguardRuleParserException {
       ProguardClassNameList.Builder builder = ProguardClassNameList.builder();
-      skipWhitespace();
-      boolean negated = acceptChar('!');
-      builder.addClassName(negated,
-          ProguardTypeMatcher.create(parseClassName(), ClassOrType.CLASS, dexItemFactory));
-      skipWhitespace();
-      while (acceptChar(',')) {
-        skipWhitespace();
-        negated = acceptChar('!');
-        builder.addClassName(negated,
-            ProguardTypeMatcher.create(parseClassName(), ClassOrType.CLASS, dexItemFactory));
-        skipWhitespace();
-      }
+      do {
+        parseClassNameAddToBuilder(builder);
+      } while (acceptChar(','));
       return builder.build();
     }
 
@@ -1650,8 +1672,15 @@
     }
 
     private IdentifierPatternWithWildcards parseClassName() throws ProguardRuleParserException {
-      IdentifierPatternWithWildcards name =
-          acceptIdentifierWithBackreference(IdentifierType.CLASS_NAME);
+      IdentifierPatternWithWildcardsAndNegation name = parseClassName(false);
+      assert !name.negated;
+      return name.patternWithWildcards;
+    }
+
+    private IdentifierPatternWithWildcardsAndNegation parseClassName(boolean allowNegation)
+        throws ProguardRuleParserException {
+      IdentifierPatternWithWildcardsAndNegation name =
+          acceptIdentifierWithBackreference(IdentifierType.CLASS_NAME, allowNegation);
       if (name == null) {
         throw parseError("Class name expected");
       }
@@ -1838,4 +1867,15 @@
       return false;
     }
   }
+
+  static class IdentifierPatternWithWildcardsAndNegation {
+    final IdentifierPatternWithWildcards patternWithWildcards;
+    final boolean negated;
+
+    IdentifierPatternWithWildcardsAndNegation(
+        String pattern, List<ProguardWildcard> wildcards, boolean negated) {
+      patternWithWildcards = new IdentifierPatternWithWildcards(pattern, wildcards);
+      this.negated = negated;
+    }
+  }
 }
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 f3aba75..cac6dd3 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -2402,12 +2402,14 @@
 
   @Test
   public void b124181032() throws Exception {
+    // Test spaces and quotes in class name list.
     ProguardConfigurationParser parser;
     parser = new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(
         createConfigurationForTesting(
             ImmutableList.of(
-                "-keepclassmembers class a.b.c.**, !**Client, !**Interceptor {",
+                "-keepclassmembers class \"a.b.c.**\" ,"
+                    + " !**d , '!**e' , \"!**f\" , g , 'h' , \"i\" { ",
                 "<fields>;",
                 "<init>();",
                 "}")));
@@ -2415,6 +2417,6 @@
     assertEquals(1, rules.size());
     ProguardConfigurationRule rule = rules.get(0);
     assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS.toString(), rule.typeString());
-    assertEquals("a.b.c.**,!**Client,!**Interceptor", rule.getClassNames().toString());
+    assertEquals("a.b.c.**,!**d,!**e,!**f,g,h,i", rule.getClassNames().toString());
   }
 }