Version 1.4.51

Cherry-pick: Don't class-inline synchronized methods (even for CF).
CL: https://r8-review.googlesource.com/c/r8/+/33867

Cherry-pick: Proguard config parser: Allow space after comma in class name list.
CL: https://r8-review.googlesource.com/c/r8/+/34062

Cherry-pick: Proguard config parser: allow spaces and quotes in class name list.
CL: https://r8-review.googlesource.com/c/r8/+/34080

Bug: 112295630, 124181032
Change-Id: Icd0ba00dfa0dd4bbd5ae7f6ef3d8a9b08a9c35ec
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index a6b94b0..ed6b403 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.4.50";
+  public static final String LABEL = "1.4.51";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index e723a55..3861c66 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1037,8 +1037,9 @@
     //      that it is not the instance being initialized.
     //
     boolean instanceInitializer = method.isInstanceInitializer();
-    if (method.accessFlags.isNative() ||
-        (!method.isNonAbstractVirtualMethod() && !instanceInitializer)) {
+    if (method.accessFlags.isNative()
+        || (!method.isNonAbstractVirtualMethod() && !instanceInitializer)
+        || method.accessFlags.isSynchronized()) {
       return;
     }
 
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 c2f4849..eaad9a4 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -1431,11 +1431,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)) {
@@ -1514,9 +1530,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()));
       }
@@ -1528,10 +1552,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() {
@@ -1625,19 +1647,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(',')) {
-        negated = acceptChar('!');
-        builder.addClassName(negated,
-            ProguardTypeMatcher.create(parseClassName(), ClassOrType.CLASS, dexItemFactory));
-        skipWhitespace();
-      }
+      do {
+        parseClassNameAddToBuilder(builder);
+      } while (acceptChar(','));
       return builder.build();
     }
 
@@ -1647,8 +1670,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");
       }
@@ -1835,4 +1865,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/ir/optimize/inliner/InlineSynchronizedTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
new file mode 100644
index 0000000..519c273
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
@@ -0,0 +1,102 @@
+// 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.ir.optimize.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InlineSynchronizedTest extends TestBase {
+
+  private static final List<String> METHOD_NAMES =
+      ImmutableList.of(
+          "println",
+          "normalInlinedSynchronized",
+          "classInlinedSynchronized",
+          "normalInlinedControl",
+          "classInlinedControl");
+
+  @Parameterized.Parameters(name = "Backend: {0}, ClassInlining: {1}")
+  public static Collection data() {
+    return buildParameters(Backend.values(), BooleanUtils.values());
+  }
+
+  private final Backend backend;
+  private final boolean classInlining;
+
+  public InlineSynchronizedTest(Backend backend, boolean classInlining) {
+    this.backend = backend;
+    this.classInlining = classInlining;
+  }
+
+  private void configure(InternalOptions options) {
+    options.enableClassInlining = classInlining;
+  }
+
+  @Test
+  public void test() throws Exception {
+    CodeInspector codeInspector =
+        testForR8(backend)
+            .addProgramClasses(InlineSynchronizedTestClass.class)
+            .addKeepMainRule("com.android.tools.r8.ir.optimize.inliner.InlineSynchronizedTestClass")
+            .addKeepRules("-dontobfuscate")
+            .addOptionsModification(o -> o.enableClassInlining = classInlining)
+            .compile()
+            .inspector();
+
+    ClassSubject classSubject = codeInspector.clazz(InlineSynchronizedTestClass.class);
+    assertThat(classSubject, isPresent());
+    MethodSubject methodSubject = classSubject.mainMethod();
+    Iterator<InstructionSubject> it = methodSubject.iterateInstructions();
+    int[] counts = new int[METHOD_NAMES.size()];
+    while (it.hasNext()) {
+      InstructionSubject instruction = it.next();
+      if (!instruction.isInvoke()) {
+        continue;
+      }
+      DexString invokedName = ((InvokeInstructionSubject) instruction).invokedMethod().name;
+      int idx = METHOD_NAMES.indexOf(invokedName.toString());
+      if (idx >= 0) {
+        ++counts[idx];
+      }
+    }
+    // Synchronized methods can never be inlined.
+    assertCount(counts, "normalInlinedSynchronized", 1);
+    assertCount(counts, "classInlinedSynchronized", 1);
+    // Control methods must be inlined, only the normal one or both, depending on classInlining.
+    assertCount(counts, "normalInlinedControl", 0);
+    assertCount(counts, "classInlinedControl", classInlining ? 0 : 1);
+    // Double check the total.
+    int total = 0;
+    for (int i = 0; i < counts.length; ++i) {
+      total += counts[i];
+    }
+    assertEquals(4, total);
+  }
+
+  private static void assertCount(int counts[], String methodName, int expectedCount) {
+    int idx = METHOD_NAMES.indexOf(methodName);
+    assert idx >= 0;
+    assertEquals(expectedCount, counts[idx]);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTestClass.java
new file mode 100644
index 0000000..4e2cae1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTestClass.java
@@ -0,0 +1,34 @@
+// 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.ir.optimize.inliner;
+
+class InlineSynchronizedTestClass {
+  private synchronized void normalInlinedSynchronized() {
+    System.out.println("InlineSynchronizedTestClass::normalInlinedSynchronized");
+  }
+
+  public synchronized void classInlinedSynchronized() {
+    System.out.println("InlineSynchronizedTestClass::classInlinedSynchronized");
+  }
+
+  private void normalInlinedControl() {
+    System.out.println("InlineSynchronizedTestClass::normalInlinedControl");
+  }
+
+  public void classInlinedControl() {
+    System.out.println("InlineSynchronizedTestClass::classInlinedControl");
+  }
+
+  public static void main(String[] args) {
+    // Test normal inlining.
+    InlineSynchronizedTestClass testClass = new InlineSynchronizedTestClass();
+    testClass.normalInlinedSynchronized();
+    testClass.normalInlinedControl();
+
+    // Test class-inlining.
+    new InlineSynchronizedTestClass().classInlinedSynchronized();
+    new InlineSynchronizedTestClass().classInlinedControl();
+  }
+}
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 26e5356..cac6dd3 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -2399,4 +2399,24 @@
       assertThat(result.stderr, containsString(expectedMessage));
     }
   }
+
+  @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.**\" ,"
+                    + " !**d , '!**e' , \"!**f\" , g , 'h' , \"i\" { ",
+                "<fields>;",
+                "<init>();",
+                "}")));
+    List<ProguardConfigurationRule> rules = parser.getConfig().getRules();
+    assertEquals(1, rules.size());
+    ProguardConfigurationRule rule = rules.get(0);
+    assertEquals(ProguardKeepRuleType.KEEP_CLASS_MEMBERS.toString(), rule.typeString());
+    assertEquals("a.b.c.**,!**d,!**e,!**f,g,h,i", rule.getClassNames().toString());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
index d300a4e..da38a47 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -12,7 +12,6 @@
 
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -45,11 +44,6 @@
     return ImmutableList.of(Shrinker.R8_CF, Shrinker.PROGUARD6, Shrinker.R8);
   }
 
-  private void configure(InternalOptions options) {
-    // Disable inlining, otherwise classes can be pruned away if all their methods are inlined.
-    options.enableInlining = false;
-  }
-
   @Test
   public void ifOnPublic_noPublicClassForIfRule() throws Exception {
     List<String> config = ImmutableList.of(
@@ -64,7 +58,7 @@
         "  public <methods>;",
         "}"
     );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -93,7 +87,7 @@
         "  public <methods>;",
         "}"
     );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -126,7 +120,7 @@
         "  !public <methods>;",
         "}"
     );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -160,7 +154,7 @@
         "  public <methods>;",
         "}"
     );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -192,7 +186,7 @@
         "  !public <methods>;",
         "}"
     );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(publicMethod);