Reland "Discard init with wildcards."

This time, account for <clinit> and back-references in method names.

Bug: 130710288, 131232018, 131370264
Change-Id: I36359934ec43f94b1ff65f7cfabf688d109bdf94
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 8218679..e4113b3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -457,8 +457,8 @@
 
     private boolean parseIgnoredOption(TextPosition optionStart) {
       return Iterables.any(IGNORED_SINGLE_ARG_OPTIONS, this::skipOptionWithSingleArg)
-          || Iterables.any(IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS,
-                           this::skipOptionWithOptionalSingleArg)
+          || Iterables.any(
+              IGNORED_OPTIONAL_SINGLE_ARG_OPTIONS, this::skipOptionWithOptionalSingleArg)
           || Iterables.any(IGNORED_FLAG_OPTIONS, this::skipFlag)
           || Iterables.any(IGNORED_CLASS_DESCRIPTOR_OPTIONS, this::skipOptionWithClassSpec)
           || parseOptimizationOption(optionStart);
@@ -822,9 +822,7 @@
       }
     }
 
-    private ProguardTypeMatcher parseAnnotation()
-      throws ProguardRuleParserException {
-
+    private ProguardTypeMatcher parseAnnotation() throws ProguardRuleParserException {
       skipWhitespace();
       int startPosition = position;
       if (acceptChar('@')) {
@@ -1038,6 +1036,10 @@
         ruleBuilder.setRuleType(ProguardMemberType.INIT);
         ruleBuilder.setName(IdentifierPatternWithWildcards.withoutWildcards("<init>"));
         ruleBuilder.setArguments(parseArgumentList());
+      } else if (acceptString("<clinit>")) {
+        ruleBuilder.setRuleType(ProguardMemberType.CLINIT);
+        ruleBuilder.setName(IdentifierPatternWithWildcards.withoutWildcards("<clinit>"));
+        ruleBuilder.setArguments(parseArgumentList());
       } else {
         TextPosition firstStart = getPosition();
         IdentifierPatternWithWildcards first =
@@ -1047,90 +1049,122 @@
           if (first.pattern.equals("*") && hasNextChar(';')) {
             ruleBuilder.setRuleType(ProguardMemberType.ALL);
           } else {
+            // No return type present, only method name, most likely constructors.
             if (hasNextChar('(')) {
+              // "<init>" and "<clinit>" are explicitly checked, so angular brackets can't appear.
+              checkConstructorPattern(first, firstStart);
               ruleBuilder.setRuleType(ProguardMemberType.CONSTRUCTOR);
               ruleBuilder.setName(first);
               ruleBuilder.setArguments(parseArgumentList());
             } else {
-              TextPosition secondStart = getPosition();
-              IdentifierPatternWithWildcards second =
-                  acceptIdentifierWithBackreference(IdentifierType.ANY);
-              if (second != null) {
-                skipWhitespace();
-                if (hasNextChar('(')) {
-                  ruleBuilder.setRuleType(ProguardMemberType.METHOD);
-                  ruleBuilder.setName(second);
-                  ruleBuilder
-                      .setTypeMatcher(
-                          ProguardTypeMatcher.create(first, ClassOrType.TYPE, dexItemFactory));
-                  ruleBuilder.setArguments(parseArgumentList());
-                } else {
-                  if (first.hasUnusualCharacters()) {
-                    warnUnusualCharacters("type", first.pattern, "field", firstStart);
-                  }
-                  if (second.hasUnusualCharacters()) {
-                    warnUnusualCharacters("field name", second.pattern, "field", secondStart);
-                  }
-                  ruleBuilder.setRuleType(ProguardMemberType.FIELD);
-                  ruleBuilder.setName(second);
-                  ruleBuilder
-                      .setTypeMatcher(
-                          ProguardTypeMatcher.create(first, ClassOrType.TYPE, dexItemFactory));
+              if (acceptString("<init>")) {
+                ProguardTypeMatcher typeMatcher =
+                    ProguardTypeMatcher.create(first, ClassOrType.TYPE, dexItemFactory);
+                if (!typeMatcher.matchesSpecificType()
+                    || !typeMatcher.getSpecificType().isVoidType()) {
+                  throw parseError("Expected [access-flag]* void <init>");
                 }
-                skipWhitespace();
-                // Parse "return ..." if present.
-                if (acceptString("return")) {
+                ruleBuilder.setRuleType(ProguardMemberType.INIT);
+                ruleBuilder.setName(IdentifierPatternWithWildcards.withoutWildcards("<init>"));
+                ruleBuilder.setTypeMatcher(typeMatcher);
+                ruleBuilder.setArguments(parseArgumentList());
+              } else if (acceptString("<clinit>")) {
+                ProguardTypeMatcher typeMatcher =
+                    ProguardTypeMatcher.create(first, ClassOrType.TYPE, dexItemFactory);
+                if (!typeMatcher.matchesSpecificType()
+                    || !typeMatcher.getSpecificType().isVoidType()) {
+                  throw parseError("Expected [access-flag]* void <clinit>");
+                }
+                ruleBuilder.setRuleType(ProguardMemberType.CLINIT);
+                ruleBuilder.setName(IdentifierPatternWithWildcards.withoutWildcards("<clinit>"));
+                ruleBuilder.setTypeMatcher(typeMatcher);
+                ruleBuilder.setArguments(parseArgumentList());
+              } else {
+                TextPosition secondStart = getPosition();
+                IdentifierPatternWithWildcards second =
+                    acceptIdentifierWithBackreference(IdentifierType.ANY);
+                if (second != null) {
                   skipWhitespace();
-                  if (acceptString("true")) {
-                    ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(true));
-                  } else if (acceptString("false")) {
-                    ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(false));
-                  } else if (acceptString("null")) {
-                    ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue());
+                  if (hasNextChar('(')) {
+                    // Parsing legitimate constructor patters is already done, so angular brackets
+                    // can't appear, except for legitimate back references.
+                    if (!second.hasBackreference() || second.hasUnusualCharacters()) {
+                      checkConstructorPattern(second, secondStart);
+                    }
+                    ruleBuilder.setRuleType(ProguardMemberType.METHOD);
+                    ruleBuilder.setName(second);
+                    ruleBuilder
+                        .setTypeMatcher(
+                            ProguardTypeMatcher.create(first, ClassOrType.TYPE, dexItemFactory));
+                    ruleBuilder.setArguments(parseArgumentList());
                   } else {
-                    TextPosition fieldOrValueStart = getPosition();
-                    String qualifiedFieldNameOrInteger = acceptFieldNameOrIntegerForReturn();
-                    if (qualifiedFieldNameOrInteger != null) {
-                      if (isInteger(qualifiedFieldNameOrInteger)) {
-                        Integer min = Integer.parseInt(qualifiedFieldNameOrInteger);
-                        Integer max = min;
-                        skipWhitespace();
-                        if (acceptString("..")) {
+                    if (first.hasUnusualCharacters()) {
+                      warnUnusualCharacters("type", first.pattern, "field", firstStart);
+                    }
+                    if (second.hasUnusualCharacters()) {
+                      warnUnusualCharacters("field name", second.pattern, "field", secondStart);
+                    }
+                    ruleBuilder.setRuleType(ProguardMemberType.FIELD);
+                    ruleBuilder.setName(second);
+                    ruleBuilder
+                        .setTypeMatcher(
+                            ProguardTypeMatcher.create(first, ClassOrType.TYPE, dexItemFactory));
+                  }
+                  skipWhitespace();
+                  // Parse "return ..." if present.
+                  if (acceptString("return")) {
+                    skipWhitespace();
+                    if (acceptString("true")) {
+                      ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(true));
+                    } else if (acceptString("false")) {
+                      ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(false));
+                    } else if (acceptString("null")) {
+                      ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue());
+                    } else {
+                      TextPosition fieldOrValueStart = getPosition();
+                      String qualifiedFieldNameOrInteger = acceptFieldNameOrIntegerForReturn();
+                      if (qualifiedFieldNameOrInteger != null) {
+                        if (isInteger(qualifiedFieldNameOrInteger)) {
+                          Integer min = Integer.parseInt(qualifiedFieldNameOrInteger);
+                          Integer max = min;
                           skipWhitespace();
-                          max = acceptInteger();
-                          if (max == null) {
-                            throw parseError("Expected integer value");
+                          if (acceptString("..")) {
+                            skipWhitespace();
+                            max = acceptInteger();
+                            if (max == null) {
+                              throw parseError("Expected integer value");
+                            }
                           }
-                        }
-                        if (!allowValueSpecification) {
-                          throw parseError("Unexpected value specification", fieldOrValueStart);
-                        }
-                        ruleBuilder.setReturnValue(
-                            new ProguardMemberRuleReturnValue(new LongInterval(min, max)));
-                      } else {
-                        if (ruleBuilder.getTypeMatcher() instanceof MatchSpecificType) {
-                          int lastDotIndex = qualifiedFieldNameOrInteger.lastIndexOf(".");
-                          DexType fieldType = ((MatchSpecificType) ruleBuilder
-                              .getTypeMatcher()).type;
-                          DexType fieldClass =
-                              dexItemFactory.createType(
-                                  javaTypeToDescriptor(
-                                      qualifiedFieldNameOrInteger.substring(0, lastDotIndex)));
-                          DexString fieldName =
-                              dexItemFactory.createString(
-                                  qualifiedFieldNameOrInteger.substring(lastDotIndex + 1));
-                          DexField field = dexItemFactory
-                              .createField(fieldClass, fieldType, fieldName);
-                          ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(field));
+                          if (!allowValueSpecification) {
+                            throw parseError("Unexpected value specification", fieldOrValueStart);
+                          }
+                          ruleBuilder.setReturnValue(
+                              new ProguardMemberRuleReturnValue(new LongInterval(min, max)));
                         } else {
-                          throw parseError("Expected specific type", fieldOrValueStart);
+                          if (ruleBuilder.getTypeMatcher() instanceof MatchSpecificType) {
+                            int lastDotIndex = qualifiedFieldNameOrInteger.lastIndexOf(".");
+                            DexType fieldType = ((MatchSpecificType) ruleBuilder
+                                .getTypeMatcher()).type;
+                            DexType fieldClass =
+                                dexItemFactory.createType(
+                                    javaTypeToDescriptor(
+                                        qualifiedFieldNameOrInteger.substring(0, lastDotIndex)));
+                            DexString fieldName =
+                                dexItemFactory.createString(
+                                    qualifiedFieldNameOrInteger.substring(lastDotIndex + 1));
+                            DexField field = dexItemFactory
+                                .createField(fieldClass, fieldType, fieldName);
+                            ruleBuilder.setReturnValue(new ProguardMemberRuleReturnValue(field));
+                          } else {
+                            throw parseError("Expected specific type", fieldOrValueStart);
+                          }
                         }
                       }
                     }
                   }
+                } else {
+                  throw parseError("Expected field or method name");
                 }
-              } else {
-                throw parseError("Expected field or method name");
               }
             }
           }
@@ -1143,6 +1177,23 @@
       }
     }
 
+    private void checkConstructorPattern(
+        IdentifierPatternWithWildcards pattern, TextPosition position)
+        throws ProguardRuleParserException {
+      if (pattern.pattern.equals("<clinit>")) {
+        reporter.warning(
+            new StringDiagnostic("Member rule for <clinit> has no effect.", origin, position));
+        return;
+      }
+      if (pattern.pattern.contains("<")) {
+        throw parseError("Unexpected character '<' in method name. "
+            + "The character '<' is only allowed in the method name '<init>'.", position);
+      } else if (pattern.pattern.contains(">")) {
+        throw parseError("Unexpected character '>' in method name. "
+            + "The character '>' is only allowed in the method name '<init>'.", position);
+      }
+    }
+
     private List<ProguardTypeMatcher> parseArgumentList() throws ProguardRuleParserException {
       List<ProguardTypeMatcher> arguments = new ArrayList<>();
       skipWhitespace();
@@ -1887,19 +1938,19 @@
       return start instanceof TextPosition && end instanceof TextPosition
           ? getTextSourceSnippet(source, (TextPosition) start, (TextPosition) end)
           : null;
-      }
     }
+  }
 
-    private String getTextSourceSnippet(String source, TextPosition start, TextPosition end) {
-      long length = end.getOffset() - start.getOffset();
-      if (start.getOffset() < 0 || end.getOffset() < 0
-          || start.getOffset() >= source.length() || end.getOffset() > source.length()
-          || length <= 0) {
-        return null;
-      } else {
-        return source.substring((int) start.getOffset(), (int) end.getOffset());
-      }
+  private String getTextSourceSnippet(String source, TextPosition start, TextPosition end) {
+    long length = end.getOffset() - start.getOffset();
+    if (start.getOffset() < 0 || end.getOffset() < 0
+        || start.getOffset() >= source.length() || end.getOffset() > source.length()
+        || length <= 0) {
+      return null;
+    } else {
+      return source.substring((int) start.getOffset(), (int) end.getOffset());
     }
+  }
 
   static class IdentifierPatternWithWildcards {
     final String pattern;
@@ -1918,6 +1969,11 @@
       return pattern.equals("*");
     }
 
+    boolean hasBackreference() {
+      return !wildcards.isEmpty()
+          && wildcards.stream().anyMatch(ProguardWildcard::isBackReference);
+    }
+
     boolean hasUnusualCharacters() {
       if (pattern.contains("<") || pattern.contains(">")) {
         int angleStartCount = 0;
@@ -1949,4 +2005,4 @@
       this.negated = negated;
     }
   }
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index 745b76d..567fee9 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -201,6 +201,7 @@
         }
         return true;
       case ALL_METHODS:
+      case CLINIT:
       case INIT:
       case CONSTRUCTOR:
       case METHOD:
@@ -233,6 +234,7 @@
         // Fall through for access flags, name and arguments.
       case CONSTRUCTOR:
       case INIT:
+      case CLINIT:
         // Name check.
         String name = stringCache.lookupString(originalSignature.name);
         if (!getName().matches(name)) {
@@ -371,6 +373,7 @@
         result.append(' ');
         // Fall through for rest of method signature.
       case CONSTRUCTOR:
+      case CLINIT:
       case INIT: {
         result.append(getName());
         result.append('(');
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberType.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberType.java
index aa37a2a..47c00b4 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberType.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberType.java
@@ -9,6 +9,7 @@
   ALL_FIELDS,
   ALL,
   ALL_METHODS,
+  CLINIT,
   INIT,
   CONSTRUCTOR,
   METHOD;
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 001e3201..0e6f114 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.DiagnosticsChecker.checkDiagnostics;
 import static com.android.tools.r8.shaking.ProguardConfigurationSourceStrings.createConfigurationForTesting;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -12,7 +13,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -27,6 +27,7 @@
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.position.TextRange;
 import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
+import com.android.tools.r8.shaking.constructor.InitMatchingTest;
 import com.android.tools.r8.utils.AbortException;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
@@ -616,16 +617,16 @@
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     String config1 =
         "-identifiernamestring class a.b.c.*GeneratedClass {\n"
-        + "  static java.lang.String CONTAINING_TYPE_*;\n"
-        + "}";
+            + "  static java.lang.String CONTAINING_TYPE_*;\n"
+            + "}";
     String config2 =
         "-identifiernamestring class x.y.z.ReflectionBasedFactory {\n"
-        + "  private static java.lang.reflect.Field field(java.lang.Class,java.lang.String);\n"
-        + "}";
+            + "  private static java.lang.reflect.Field field(java.lang.Class,java.lang.String);\n"
+            + "}";
     String config3 =
         "-identifiernamestring class * {\n"
-        + "  @my.annotations.IdentifierNameString *;\n"
-        + "}";
+            + "  @my.annotations.IdentifierNameString *;\n"
+            + "}";
     parser.parse(createConfigurationForTesting(ImmutableList.of(config1, config2, config3)));
     verifyParserEndsCleanly();
     ProguardConfiguration config = parser.getConfig();
@@ -1694,6 +1695,157 @@
   }
 
   @Test
+  public void parse_if_fieldType() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-if class **.R*",
+        "-keep class **.D<2> {",
+        "  <1>.F<2> fld;",
+        "}"
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    verifyParserEndsCleanly();
+    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());
+    assertEquals(1, if0.subsequentRule.getMemberRules().size());
+    ProguardMemberRule fieldRule = if0.subsequentRule.getMemberRules().get(0);
+    assertEquals("<1>.F<2>", fieldRule.getType().toString());
+
+    verifyWithProguard6(proguardConfig);
+  }
+
+  @Test
+  public void parse_if_fieldName() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-if class **.R*",
+        "-keep class **.D<2> {",
+        "  java.lang.String fld<2>;",
+        "}"
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    verifyParserEndsCleanly();
+    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());
+    assertEquals(1, if0.subsequentRule.getMemberRules().size());
+    ProguardMemberRule fieldRule = if0.subsequentRule.getMemberRules().get(0);
+    assertEquals("fld<2>", fieldRule.getName().toString());
+
+    verifyWithProguard6(proguardConfig);
+  }
+
+  @Test
+  public void parse_if_returnType() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-if class **.R*",
+        "-keep class **.D<2> {",
+        "  <1>.M<2> mtd(...);",
+        "}"
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    verifyParserEndsCleanly();
+    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());
+    assertEquals(1, if0.subsequentRule.getMemberRules().size());
+    ProguardMemberRule methodRule = if0.subsequentRule.getMemberRules().get(0);
+    assertEquals("<1>.M<2>", methodRule.getType().toString());
+
+    verifyWithProguard6(proguardConfig);
+  }
+
+  @Test
+  public void parse_if_init() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-if class **.R* {",
+        "  void *(...);",
+        "}",
+        "-keep class **.D<2> {",
+        "  <3>(...);",
+        "}"
+    );
+    try {
+      parser.parse(proguardConfig);
+      fail("Expect to fail due to unsupported constructor name pattern.");
+    } catch (AbortException e) {
+      checkDiagnostics(
+          handler.errors, proguardConfig, 5, 3, "Unexpected character", "method name");
+    }
+
+    verifyFailWithProguard6(proguardConfig, "Expecting type and name instead of just '<3>'");
+  }
+
+  @Test
+  public void parse_if_methodName_void() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-if class **.R* {",
+        "  void *(...);",
+        "}",
+        "-keep class **.D<2> {",
+        "  void <3>_delegate(...);",
+        "}"
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    verifyParserEndsCleanly();
+    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());
+    assertEquals(1, if0.subsequentRule.getMemberRules().size());
+    ProguardMemberRule methodRule = if0.subsequentRule.getMemberRules().get(0);
+    assertEquals("<3>_delegate", methodRule.getName().toString());
+
+    verifyWithProguard6(proguardConfig);
+  }
+
+  @Test
+  public void parse_if_methodName_class() throws Exception {
+    Path proguardConfig = writeTextToTempFile(
+        "-if class **.R* {",
+        "  ** *(...);",
+        "}",
+        "-keep class **.D<2> {",
+        "  <3> <4>(...);",
+        "}"
+    );
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(new DexItemFactory(), reporter);
+    parser.parse(proguardConfig);
+    verifyParserEndsCleanly();
+    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());
+    assertEquals(1, if0.subsequentRule.getMemberRules().size());
+    ProguardMemberRule methodRule = if0.subsequentRule.getMemberRules().get(0);
+    assertEquals("<3>", methodRule.getType().toString());
+    assertEquals("<4>", methodRule.getName().toString());
+
+    verifyWithProguard6(proguardConfig);
+  }
+
+  @Test
   public void parse_if_nthWildcard_notNumber_literalN() throws Exception {
     Path proguardConfig = writeTextToTempFile(
         "-if class **$R**",
@@ -1874,7 +2026,8 @@
       checkDiagnostics(handler.errors, proguardConfig, 5, 1,
           "Wildcard", "<3>", "invalid");
     }
-    verifyFailWithProguard6(proguardConfig, "Invalid reference to wildcard (3,");
+    verifyFailWithProguard6(
+        proguardConfig, "Use of generics not allowed for java type at '<1>.<3><2>'");
   }
 
   @Test
@@ -2415,6 +2568,12 @@
     return parser.getConfig();
   }
 
+  private ProguardConfiguration parseAndVerifyParserEndsCleanly(Path config) {
+    parser.parse(config);
+    verifyParserEndsCleanly();
+    return parser.getConfig();
+  }
+
   private void verifyParserEndsCleanly() {
     assertEquals(0, handler.infos.size());
     assertEquals(0, handler.warnings.size());
@@ -2572,4 +2731,50 @@
               + "The negation character can only be used to negate access flags");
     }
   }
-}
+
+  @Test
+  public void parseInits() throws Exception {
+    for (String initName : InitMatchingTest.ALLOWED_INIT_NAMES) {
+      reset();
+      Path initConfig = writeTextToTempFile(
+          "-keep class **.MyClass {",
+          "  " + initName + "(...);",
+          "}");
+      parseAndVerifyParserEndsCleanly(initConfig);
+    }
+
+    for (String initName : InitMatchingTest.INIT_NAMES) {
+      // Tested above.
+      if (InitMatchingTest.ALLOWED_INIT_NAMES.contains(initName)) {
+        continue;
+      }
+      reset();
+      Path proguardConfig = writeTextToTempFile(
+          "-keep class **.MyClass {",
+          "  " + initName + "(...);",
+          "}");
+      try {
+        parser.parse(proguardConfig);
+        fail("Expect to fail due to unsupported constructor name pattern.");
+      } catch (AbortException e) {
+        int column = initName.contains("void") ? initName.indexOf("void") + 8
+            : (initName.contains("XYZ") ? initName.indexOf(">") + 4 : 3);
+        if (initName.contains("XYZ")) {
+          checkDiagnostics(
+              handler.errors, proguardConfig, 2, column, "Expected [access-flag]* void ");
+        } else {
+          checkDiagnostics(
+              handler.errors, proguardConfig, 2, column, "Unexpected character", "method name");
+        }
+      }
+      // For some exceptional cases, Proguard accepts the rules but fails with an empty jar message.
+      if (initName.contains("<init>")
+          || initName.contains("<clinit>")
+          || initName.contains("void")) {
+        continue;
+      }
+      verifyFailWithProguard6(
+          proguardConfig, "Expecting type and name instead of just '" + initName + "'");
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/shaking/constructor/InitMatchingTest.java b/src/test/java/com/android/tools/r8/shaking/constructor/InitMatchingTest.java
new file mode 100644
index 0000000..a7fa4c2
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/constructor/InitMatchingTest.java
@@ -0,0 +1,188 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.constructor;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.ProguardTestCompileResult;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+class InitMatchingTestClass {
+  // Trivial <clinit>
+  static {
+  }
+  int field;
+  public InitMatchingTestClass(int arg) {
+    field = arg;
+  }
+}
+
+@RunWith(Parameterized.class)
+public class InitMatchingTest extends TestBase {
+  public static final List<String> INIT_NAMES = ImmutableList.of(
+      "<random>", "<clinit>", "<init>", "<*init>", "<in*>", "<in*", "*init>", "<1>", "<<1>init>",
+      "void <clinit>", "void <init>", "void <*init>", "void <in*>", "void <in*", "void *init>",
+      "void <1>", "void <<1>init>", "public void <init>", "public void <*init>",
+      "public void <clinit>", "static void <clinit>", "XYZ <clinit>", "private XYZ <init>"
+  );
+
+  @Parameterized.Parameters(name = "{0} \"{1}\"")
+  public static Collection<Object[]> data() {
+    return buildParameters(ToolHelper.getBackends(), INIT_NAMES);
+  }
+
+  private final Backend backend;
+  private final String initName;
+
+  public InitMatchingTest(Backend backend, String initName) {
+    this.backend = backend;
+    this.initName = initName;
+  }
+
+  private String createKeepRule() {
+    return "-keep class * { " + initName + "(...); }";
+  }
+
+  private static final List<String> ALLOWED_INIT_NAMES_PG = ImmutableList.of(
+      "<init>", "void <clinit>", "void <init>", "void <1>", "public void <init>",
+      "void <*init>", "void <in*>", "void *init>", "public void <*init>",
+      "void <<1>init>", "public void <clinit>", "static void <clinit>",
+      "XYZ <clinit>", "private XYZ <init>");
+  private static final List<String> EFFECTIVE_INIT_NAMES_PG = ImmutableList.of(
+      "<init>", "void <init>", "public void <init>",
+      "void <*init>", "void <in*>", "void *init>", "public void <*init>");
+  private static final List<String> EFFECTIVE_CLINIT_NAMES_PG = ImmutableList.of(
+      "void <clinit>", "static void <clinit>", "void <*init>", "void *init>");
+
+  @BeforeClass
+  public static void checkPGInitNames() {
+    assert INIT_NAMES.containsAll(ALLOWED_INIT_NAMES_PG);
+    assert ALLOWED_INIT_NAMES_PG.containsAll(EFFECTIVE_INIT_NAMES_PG);
+    assert ALLOWED_INIT_NAMES_PG.containsAll(EFFECTIVE_CLINIT_NAMES_PG);
+  }
+
+  @Test
+  public void testProguard() throws Exception {
+    assumeTrue(backend == Backend.CF);
+    ProguardTestCompileResult result;
+    try {
+      result =
+          testForProguard()
+              .addProgramClasses(InitMatchingTestClass.class)
+              .addKeepRules(createKeepRule())
+              .compile();
+      if (!ALLOWED_INIT_NAMES_PG.contains(initName)) {
+        fail("Expect to fail");
+      }
+    } catch (CompilationFailedException e) {
+      assertFalse(ALLOWED_INIT_NAMES_PG.contains(initName));
+      if (initName.equals("void <in*")) {
+        assertThat(e.getMessage(), containsString("Missing closing angular bracket"));
+      } else {
+        assertThat(e.getMessage(),
+            containsString("Expecting type and name instead of just '" + initName + "'"));
+      }
+      return;
+    }
+    result.inspect(this::inspectProguard);
+  }
+
+  private void inspectProguard(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(InitMatchingTestClass.class);
+    assertThat(classSubject, isPresent());
+    MethodSubject init = classSubject.init(ImmutableList.of("int"));
+    if (EFFECTIVE_INIT_NAMES_PG.contains(initName)) {
+      assertThat(init, isPresent());
+    } else {
+      assertThat(init, not(isPresent()));
+    }
+    MethodSubject clinit = classSubject.clinit();
+    if (EFFECTIVE_CLINIT_NAMES_PG.contains(initName)) {
+      assertThat(clinit, isPresent());
+    } else {
+      assertThat(clinit, not(isPresent()));
+    }
+  }
+
+  // "[[access-flag]* void] <[cl]init>" is the only valid format. Plus legitimate back-references.
+  public static final List<String> ALLOWED_INIT_NAMES = ImmutableList.of(
+      "<clinit>", "<init>", "void <clinit>", "void <init>", "void <1>",
+      "public void <init>", "public void <clinit>", "static void <clinit>");
+  private static final List<String> EFFECTIVE_INIT_NAMES = ImmutableList.of(
+      "<init>", "void <init>", "public void <init>");
+  private static final List<String> EFFECTIVE_CLINIT_NAMES = ImmutableList.of(
+      "<clinit>", "void <clinit>", "static void <clinit>");
+
+  @BeforeClass
+  public static void checkR8InitNames() {
+    assert INIT_NAMES.containsAll(ALLOWED_INIT_NAMES);
+    assert ALLOWED_INIT_NAMES.containsAll(EFFECTIVE_INIT_NAMES);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    R8TestCompileResult result;
+    try {
+      result =
+          testForR8(backend)
+              .addProgramClasses(InitMatchingTestClass.class)
+              .addKeepRules(createKeepRule())
+              .compile();
+      if (!ALLOWED_INIT_NAMES.contains(initName)) {
+        fail("Expect to fail");
+      }
+    } catch (CompilationFailedException e) {
+      assertFalse(ALLOWED_INIT_NAMES.contains(initName));
+      if (initName.contains("XYZ")) {
+        assertThat(e.getCause().getMessage(),
+            containsString("Expected [access-flag]* void "));
+        return;
+      }
+      assertThat(e.getCause().getMessage(),
+          containsString("Unexpected character '" + (initName.contains("<") ? "<" : ">") + "'"));
+      assertThat(e.getCause().getMessage(),
+          containsString("only allowed in the method name '<init>'"));
+      return;
+    }
+    result
+        .assertNoMessages()
+        .inspect(this::inspectR8);
+  }
+
+  private void inspectR8(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(InitMatchingTestClass.class);
+    assertThat(classSubject, isPresent());
+    MethodSubject init = classSubject.init(ImmutableList.of("int"));
+    if (EFFECTIVE_INIT_NAMES.contains(initName)) {
+      assertThat(init, isPresent());
+    } else {
+      assertThat(init, not(isPresent()));
+    }
+    MethodSubject clinit = classSubject.clinit();
+    if (EFFECTIVE_CLINIT_NAMES.contains(initName)) {
+      assertThat(clinit, isPresent());
+    } else {
+      assertThat(clinit, not(isPresent()));
+    }
+  }
+}