Version 2.0.15

Cherry-pick: Fix version test when we are on a release branch
CL: https://r8-review.googlesource.com/47564

Cherry-pick: Reland "Do not rewrite generic signatures in target of
merged classes"
CL: https://r8-review.googlesource.com/47512

Cherry-pick: Do not classinline when root is a static get and is used with a monitor
CL: https://r8-review.googlesource.com/47280

Cherry-pick: Allow for '.' for patterns when parsing -keepattributes
CL: https://r8-review.googlesource.com/47321

Cherry-pick: Fix proguard configuration parser test to allow classname in attribute
CL: https://r8-review.googlesource.com/47361

Bug: 147386014
Bug: 147411673
Bug: 147470785
Change-Id: If56d14012b6121ab76416ea7d8a8dda78d02136e
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 49c5a10..c47bc81 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 = "2.0.14";
+  public static final String LABEL = "2.0.15";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
index bf06bc1..8d178a3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerEligibilityInfo.java
@@ -21,9 +21,14 @@
    */
   final OptionalBool returnsReceiver;
 
+  final boolean hasMonitorOnReceiver;
+
   public ClassInlinerEligibilityInfo(
-      List<Pair<Invoke.Type, DexMethod>> callsReceiver, OptionalBool returnsReceiver) {
+      List<Pair<Invoke.Type, DexMethod>> callsReceiver,
+      OptionalBool returnsReceiver,
+      boolean hasMonitorOnReceiver) {
     this.callsReceiver = callsReceiver;
     this.returnsReceiver = returnsReceiver;
+    this.hasMonitorOnReceiver = hasMonitorOnReceiver;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index d4d43f4..452daab 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -879,6 +879,19 @@
       return null;
     }
 
+    if (root.isStaticGet()) {
+      // If we are class inlining a singleton instance from a static-get, then we don't know the
+      // value of the fields.
+      ParameterUsage receiverUsage = optimizationInfo.getParameterUsages(0);
+      if (receiverUsage == null || receiverUsage.hasFieldRead) {
+        return null;
+      }
+      if (eligibility.hasMonitorOnReceiver) {
+        // We will not be able to remove the monitor instruction afterwards.
+        return null;
+      }
+    }
+
     // If the method returns receiver and the return value is actually
     // used in the code we need to make some additional checks.
     if (!eligibilityAcceptanceCheck.test(eligibility)) {
@@ -1021,6 +1034,10 @@
       }
     }
 
+    if (parameterUsage.isUsedInMonitor) {
+      return !root.isStaticGet();
+    }
+
     if (!Sets.difference(parameterUsage.ifZeroTest, ALLOWED_ZERO_TEST_TYPES).isEmpty()) {
       // Used in unsupported zero-check-if kinds.
       return false;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 1fb2e0c..8238723 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -166,12 +166,14 @@
 
     List<Pair<Invoke.Type, DexMethod>> callsReceiver = new ArrayList<>();
     boolean seenSuperInitCall = false;
+    boolean seenMonitor = false;
     for (Instruction insn : receiver.aliasedUsers()) {
       if (insn.isAssume()) {
         continue;
       }
 
       if (insn.isMonitor()) {
+        seenMonitor = true;
         continue;
       }
 
@@ -246,11 +248,15 @@
       return;
     }
 
+    boolean synchronizedVirtualMethod =
+        method.accessFlags.isSynchronized() && method.isVirtualMethod();
+
     feedback.setClassInlinerEligibility(
         method,
         new ClassInlinerEligibilityInfo(
             callsReceiver,
-            new ClassInlinerReceiverAnalysis(appView, method, code).computeReturnsReceiver()));
+            new ClassInlinerReceiverAnalysis(appView, method, code).computeReturnsReceiver(),
+            seenMonitor || synchronizedVirtualMethod));
   }
 
   private void identifyParameterUsages(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
index ef87dcf..e911307 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ParameterUsagesInfo.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.Monitor;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.utils.ListUtils;
@@ -86,6 +87,9 @@
     // If this argument is returned: return arg.
     public final boolean isReturned;
 
+    // If this argument is used in a monitor instruction.
+    public final boolean isUsedInMonitor;
+
     ParameterUsage(
         int index,
         Set<Type> ifZeroTest,
@@ -93,7 +97,8 @@
         boolean hasFieldAssignment,
         boolean hasFieldRead,
         boolean isAssignedToField,
-        boolean isReturned) {
+        boolean isReturned,
+        boolean isUsedInMonitor) {
       this.index = index;
       this.ifZeroTest =
           ifZeroTest.isEmpty() ? Collections.emptySet() : ImmutableSet.copyOf(ifZeroTest);
@@ -103,6 +108,7 @@
       this.hasFieldRead = hasFieldRead;
       this.isAssignedToField = isAssignedToField;
       this.isReturned = isReturned;
+      this.isUsedInMonitor = isUsedInMonitor;
     }
 
     static ParameterUsage copyAndShift(ParameterUsage original, int shift) {
@@ -114,7 +120,8 @@
           original.hasFieldAssignment,
           original.hasFieldRead,
           original.isAssignedToField,
-          original.isReturned);
+          original.isReturned,
+          original.isUsedInMonitor);
     }
 
     public boolean notUsed() {
@@ -123,7 +130,8 @@
           && !hasFieldAssignment
           && !hasFieldRead
           && !isAssignedToField
-          && !isReturned;
+          && !isReturned
+          && !isUsedInMonitor;
     }
   }
 
@@ -138,6 +146,7 @@
     private boolean hasFieldRead = false;
     private boolean isAssignedToField = false;
     private boolean isReturned = false;
+    private boolean isUsedInMonitor = false;
 
     ParameterUsageBuilder(Value arg, int index) {
       this.arg = arg;
@@ -166,6 +175,9 @@
       if (instruction.isReturn()) {
         return note(instruction.asReturn());
       }
+      if (instruction.isMonitor()) {
+        return note(instruction.asMonitor());
+      }
       return false;
     }
 
@@ -177,7 +189,8 @@
           hasFieldAssignment,
           hasFieldRead,
           isAssignedToField,
-          isReturned);
+          isReturned,
+          isUsedInMonitor);
     }
 
     private boolean note(If ifInstruction) {
@@ -231,5 +244,12 @@
       isReturned = true;
       return true;
     }
+
+    private boolean note(Monitor monitorInstruction) {
+      assert monitorInstruction.inValues().size() == 1;
+      assert monitorInstruction.inValues().get(0).getAliasedValue() == arg;
+      isUsedInMonitor = true;
+      return true;
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index d0240e9..d13cc3b 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -24,12 +25,14 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
+import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -132,7 +135,15 @@
     timing.end();
 
     timing.begin("rename-generic");
-    new GenericSignatureRewriter(appView, renaming).run(classes);
+    new GenericSignatureRewriter(appView, renaming)
+        .run(
+            new Iterable<DexProgramClass>() {
+              @Override
+              public Iterator<DexProgramClass> iterator() {
+                return IteratorUtils.<DexClass, DexProgramClass>filter(
+                    classes.iterator(), DexClass::isProgramClass);
+              }
+            });
     timing.end();
 
     timing.begin("rename-arrays");
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java
index 6753e06..7cb3506 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java
@@ -9,11 +9,17 @@
  */
 public interface GenericSignatureAction<T> {
 
+  enum ParserPosition {
+    CLASS_SUPER_OR_INTERFACE_ANNOTATION,
+    ENCLOSING_INNER_OR_TYPE_ANNOTATION,
+    MEMBER_ANNOTATION
+  }
+
   public void parsedSymbol(char symbol);
 
   public void parsedIdentifier(String identifier);
 
-  public T parsedTypeName(String name);
+  public T parsedTypeName(String name, ParserPosition isTopLevel);
 
   public T parsedInnerTypeName(T enclosingType, String name);
 
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
index c1f6a7d..dc0aa8c 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.naming.signature;
 
+import com.android.tools.r8.naming.signature.GenericSignatureAction.ParserPosition;
 import java.lang.reflect.GenericSignatureFormatError;
 import java.nio.CharBuffer;
 
@@ -50,7 +51,7 @@
  */
 public class GenericSignatureParser<T> {
 
-  private final GenericSignatureAction<T> actions;
+  private GenericSignatureAction<T> actions;
 
   /*
    * Parser:
@@ -110,7 +111,7 @@
     try {
       actions.start();
       setInput(signature);
-      parseFieldTypeSignature();
+      parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION);
       actions.stop();
     } catch (GenericSignatureFormatError e) {
       throw e;
@@ -141,11 +142,11 @@
     parseOptFormalTypeParameters();
 
     // SuperclassSignature ::= ClassTypeSignature.
-    parseClassTypeSignature();
+    parseClassTypeSignature(ParserPosition.CLASS_SUPER_OR_INTERFACE_ANNOTATION);
 
     while (symbol > 0) {
       // SuperinterfaceSignature ::= ClassTypeSignature.
-      parseClassTypeSignature();
+      parseClassTypeSignature(ParserPosition.CLASS_SUPER_OR_INTERFACE_ANNOTATION);
     }
   }
 
@@ -178,28 +179,28 @@
     expect(':');
 
     if (symbol == 'L' || symbol == '[' || symbol == 'T') {
-      parseFieldTypeSignature();
+      parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION);
     }
 
     while (symbol == ':') {
       // InterfaceBound ::= ":" FieldTypeSignature.
       actions.parsedSymbol(symbol);
       scanSymbol();
-      parseFieldTypeSignature();
+      parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION);
     }
   }
 
-  private void parseFieldTypeSignature() {
+  private void parseFieldTypeSignature(ParserPosition parserPosition) {
     // FieldTypeSignature ::= ClassTypeSignature | ArrayTypeSignature | TypeVariableSignature.
     switch (symbol) {
       case 'L':
-        parseClassTypeSignature();
+        parseClassTypeSignature(parserPosition);
         break;
       case '[':
-        // ArrayTypeSignature ::= "[" TypSignature.
+        // ArrayTypeSignature ::= "[" TypeSignature.
         actions.parsedSymbol(symbol);
         scanSymbol();
-        updateTypeSignature();
+        updateTypeSignature(parserPosition);
         break;
       case 'T':
         updateTypeVariableSignature();
@@ -209,7 +210,7 @@
     }
   }
 
-  private void parseClassTypeSignature() {
+  private void parseClassTypeSignature(ParserPosition parserPosition) {
     // ClassTypeSignature ::= "L" {Ident "/"} Ident OptTypeArguments {"." Ident OptTypeArguments}
     //  ";".
     actions.parsedSymbol(symbol);
@@ -226,18 +227,22 @@
     }
 
     qualIdent.append(this.identifier);
-    T parsedEnclosingType = actions.parsedTypeName(qualIdent.toString());
+    T parsedEnclosingType = actions.parsedTypeName(qualIdent.toString(), parserPosition);
 
-    updateOptTypeArguments();
-
-    while (symbol == '.') {
-      // Deal with Member Classes:
-      actions.parsedSymbol(symbol);
-      scanSymbol();
-      scanIdentifier();
-      assert identifier != null;
-      parsedEnclosingType = actions.parsedInnerTypeName(parsedEnclosingType, identifier);
+    if (parsedEnclosingType != null) {
+      // We should only parse any optional type arguments and member classes if we have not merged
+      // the class into the current subtype.
       updateOptTypeArguments();
+
+      while (symbol == '.') {
+        // Deal with Member Classes.
+        actions.parsedSymbol(symbol);
+        scanSymbol();
+        scanIdentifier();
+        assert identifier != null;
+        parsedEnclosingType = actions.parsedInnerTypeName(parsedEnclosingType, identifier);
+        updateOptTypeArguments();
+      }
     }
 
     actions.parsedSymbol(symbol);
@@ -268,13 +273,13 @@
     } else if (symbol == '+') {
       actions.parsedSymbol(symbol);
       scanSymbol();
-      parseFieldTypeSignature();
+      parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION);
     } else if (symbol == '-') {
       actions.parsedSymbol(symbol);
       scanSymbol();
-      parseFieldTypeSignature();
+      parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION);
     } else {
-      parseFieldTypeSignature();
+      parseFieldTypeSignature(ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION);
     }
   }
 
@@ -291,7 +296,7 @@
     expect(';');
   }
 
-  private void updateTypeSignature() {
+  private void updateTypeSignature(ParserPosition parserPosition) {
     switch (symbol) {
       case 'B':
       case 'C':
@@ -306,7 +311,7 @@
         break;
       default:
         // Not an elementary type, but a FieldTypeSignature.
-        parseFieldTypeSignature();
+        parseFieldTypeSignature(parserPosition);
     }
   }
 
@@ -319,7 +324,7 @@
     expect('(');
 
     while (symbol != ')' && (symbol > 0)) {
-      updateTypeSignature();
+      updateTypeSignature(ParserPosition.MEMBER_ANNOTATION);
     }
 
     actions.parsedSymbol(symbol);
@@ -336,7 +341,7 @@
         if (symbol == 'T') {
           updateTypeVariableSignature();
         } else {
-          parseClassTypeSignature();
+          parseClassTypeSignature(ParserPosition.MEMBER_ANNOTATION);
         }
       } while (symbol == '^');
     }
@@ -345,7 +350,7 @@
   private void updateReturnType() {
     // ReturnType ::= TypeSignature | "V".
     if (symbol != 'V') {
-      updateTypeSignature();
+      updateTypeSignature(ParserPosition.MEMBER_ANNOTATION);
     } else {
       actions.parsedSymbol(symbol);
       scanSymbol();
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index c3b7188..13c2c43 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -10,8 +10,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.origin.Origin;
@@ -43,15 +43,16 @@
     this.reporter = appView.options().reporter;
   }
 
-  public void run(Iterable<? extends DexClass> classes) {
+  public void run(Iterable<? extends DexProgramClass> classes) {
     final GenericSignatureCollector genericSignatureCollector = new GenericSignatureCollector();
     final GenericSignatureParser<DexType> genericSignatureParser =
         new GenericSignatureParser<>(genericSignatureCollector);
-    // classes may not be the same as appInfo().classes() if applymapping is used on classpath
+    // Classes may not be the same as appInfo().classes() if applymapping is used on classpath
     // arguments. If that is the case, the ProguardMapMinifier will pass in all classes that is
     // either ProgramClass or has a mapping. This is then transitively called inside the
     // ClassNameMinifier.
-    for (DexClass clazz : classes) {
+    for (DexProgramClass clazz : classes) {
+      genericSignatureCollector.setCurrentClassContext(clazz);
       clazz.annotations =
           rewriteGenericSignatures(
               clazz.annotations,
@@ -152,13 +153,22 @@
 
   private class GenericSignatureCollector implements GenericSignatureAction<DexType> {
     private StringBuilder renamedSignature;
+    private DexProgramClass currentClassContext;
 
-    public String getRenamedSignature() {
+    String getRenamedSignature() {
       return renamedSignature.toString();
     }
 
+    void setCurrentClassContext(DexProgramClass clazz) {
+      currentClassContext = clazz;
+    }
+
     @Override
     public void parsedSymbol(char symbol) {
+      if (symbol == ';' && renamedSignature.charAt(renamedSignature.length() - 1) == ';') {
+        // The type was never written (maybe because it was merged with it's subtype)
+        return;
+      }
       renamedSignature.append(symbol);
     }
 
@@ -168,13 +178,24 @@
     }
 
     @Override
-    public DexType parsedTypeName(String name) {
-      DexType type = appView.dexItemFactory().createType(getDescriptorFromClassBinaryName(name));
-      type = appView.graphLense().lookupType(type);
+    public DexType parsedTypeName(String name, ParserPosition parserPosition) {
+      String originalDescriptor = getDescriptorFromClassBinaryName(name);
+      DexType type =
+          appView.graphLense().lookupType(appView.dexItemFactory().createType(originalDescriptor));
       if (appView.appInfo().wasPruned(type)) {
         type = appView.dexItemFactory().objectType;
       }
       DexString renamedDescriptor = renaming.getOrDefault(type, type.descriptor);
+      if (parserPosition == ParserPosition.CLASS_SUPER_OR_INTERFACE_ANNOTATION
+          && currentClassContext != null) {
+        // We may have merged the type down to the current class type.
+        DexString classDescriptor = currentClassContext.type.descriptor;
+        if (!originalDescriptor.equals(classDescriptor.toString())
+            && renamedDescriptor.equals(classDescriptor)) {
+          renamedSignature.deleteCharAt(renamedSignature.length() - 1);
+          return null;
+        }
+      }
       renamedSignature.append(getClassBinaryNameFromDescriptor(renamedDescriptor.toString()));
       return type;
     }
@@ -197,6 +218,7 @@
       type = appView.graphLense().lookupType(type);
       DexString renamedDescriptor = renaming.get(type);
       if (renamedDescriptor != null) {
+        // TODO(b/147504070): If this is a merged class equal to the class context, do not add.
         // Pick the renamed inner class from the fully renamed binary name.
         String fullRenamedBinaryName =
             getClassBinaryNameFromDescriptor(renamedDescriptor.toString());
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 ab9e92f..a589f9a 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -1803,7 +1803,8 @@
           codePoint ->
               IdentifierUtils.isDexIdentifierPart(codePoint)
                   || codePoint == '!'
-                  || codePoint == '*');
+                  || codePoint == '*'
+                  || codePoint == '.');
     }
 
     private String acceptString(Predicate<Integer> codepointAcceptor) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index ae8eb7e..ab32bd5 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -383,7 +383,7 @@
       }
     }
     if (clazz.getEnclosingMethod() != null || !clazz.getInnerClasses().isEmpty()) {
-      // TODO(herhut): Consider supporting merging of enclosing-method and inner-class attributes.
+      // TODO(b/147504070): Consider merging of enclosing-method and inner-class attributes.
       if (Log.ENABLED) {
         AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(clazz);
       }
@@ -440,7 +440,7 @@
       return false;
     }
     if (targetClass.getEnclosingMethod() != null || !targetClass.getInnerClasses().isEmpty()) {
-      // TODO(herhut): Consider supporting merging of enclosing-method and inner-class attributes.
+      // TODO(b/147504070): Consider merging of enclosing-method and inner-class attributes.
       if (Log.ENABLED) {
         AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(clazz);
       }
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
index 7ffce35..f38c02a 100644
--- a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -16,7 +16,8 @@
 
 public class IteratorUtils {
 
-  public static <T, S extends T> Iterator<S> filter(Iterator<T> iterator, Predicate<T> predicate) {
+  public static <T, S extends T> Iterator<S> filter(
+      Iterator<? extends T> iterator, Predicate<T> predicate) {
     return new Iterator<S>() {
 
       private S next = advance();
diff --git a/src/test/java/com/android/tools/r8/VersionTests.java b/src/test/java/com/android/tools/r8/VersionTests.java
index dffaa2b..e32841c 100644
--- a/src/test/java/com/android/tools/r8/VersionTests.java
+++ b/src/test/java/com/android/tools/r8/VersionTests.java
@@ -61,7 +61,13 @@
 
   @Test
   public void testDevelopmentPredicate() {
-    assertEquals(LABEL.equals("master") || LABEL.contains("-dev"), Version.isDevelopmentVersion());
+    if (LABEL.equals("master") || LABEL.contains("-dev")) {
+      assertTrue(Version.isDevelopmentVersion());
+    } else {
+      // This is a release branch, but Version.isDevelopmentVersion will still return true
+      // since this is not the release archive with the r8-version.properties file.
+      assertFalse(Version.isDevelopmentVersion(LABEL, false));
+    }
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerStaticGetDirectMonitorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerStaticGetDirectMonitorTest.java
new file mode 100644
index 0000000..f6252ea
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerStaticGetDirectMonitorTest.java
@@ -0,0 +1,114 @@
+// Copyright (c) 2020, 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.classinliner;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Currently, the class-inliner will not inline if the root is used in a monitor. This test just
+ * ensures, that if that ever changes, the monitor instructions will not be removed.
+ */
+@RunWith(Parameterized.class)
+public class ClassInlinerStaticGetDirectMonitorTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInlinerStaticGetDirectMonitorTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInlinerStaticGetDirectMonitorTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("20000");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertTrue(
+        inspector
+            .clazz(TestClass.class)
+            .uniqueMethodWithName("produce1")
+            .streamInstructions()
+            .anyMatch(InstructionSubject::isMonitorEnter));
+    assertTrue(
+        inspector
+            .clazz(TestClass.class)
+            .uniqueMethodWithName("produce2")
+            .streamInstructions()
+            .anyMatch(InstructionSubject::isMonitorExit));
+  }
+
+  static class TestClass {
+
+    private static volatile Thread t1 = new Thread(TestClass::produce1);
+    private static volatile Thread t2 = new Thread(TestClass::produce2);
+
+    @NeverInline
+    static void produce1() {
+      Container instance = Container.getInstance();
+      for (int i = 0; i < 10000; i++) {
+        synchronized (instance) {
+          instance.increment();
+        }
+      }
+    }
+
+    @NeverInline
+    static void produce2() {
+      Container instance = Container.getInstance();
+      for (int i = 0; i < 10000; i++) {
+        synchronized (instance) {
+          instance.increment();
+        }
+      }
+    }
+
+    public static void main(String[] args) {
+      t1.start();
+      t2.start();
+      while (t1.isAlive() || t2.isAlive()) {}
+      System.out.println(Container.counter);
+    }
+  }
+
+  static class Container {
+
+    static Container INSTANCE = new Container();
+    public static int counter = 0;
+
+    static Container getInstance() {
+      return INSTANCE;
+    }
+
+    @NeverInline
+    final void increment() {
+      counter += 1;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerStaticGetExtraMethodMonitorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerStaticGetExtraMethodMonitorTest.java
new file mode 100644
index 0000000..0e0b260
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerStaticGetExtraMethodMonitorTest.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2020, 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.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * This is a reproduction of b/147411673 where we inline classes and remove monitor instructions.
+ */
+@RunWith(Parameterized.class)
+public class ClassInlinerStaticGetExtraMethodMonitorTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInlinerStaticGetExtraMethodMonitorTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInlinerStaticGetExtraMethodMonitorTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("20000");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(inspector.clazz(Container.class).uniqueMethodWithName("increment"), isPresent());
+  }
+
+  static class TestClass {
+
+    private static volatile Thread t1 = new Thread(TestClass::produce1);
+    private static volatile Thread t2 = new Thread(TestClass::produce2);
+
+    @NeverInline
+    static void produce1() {
+      Container instance = Container.getInstance();
+      for (int i = 0; i < 10000; i++) {
+        synchronizeOnExtraMethod(instance);
+      }
+    }
+
+    @NeverInline
+    static void produce2() {
+      Container instance = Container.getInstance();
+      for (int i = 0; i < 10000; i++) {
+        synchronizeOnExtraMethod(instance);
+      }
+    }
+
+    @NeverInline
+    static void synchronizeOnExtraMethod(Container container) {
+      synchronized (container) {
+        container.increment();
+      }
+    }
+
+    public static void main(String[] args) {
+      t1.start();
+      t2.start();
+      while (t1.isAlive() || t2.isAlive()) {}
+      System.out.println(Container.counter);
+    }
+  }
+
+  static class Container {
+
+    static Container INSTANCE = new Container();
+    public static int counter = 0;
+
+    static Container getInstance() {
+      return INSTANCE;
+    }
+
+    @NeverInline
+    final void increment() {
+      counter += 1;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerStaticGetMonitorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerStaticGetMonitorTest.java
new file mode 100644
index 0000000..216f34e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerStaticGetMonitorTest.java
@@ -0,0 +1,98 @@
+// 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.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * This is a reproduction of b/147411673 where we inline classes and remove monitor instructions.
+ */
+@RunWith(Parameterized.class)
+public class ClassInlinerStaticGetMonitorTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ClassInlinerStaticGetMonitorTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ClassInlinerStaticGetMonitorTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("20000");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    assertThat(inspector.clazz(Container.class).uniqueMethodWithName("increment"), isPresent());
+  }
+
+  static class TestClass {
+
+    private static volatile Thread t1 = new Thread(TestClass::produce1);
+    private static volatile Thread t2 = new Thread(TestClass::produce2);
+
+    @NeverInline
+    static void produce1() {
+      for (int i = 0; i < 10000; i++) {
+        Container.getInstance().increment();
+      }
+    }
+
+    @NeverInline
+    static void produce2() {
+      for (int i = 0; i < 10000; i++) {
+        Container.getInstance().increment();
+      }
+    }
+
+    public static void main(String[] args) {
+      t1.start();
+      t2.start();
+      while (t1.isAlive() || t2.isAlive()) {}
+      System.out.println(Container.counter);
+    }
+  }
+
+  static class Container {
+
+    static Container INSTANCE = new Container();
+    public static int counter = 0;
+
+    static Container getInstance() {
+      return INSTANCE;
+    }
+
+    @NeverInline
+    final void increment() {
+      synchronized (this) {
+        counter += 1;
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java b/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
index b264ef5..4489b75 100644
--- a/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
+++ b/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
@@ -22,6 +22,7 @@
 import org.junit.Test;
 
 public class GenericSignatureParserTest extends TestBase {
+
   private static class ReGenerateGenericSignatureRewriter
       implements GenericSignatureAction<String> {
 
@@ -42,7 +43,7 @@
     }
 
     @Override
-    public String parsedTypeName(String name) {
+    public String parsedTypeName(String name, ParserPosition parserPosition) {
       renamedSignature.append(name);
       return name;
     }
@@ -389,7 +390,7 @@
       }
 
       @Override
-      public String parsedTypeName(String name) {
+      public String parsedTypeName(String name, ParserPosition parserPosition) {
         throw exceptionSupplier.get();
       }
     }
diff --git a/src/test/java/com/android/tools/r8/naming/signature/SignatureOfMergedClassesTest.java b/src/test/java/com/android/tools/r8/naming/signature/SignatureOfMergedClassesTest.java
new file mode 100644
index 0000000..a9fc20f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/SignatureOfMergedClassesTest.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2020, 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.naming.signature;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.signature.merging.I;
+import com.android.tools.r8.naming.signature.merging.ImplI;
+import com.android.tools.r8.naming.signature.merging.ImplK;
+import com.android.tools.r8.naming.signature.merging.InterfaceToKeep;
+import com.android.tools.r8.naming.signature.merging.J;
+import com.android.tools.r8.naming.signature.merging.K;
+import java.io.IOException;
+import java.lang.invoke.LambdaConversionException;
+import java.lang.reflect.Type;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SignatureOfMergedClassesTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SignatureOfMergedClassesTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRemovalOfMergedInterfaceOnSameClass()
+      throws IOException, CompilationFailedException, ExecutionException,
+          LambdaConversionException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(
+            ImplI.class, ImplK.class, I.class, J.class, InterfaceToKeep.class, K.class, Main.class)
+        .addKeepMainRule(Main.class)
+        .addKeepClassRules(InterfaceToKeep.class)
+        .addKeepAttributes("Signature, InnerClasses, EnclosingMethod, *Annotation*")
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .addOptionsModification(
+            internalOptions -> {
+              internalOptions.enableUnusedInterfaceRemoval = false;
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            "ImplI.foo",
+            "ImplI: com.android.tools.r8.naming.signature.merging.InterfaceToKeep<java.lang.Void>",
+            "K: com.android.tools.r8.naming.signature.merging.InterfaceToKeep<java.lang.Void>",
+            "ImplK.foo",
+            "ImplK.bar",
+            "ImplK: interface com.android.tools.r8.naming.signature.merging.K")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(I.class), not(isPresent()));
+              assertThat(codeInspector.clazz(J.class), not(isPresent()));
+            });
+  }
+
+  @Test
+  public void testKeepingOneSelfOnInterface()
+      throws ExecutionException, CompilationFailedException, IOException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Foo.class, InterfaceToKeep.class)
+        .addKeepMainRule(Foo.class)
+        .addKeepClassRules(InterfaceToKeep.class)
+        .addKeepAttributes("Signature, InnerClasses, EnclosingMethod, *Annotation*")
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .addOptionsModification(
+            internalOptions -> {
+              internalOptions.enableUnusedInterfaceRemoval = false;
+            })
+        .run(parameters.getRuntime(), Foo.class)
+        .assertSuccessWithOutputLines(
+            "com.android.tools.r8.naming.signature.merging.InterfaceToKeep"
+                + "<com.android.tools.r8.naming.signature.SignatureOfMergedClassesTest$Foo>");
+  }
+
+  public static class Foo implements InterfaceToKeep<Foo> {
+
+    public static void main(String[] args) {
+      for (Type genericInterface : Foo.class.getGenericInterfaces()) {
+        System.out.println(genericInterface);
+      }
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new ImplI().foo();
+      for (Type genericInterface : ImplI.class.getGenericInterfaces()) {
+        System.out.println("ImplI: " + genericInterface);
+      }
+      for (Type genericInterface : K.class.getGenericInterfaces()) {
+        System.out.println("K: " + genericInterface);
+      }
+      K k = new ImplK();
+      k.foo();
+      k.bar();
+      for (Type genericInterface : ImplK.class.getGenericInterfaces()) {
+        System.out.println("ImplK: " + genericInterface);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/I.java b/src/test/java/com/android/tools/r8/naming/signature/merging/I.java
new file mode 100644
index 0000000..237786c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/I.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2020, 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.naming.signature.merging;
+
+public interface I {
+  void foo();
+}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/ImplI.java b/src/test/java/com/android/tools/r8/naming/signature/merging/ImplI.java
new file mode 100644
index 0000000..b842386
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/ImplI.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2020, 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.naming.signature.merging;
+
+public class ImplI implements InterfaceToKeep<Void>, I {
+
+  @Override
+  public void foo() {
+    System.out.println("ImplI.foo");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/ImplK.java b/src/test/java/com/android/tools/r8/naming/signature/merging/ImplK.java
new file mode 100644
index 0000000..3c23d56
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/ImplK.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2020, 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.naming.signature.merging;
+
+public class ImplK implements K {
+
+  @Override
+  public void foo() {
+    System.out.println("ImplK.foo");
+  }
+
+  @Override
+  public void bar() {
+    System.out.println("ImplK.bar");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/InterfaceToKeep.java b/src/test/java/com/android/tools/r8/naming/signature/merging/InterfaceToKeep.java
new file mode 100644
index 0000000..6bebcd0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/InterfaceToKeep.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2020, 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.naming.signature.merging;
+
+public interface InterfaceToKeep<T> {}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/J.java b/src/test/java/com/android/tools/r8/naming/signature/merging/J.java
new file mode 100644
index 0000000..444a5e8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/J.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2020, 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.naming.signature.merging;
+
+public interface J {
+  void foo();
+}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/K.java b/src/test/java/com/android/tools/r8/naming/signature/merging/K.java
new file mode 100644
index 0000000..4b8ed50
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/K.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2020, 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.naming.signature.merging;
+
+public interface K extends InterfaceToKeep<Void>, J {
+  void bar();
+}
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 1a37fc5..a06dea1 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -1407,40 +1407,6 @@
   }
 
   @Test
-  public void parseInvalidKeepattributes_className() throws Exception {
-    List<String> classNames = ImmutableList.of("androidx.annotation.Keep", "**.Keep", "K*<1>p");
-    Path proguardConfig;
-    for (String className : classNames) {
-      reset();
-      proguardConfig = writeTextToTempFile("-keepattributes " + className + ",*Annotations*");
-      try {
-        parser.parse(proguardConfig);
-        fail("Expect to fail due to unsupported attribute.");
-      } catch (AbortException e) {
-        checkDiagnostics(
-            handler.errors,
-            proguardConfig,
-            1,
-            className.contains(".") ? className.indexOf('.') + 17 : className.length() + 13,
-            "Unexpected attribute");
-      }
-      reset();
-      proguardConfig = writeTextToTempFile("-keepattributes *Annotations*," + className);
-      try {
-        parser.parse(proguardConfig);
-        fail("Expect to fail due to unsupported attribute.");
-      } catch (AbortException e) {
-        checkDiagnostics(
-            handler.errors,
-            proguardConfig,
-            1,
-            className.contains(".") ? className.indexOf('.') + 31 : className.length() + 27,
-            "Unexpected attribute");
-      }
-    }
-  }
-
-  @Test
   public void parseUseUniqueClassMemberNames() throws IOException {
     Path proguardConfig = writeTextToTempFile("-useuniqueclassmembernames");
     new ProguardConfigurationParser(new DexItemFactory(), reporter).parse(proguardConfig);
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java
new file mode 100644
index 0000000..97763b1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepAttributesDotsTest.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2020, 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.attributes;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepAttributesDotsTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final String keepAttributes;
+
+  @Parameterized.Parameters(name = "-keepattributes {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withNoneRuntime().build(),
+        new String[] {".", "...", "XYZ,..", "XYZ,..,A.B"});
+  }
+
+  public KeepAttributesDotsTest(TestParameters parameters, String keepAttributes) {
+    this.parameters = parameters;
+    this.keepAttributes = keepAttributes;
+  }
+
+  @Test
+  public void testProguard() throws ExecutionException, CompilationFailedException, IOException {
+    testForProguard()
+        .addProgramClassesAndInnerClasses(Main.class)
+        .addKeepAllClassesRule()
+        .addKeepAttributes(keepAttributes)
+        .addKeepRules("-dontwarn com.android.tools.r8.shaking.attributes.*")
+        .run(TestRuntime.getCheckedInJdk8(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!")
+        .inspect(this::inspect);
+  }
+
+  @Test
+  public void testR8() throws IOException, CompilationFailedException, ExecutionException {
+    testForR8(Backend.CF)
+        .addProgramClassesAndInnerClasses(Main.class)
+        .addKeepAttributes(keepAttributes)
+        .addKeepAllClassesRule()
+        .run(TestRuntime.getCheckedInJdk8(), Main.class)
+        .assertSuccessWithOutputLines("Hello World!")
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(Main.class);
+    assertTrue(clazz.getDexClass().annotations.isEmpty());
+    MethodSubject main = clazz.uniqueMethodWithName("main");
+    assertTrue(main.getMethod().annotations.isEmpty());
+    FieldSubject field = clazz.uniqueFieldWithName("field");
+    assertTrue(field.getField().annotations.isEmpty());
+    assertTrue(clazz.getDexClass().sourceFile == null || clazz.getDexClass().sourceFile.size == 0);
+    assertNull(main.getLineNumberTable());
+    assertTrue(main.getLocalVariableTable().isEmpty());
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD})
+  public @interface MethodRuntimeAnnotation {}
+
+  @Retention(RetentionPolicy.CLASS)
+  @Target({ElementType.METHOD})
+  public @interface MethodCompileTimeAnnotation {}
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.TYPE})
+  public @interface ClassRuntimeAnnotation {}
+
+  @Retention(RetentionPolicy.CLASS)
+  @Target({ElementType.TYPE})
+  public @interface ClassCompileTimeAnnotation {}
+
+  @ClassCompileTimeAnnotation
+  @ClassRuntimeAnnotation
+  public static class Main {
+
+    public static class Inner<T> {}
+
+    public Inner<Boolean> field;
+
+    @MethodCompileTimeAnnotation
+    @MethodRuntimeAnnotation
+    public static void main(String[] args) {
+      System.out.println("Hello World!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 4aec544..1a4dcc16 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -423,7 +423,7 @@
     }
 
     @Override
-    public String parsedTypeName(String name) {
+    public String parsedTypeName(String name, ParserPosition parserPosition) {
       String type = name;
       if (obfuscatedToOriginalMapping != null) {
         String original = mapType(obfuscatedToOriginalMapping, name);