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);