Version 1.2.36

Merge: Regard multiple asterisks in member names as separate wildcards.
CL: https://r8-review.googlesource.com/c/r8/+/24620

and then adjust tests a bit as code inspector refactoring is not there.

Bug: 111974287
Change-Id: I9883fe6bfb63094bcbabbf760648d9798732fcaf
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 4bb9fb2..73f0611 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.2.35";
+  public static final String LABEL = "1.2.36";
 
   private Version() {
   }
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 c4276dc..c4e6251 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -1194,6 +1194,7 @@
     private IdentifierPatternWithWildcards acceptIdentifierWithBackreference(IdentifierType kind) {
       ImmutableList.Builder<ProguardWildcard> wildcardsCollector = ImmutableList.builder();
       StringBuilder currentAsterisks = null;
+      int asteriskCount = 0;
       StringBuilder currentBackreference = null;
       skipWhitespace();
       int start = position;
@@ -1235,19 +1236,35 @@
           }
         } else if (currentAsterisks != null) {
           if (current == '*') {
+            // only '*', '**', and '***' are allowed.
+            // E.g., '****' should be regarded as two separate wildcards (e.g., '***' and '*')
+            if (asteriskCount >= 3) {
+              wildcardsCollector.add(new ProguardWildcard.Pattern(currentAsterisks.toString()));
+              currentAsterisks = new StringBuilder();
+              asteriskCount = 0;
+            }
             currentAsterisks.append((char) current);
+            asteriskCount++;
             end += Character.charCount(current);
             continue;
           } else {
             wildcardsCollector.add(new ProguardWildcard.Pattern(currentAsterisks.toString()));
             currentAsterisks = null;
+            asteriskCount = 0;
           }
         }
         // From now on, neither in asterisk collecting state nor back reference collecting state.
         assert currentAsterisks == null && currentBackreference == null;
         if (current == '*') {
-          currentAsterisks = new StringBuilder();
-          currentAsterisks.append((char) current);
+          if (kind == IdentifierType.CLASS_NAME) {
+            // '**' and '***' are only allowed in type name.
+            currentAsterisks = new StringBuilder();
+            currentAsterisks.append((char) current);
+            asteriskCount = 1;
+          } else {
+            // For member names, regard '**' or '***' as separate single-asterisk wildcards.
+            wildcardsCollector.add(new ProguardWildcard.Pattern(String.valueOf((char) current)));
+          }
           end += Character.charCount(current);
         } else if (current == '?' || current == '%') {
           wildcardsCollector.add(new ProguardWildcard.Pattern(String.valueOf((char) current)));
diff --git a/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
new file mode 100644
index 0000000..8290521
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
@@ -0,0 +1,176 @@
+// Copyright (c) 2018, 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;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isRenamed;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+class B111974287 {
+  B111974287 self;
+  B111974287[] clones;
+
+  B111974287() {
+    self = this;
+    clones = new B111974287[1];
+    clones[0] = self;
+  }
+
+  B111974287 fooX() {
+    System.out.println("fooX");
+    return self;
+  }
+
+  B111974287 fooYY() {
+    System.out.println("fooYY");
+    return self;
+  }
+
+  B111974287 fooZZZ() {
+    System.out.println("fooZZZ");
+    return self;
+  }
+}
+
+@RunWith(Parameterized.class)
+public class AsterisksTest extends ProguardCompatabilityTestBase {
+  private final static List<Class> CLASSES = ImmutableList.of(B111974287.class);
+  private final Shrinker shrinker;
+
+  public AsterisksTest(Shrinker shrinker) {
+    this.shrinker = shrinker;
+  }
+
+  @Parameters(name = "shrinker: {0}")
+  public static Collection<Object> data() {
+    return ImmutableList.of(Shrinker.PROGUARD6, Shrinker.R8);
+  }
+
+  @Test
+  public void doubleAsterisksInField() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **." + B111974287.class.getSimpleName() + "{",
+        "  ** **;",
+        "}"
+    );
+    DexInspector codeInspector = runShrinker(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject, not(isRenamed()));
+    FieldSubject fieldSubject = classSubject.field(B111974287.class.getTypeName(), "self");
+    assertThat(fieldSubject, isPresent());
+    assertThat(fieldSubject, not(isRenamed()));
+    fieldSubject = classSubject.field(B111974287.class.getTypeName() + "[]", "clones");
+    // TODO(b/111974287): Proguard6 kept and renamed the field with array type.
+    if (shrinker == Shrinker.PROGUARD6) {
+      return;
+    }
+    assertThat(fieldSubject, not(isPresent()));
+  }
+
+  @Test
+  public void doubleAsterisksInMethod() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **." + B111974287.class.getSimpleName() + "{",
+        "  ** foo**(...);",
+        "}"
+    );
+    DexInspector codeInspector = runShrinker(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject, not(isRenamed()));
+    DexClass clazz = classSubject.getDexClass();
+    assertEquals(3, clazz.virtualMethods().length);
+    for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
+      assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
+      MethodSubject methodSubject =
+          classSubject.method(MethodSignature.fromDexMethod(encodedMethod.method));
+      assertThat(methodSubject, isPresent());
+      assertThat(methodSubject, not(isRenamed()));
+    }
+  }
+
+  @Test
+  public void tripleAsterisksInField() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **." + B111974287.class.getSimpleName() + "{",
+        "  *** ***;",
+        "}"
+    );
+    DexInspector codeInspector = runShrinker(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject, not(isRenamed()));
+    FieldSubject fieldSubject = classSubject.field(B111974287.class.getTypeName(), "self");
+    assertThat(fieldSubject, isPresent());
+    assertThat(fieldSubject, not(isRenamed()));
+    fieldSubject = classSubject.field(B111974287.class.getTypeName() + "[]", "clones");
+    assertThat(fieldSubject, isPresent());
+    assertThat(fieldSubject, not(isRenamed()));
+  }
+
+  @Test
+  public void tripleAsterisksInMethod() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **." + B111974287.class.getSimpleName() + "{",
+        "  *** foo***(...);",
+        "}"
+    );
+    DexInspector codeInspector = runShrinker(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject, not(isRenamed()));
+    DexClass clazz = classSubject.getDexClass();
+    assertEquals(3, clazz.virtualMethods().length);
+    for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
+      assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
+      MethodSubject methodSubject =
+          classSubject.method(MethodSignature.fromDexMethod(encodedMethod.method));
+      assertThat(methodSubject, isPresent());
+      assertThat(methodSubject, not(isRenamed()));
+    }
+  }
+
+  @Test
+  public void quadrupleAsterisksInType() throws Exception {
+    List<String> config = ImmutableList.of(
+        "-keep class **** {",
+        "  **** foo***(...);",
+        "}"
+    );
+    DexInspector codeInspector = runShrinker(shrinker, CLASSES, config);
+    ClassSubject classSubject = codeInspector.clazz(B111974287.class);
+    assertThat(classSubject, isPresent());
+    assertThat(classSubject, not(isRenamed()));
+    DexClass clazz = classSubject.getDexClass();
+    assertEquals(3, clazz.virtualMethods().length);
+    for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
+      assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
+      MethodSubject methodSubject =
+          classSubject.method(MethodSignature.fromDexMethod(encodedMethod.method));
+      assertThat(methodSubject, isPresent());
+      assertThat(methodSubject, not(isRenamed()));
+    }
+  }
+
+}