Parse <n> wildcard in class names.

Bug: 73800755
Change-Id: I3fccb04f2aeae420c8b2ce976a0bb5b15bcb82e7
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 b526973..e96de6f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -535,7 +535,7 @@
     private ProguardIfRule parseIfRule(TextPosition optionStart)
         throws ProguardRuleParserException {
       ProguardIfRule.Builder ifRuleBuilder = ProguardIfRule.builder();
-      parseClassSpec(ifRuleBuilder, true);
+      parseClassSpec(ifRuleBuilder, false);
 
       // Required a subsequent keep rule.
       skipWhitespace();
@@ -1086,15 +1086,52 @@
       return Integer.parseInt(s);
     }
 
+    private final Predicate<Character> CLASS_NAME_PREDICATE =
+        character -> IdentifierUtils.isDexIdentifierPart(character)
+            || character == '.'
+            || character == '*'
+            || character == '?'
+            || character == '%'
+            || character == '['
+            || character == ']';
+
     private String acceptClassName() {
-      return acceptString(character ->
-          IdentifierUtils.isDexIdentifierPart(character)
-              || character == '.'
-              || character == '*'
-              || character == '?'
-              || character == '%'
-              || character == '['
-              || character == ']');
+      return acceptString(CLASS_NAME_PREDICATE);
+    }
+
+    private String acceptClassNameWithNthWildcard() {
+      StringBuilder nthWildcard = null;
+      skipWhitespace();
+      int start = position;
+      int end = position;
+      while (!eof(end)) {
+        char current = contents.charAt(end);
+        if (nthWildcard != null) {
+          if (current == '>') {
+            try {
+              Integer.parseUnsignedInt(nthWildcard.toString());
+            } catch (NullPointerException | NumberFormatException e) {
+              return null;
+            }
+            nthWildcard = null;
+          } else {
+            nthWildcard.append(current);
+          }
+          end++;
+        } else if (CLASS_NAME_PREDICATE.test(current)) {
+          end++;
+        } else if (current == '<') {
+          nthWildcard = new StringBuilder();
+          end++;
+        } else {
+          break;
+        }
+      }
+      if (start == end) {
+        return null;
+      }
+      position = end;
+      return contents.substring(start, end);
     }
 
     private String acceptFieldNameOrIntegerForReturn() {
@@ -1194,7 +1231,7 @@
     }
 
     private String parseClassName() throws ProguardRuleParserException {
-      String name = acceptClassName();
+      String name = acceptClassNameWithNthWildcard();
       if (name == null) {
         throw parseError("Class name expected");
       }
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 5f6a5cc..84f6a08 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -37,6 +37,7 @@
 import java.util.Collections;
 import java.util.List;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class ProguardConfigurationParserTest extends TestBase {
@@ -544,7 +545,7 @@
     assertTrue(
         config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
     assertTrue(
-        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobar;")));
     assertTrue(
         config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoobar;")));
   }
@@ -561,12 +562,47 @@
     assertTrue(
         config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
     assertTrue(
-        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobar;")));
     assertTrue(
         config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoobar;")));
   }
 
   @Test
+  public void testAdaptClassStringsNthWildcard() throws Exception {
+    DexItemFactory dexItemFactory = new DexItemFactory();
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(dexItemFactory, reporter);
+    String wildcard = "-adaptclassstrings *foo<1>";
+    parser.parse(createConfigurationForTesting(ImmutableList.of(wildcard)));
+    verifyParserEndsCleanly();
+    ProguardConfiguration config = parser.getConfig();
+    assertFalse(
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoobar;")));
+    assertFalse(
+        config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboofoobar;")));
+    // TODO(b/73800755): Use <n> while matching class name list.
+    //assertTrue(
+    //    config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboofooboo;")));
+  }
+
+  @Ignore("b/73800755: verify the range of <n>")
+  @Test
+  public void testAdaptClassStringsNthWildcard_outOfRange() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-adaptclassstrings *foo<2>"
+    );
+    try {
+      ProguardConfigurationParser parser =
+          new ProguardConfigurationParser(new DexItemFactory(), reporter);
+      parser.parse(proguardConfig);
+      fail();
+    } catch (AbortException e) {
+      checkDiagnostic(handler.errors, proguardConfig, 1, 1,
+          "wildcard", "out", "range");
+    }
+  }
+
+  @Test
   public void testIdentifierNameString() throws Exception {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
@@ -1173,6 +1209,43 @@
   }
 
   @Test
+  public void parse_if_nthWildcard() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-if class **$R**",
+        "-keep class **$D<2>"  // <2> corresponds to the 2nd ** in -if rule.
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+        "Ignoring", "-if");
+    ProguardConfiguration config = parser.getConfig();
+    assertEquals(1, config.getRules().size());
+    ProguardIfRule if0 = (ProguardIfRule) config.getRules().get(0);
+    assertEquals("**$R**", if0.getClassNames().toString());
+    assertEquals(ProguardKeepRuleType.KEEP, if0.subsequentRule.getType());
+    assertEquals("**$D<2>", if0.subsequentRule.getClassNames().toString());
+  }
+
+  @Ignore("b/73800755: verify the range of <n>")
+  @Test
+  public void parse_if_nthWildcard_outOfRange() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-if class **$R**",
+        "-keep class **D<4>"  // There are 3 ** in this rule.
+    );
+    try {
+      ProguardConfigurationParser parser =
+          new ProguardConfigurationParser(new DexItemFactory(), reporter);
+      parser.parse(proguardConfig);
+      fail();
+    } catch (AbortException e) {
+      checkDiagnostic(handler.errors, proguardConfig, 1, 1,
+          "wildcard", "out", "range");
+    }
+  }
+
+  @Test
   public void parse_if_if() throws Exception {
     Path proguardConfig = writeTextToTempFile(
         "-if   class **$$ModuleAdapter",