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