Add support for negated names in member rules
RELNOTES: Support for negating member name patterns
This extend the configuration language such that it is possible to match
on negated member name patterns.
Example: It is now possible to match all methods that don't end in
"ForTesting" using the following rule:
-keepclassmembers class com.example.MyClass {
*** !*ForTesting(...);
}
Member name patterns can also be negated in the precondition of -if
rules. If a negated member name pattern contains wildcards, such
wildcards cannot be back-referenced in the -if consequent rule.
Bug: b/484870212
Change-Id: I87f23a2238404747917d8e6a02545bc0a7f740f8
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 f99f630..1be2931 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -889,7 +889,7 @@
Lists.newArrayList(
ProguardMemberRule.builder()
.setName(
- IdentifierPatternWithWildcards.withoutWildcards(
+ new IdentifierPatternWithWildcardsAndNegation(
Constants.INSTANCE_INITIALIZER_NAME))
.setRuleType(ProguardMemberType.INIT)
.setArguments(Collections.emptyList())
@@ -999,7 +999,19 @@
+ "(only seen " + patterns.size() + " at this point).",
origin, getPosition()));
}
- backReference.setReference(patterns.get(backReference.referenceIndex - 1));
+ ProguardWildcard.Pattern referencedPattern =
+ patterns.get(backReference.referenceIndex - 1);
+ if (referencedPattern.isNonReferenceable()) {
+ throw reporter.fatalError(
+ new StringDiagnostic(
+ "Wildcard <"
+ + backReference.referenceIndex
+ + "> is invalid "
+ + "(cannot reference negated matches).",
+ origin,
+ getPosition()));
+ }
+ backReference.setReference(referencedPattern);
} else {
assert wildcard.isPattern();
if (!backReferenceStarted) {
@@ -1367,19 +1379,19 @@
ruleBuilder.setRuleType(ProguardMemberType.ALL_FIELDS);
} else if (acceptString("<init>")) {
ruleBuilder.setRuleType(ProguardMemberType.INIT);
- ruleBuilder.setName(IdentifierPatternWithWildcards.withoutWildcards("<init>"));
+ ruleBuilder.setName(new IdentifierPatternWithWildcardsAndNegation("<init>"));
ruleBuilder.setArguments(parseArgumentList(allowValueSpecification, ruleBuilder));
} else if (acceptString("<clinit>")) {
ruleBuilder.setRuleType(ProguardMemberType.CLINIT);
- ruleBuilder.setName(IdentifierPatternWithWildcards.withoutWildcards("<clinit>"));
+ ruleBuilder.setName(new IdentifierPatternWithWildcardsAndNegation("<clinit>"));
ruleBuilder.setArguments(parseArgumentList(allowValueSpecification, ruleBuilder));
} else {
TextPosition firstStart = getPosition();
- IdentifierPatternWithWildcards first =
- acceptIdentifierWithBackreference(IdentifierType.ANY);
+ IdentifierPatternWithWildcardsAndNegation first =
+ acceptIdentifierWithBackreference(IdentifierType.ANY, false);
if (first != null) {
skipWhitespace();
- if (first.pattern.equals("*") && hasNextChar(';')) {
+ if (first.getStringPattern().equals("*") && hasNextChar(';')) {
ruleBuilder.setRuleType(ProguardMemberType.ALL);
} else {
// No return type present, only method name, most likely constructors.
@@ -1392,49 +1404,52 @@
} else {
if (acceptString("<init>")) {
ProguardTypeMatcher typeMatcher =
- ProguardTypeMatcher.create(first, ClassOrType.TYPE, dexItemFactory);
+ ProguardTypeMatcher.create(
+ first.getNonNegatedPattern(), ClassOrType.TYPE, dexItemFactory);
if (!typeMatcher.hasSpecificType() || !typeMatcher.getSpecificType().isVoidType()) {
throw parseError("Expected [access-flag]* void <init>");
}
ruleBuilder.setRuleType(ProguardMemberType.INIT);
- ruleBuilder.setName(IdentifierPatternWithWildcards.withoutWildcards("<init>"));
+ ruleBuilder.setName(new IdentifierPatternWithWildcardsAndNegation("<init>"));
ruleBuilder.setTypeMatcher(typeMatcher);
ruleBuilder.setArguments(parseArgumentList(allowValueSpecification, ruleBuilder));
} else if (acceptString("<clinit>")) {
ProguardTypeMatcher typeMatcher =
- ProguardTypeMatcher.create(first, ClassOrType.TYPE, dexItemFactory);
+ ProguardTypeMatcher.create(
+ first.getNonNegatedPattern(), ClassOrType.TYPE, dexItemFactory);
if (!typeMatcher.hasSpecificType() || !typeMatcher.getSpecificType().isVoidType()) {
throw parseError("Expected [access-flag]* void <clinit>");
}
ruleBuilder.setRuleType(ProguardMemberType.CLINIT);
- ruleBuilder.setName(IdentifierPatternWithWildcards.withoutWildcards("<clinit>"));
+ ruleBuilder.setName(new IdentifierPatternWithWildcardsAndNegation("<clinit>"));
ruleBuilder.setTypeMatcher(typeMatcher);
ruleBuilder.setArguments(parseArgumentList(allowValueSpecification, ruleBuilder));
} else {
TextPosition secondStart = getPosition();
- IdentifierPatternWithWildcards second =
- acceptIdentifierWithBackreference(IdentifierType.ANY);
+ IdentifierPatternWithWildcardsAndNegation second =
+ acceptIdentifierWithBackreference(IdentifierType.ANY, true);
if (second != null) {
skipWhitespace();
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()) {
+ if (!second.pattern.hasBackreference()
+ || second.pattern.hasUnusualCharacters()) {
checkConstructorPattern(second, secondStart);
}
ruleBuilder.setRuleType(ProguardMemberType.METHOD);
ruleBuilder.setName(second);
- ruleBuilder
- .setTypeMatcher(
- ProguardTypeMatcher.create(first, ClassOrType.TYPE, dexItemFactory));
+ ruleBuilder.setTypeMatcher(
+ ProguardTypeMatcher.create(
+ first.getNonNegatedPattern(), ClassOrType.TYPE, dexItemFactory));
ruleBuilder.setArguments(
parseArgumentList(allowValueSpecification, ruleBuilder));
} else {
ruleBuilder.setRuleType(ProguardMemberType.FIELD);
ruleBuilder.setName(second);
- ruleBuilder
- .setTypeMatcher(
- ProguardTypeMatcher.create(first, ClassOrType.TYPE, dexItemFactory));
+ ruleBuilder.setTypeMatcher(
+ ProguardTypeMatcher.create(
+ first.getNonNegatedPattern(), ClassOrType.TYPE, dexItemFactory));
}
// Parse "return ..." if present.
ruleBuilder.setReturnValue(
@@ -1513,16 +1528,16 @@
}
private void checkConstructorPattern(
- IdentifierPatternWithWildcards pattern, TextPosition position) {
- if (pattern.pattern.equals("<clinit>")) {
+ IdentifierPatternWithWildcardsAndNegation pattern, TextPosition position) {
+ if (pattern.getStringPattern().equals("<clinit>")) {
reporter.warning(
new StringDiagnostic("Member rule for <clinit> has no effect.", origin, position));
return;
}
- if (pattern.pattern.contains("<")) {
+ if (pattern.getStringPattern().contains("<")) {
throw parseError("Unexpected character '<' in method name. "
+ "The character '<' is only allowed in the method name '<init>'.", position);
- } else if (pattern.pattern.contains(">")) {
+ } else if (pattern.getStringPattern().contains(">")) {
throw parseError("Unexpected character '>' in method name. "
+ "The character '>' is only allowed in the method name '<init>'.", position);
}
@@ -2016,16 +2031,6 @@
return acceptString(this::isClassName);
}
- private IdentifierPatternWithWildcards acceptIdentifierWithBackreference(IdentifierType kind) {
- IdentifierPatternWithWildcardsAndNegation pattern =
- acceptIdentifierWithBackreference(kind, false);
- if (pattern == null) {
- return null;
- }
- assert !pattern.negated;
- return pattern.patternWithWildcards;
- }
-
private IdentifierPatternWithWildcardsAndNegation acceptIdentifierWithBackreference(
IdentifierType kind, boolean allowNegation) {
ImmutableList.Builder<ProguardWildcard> wildcardsCollector = ImmutableList.builder();
@@ -2080,7 +2085,8 @@
// only '*', '**', and '***' are allowed.
// E.g., '****' should be regarded as two separate wildcards (e.g., '***' and '*')
if (asteriskCount >= 3) {
- wildcardsCollector.add(new ProguardWildcard.Pattern(currentAsterisks.toString()));
+ wildcardsCollector.add(
+ ProguardWildcard.Pattern.create(currentAsterisks.toString(), negated));
currentAsterisks = new StringBuilder();
asteriskCount = 0;
}
@@ -2089,7 +2095,8 @@
end += Character.charCount(current);
continue;
} else {
- wildcardsCollector.add(new ProguardWildcard.Pattern(currentAsterisks.toString()));
+ wildcardsCollector.add(
+ ProguardWildcard.Pattern.create(currentAsterisks.toString(), negated));
currentAsterisks = null;
asteriskCount = 0;
}
@@ -2104,11 +2111,13 @@
asteriskCount = 1;
} else {
// For member names, regard '**' or '***' as separate single-asterisk wildcards.
- wildcardsCollector.add(new ProguardWildcard.Pattern(String.valueOf((char) current)));
+ wildcardsCollector.add(
+ ProguardWildcard.Pattern.create(String.valueOf((char) current), negated));
}
end += Character.charCount(current);
} else if (current == '?' || current == '%') {
- wildcardsCollector.add(new ProguardWildcard.Pattern(String.valueOf((char) current)));
+ wildcardsCollector.add(
+ ProguardWildcard.Pattern.create(String.valueOf((char) current), negated));
end += Character.charCount(current);
} else if (kind == IdentifierType.PACKAGE_NAME
? isPackageName(current)
@@ -2130,7 +2139,8 @@
}
position = quoted ? end + 1 : end;
if (currentAsterisks != null) {
- wildcardsCollector.add(new ProguardWildcard.Pattern(currentAsterisks.toString()));
+ wildcardsCollector.add(
+ ProguardWildcard.Pattern.create(currentAsterisks.toString(), negated));
}
if (kind == IdentifierType.CLASS_NAME && currentBackreference != null) {
// Proguard 6 reports this error message, so try to be compatible.
@@ -2266,7 +2276,7 @@
IdentifierPatternWithWildcardsAndNegation name = parseClassName(true);
builder.addClassName(
name.negated,
- ProguardTypeMatcher.create(name.patternWithWildcards, ClassOrType.CLASS, dexItemFactory));
+ ProguardTypeMatcher.create(name.pattern, ClassOrType.CLASS, dexItemFactory));
}
private ProguardClassNameList parseClassNames() {
@@ -2292,7 +2302,7 @@
private IdentifierPatternWithWildcards parseClassName() {
IdentifierPatternWithWildcardsAndNegation name = parseClassName(false);
assert !name.negated;
- return name.patternWithWildcards;
+ return name.pattern;
}
private IdentifierPatternWithWildcardsAndNegation parseClassName(boolean allowNegation) {
@@ -2442,14 +2452,14 @@
this.wildcards = wildcards;
}
- static IdentifierPatternWithWildcards init() {
- return withoutWildcards("<init>");
- }
-
public static IdentifierPatternWithWildcards withoutWildcards(String pattern) {
return new IdentifierPatternWithWildcards(pattern, ImmutableList.of());
}
+ public List<ProguardWildcard> getWildcards() {
+ return wildcards;
+ }
+
boolean isMatchAllNames() {
return pattern.equals("*");
}
@@ -2482,15 +2492,40 @@
}
}
- static class IdentifierPatternWithWildcardsAndNegation {
- final IdentifierPatternWithWildcards patternWithWildcards;
+ public static class IdentifierPatternWithWildcardsAndNegation {
+ final IdentifierPatternWithWildcards pattern;
final boolean negated;
+ IdentifierPatternWithWildcardsAndNegation(String pattern) {
+ this(pattern, Collections.emptyList());
+ }
+
+ IdentifierPatternWithWildcardsAndNegation(String pattern, List<ProguardWildcard> wildcards) {
+ this(pattern, wildcards, false);
+ }
+
IdentifierPatternWithWildcardsAndNegation(
String pattern, List<ProguardWildcard> wildcards, boolean negated) {
- patternWithWildcards = new IdentifierPatternWithWildcards(pattern, wildcards);
+ this.pattern = new IdentifierPatternWithWildcards(pattern, wildcards);
this.negated = negated;
}
+
+ public IdentifierPatternWithWildcards getNonNegatedPattern() {
+ assert !negated;
+ return pattern;
+ }
+
+ String getStringPattern() {
+ return pattern.pattern;
+ }
+
+ List<ProguardWildcard> getWildcards() {
+ return pattern.getWildcards();
+ }
+
+ boolean isNegated() {
+ return negated;
+ }
}
static class IncludeWorkItem {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
index 60b7ffbf..91f9fc8 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationUtils.java
@@ -8,7 +8,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
+import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcardsAndNegation;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.LongInterval;
@@ -77,7 +77,7 @@
.setAccessFlags(publicStaticFinalFlags)
.setRuleType(ProguardMemberType.FIELD)
.setTypeMatcher(ProguardTypeMatcher.create(factory.intType))
- .setName(IdentifierPatternWithWildcards.withoutWildcards("SDK_INT"))
+ .setName(new IdentifierPatternWithWildcardsAndNegation("SDK_INT"))
.setReturnValue(
new ProguardMemberRuleValue(
new LongInterval(apiLevel.getLevel(), Integer.MAX_VALUE)))
@@ -157,7 +157,7 @@
Collections.singletonList(
ProguardMemberRule.builder()
.setRuleType(ProguardMemberType.INIT)
- .setName(IdentifierPatternWithWildcards.init())
+ .setName(new IdentifierPatternWithWildcardsAndNegation("<init>"))
.setArguments(Collections.emptyList())
.setTypeMatcher(ProguardTypeMatcher.create(factory.voidType))
.build()))
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 740b2eb..4bb37f5 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -14,7 +14,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
+import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcardsAndNegation;
import com.android.tools.r8.shaking.rootset.RootSetBuilder;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.ObjectUtils;
@@ -80,8 +80,8 @@
return this;
}
- public Builder setName(IdentifierPatternWithWildcards identifierPatternWithWildcards) {
- this.name = ProguardNameMatcher.create(identifierPatternWithWildcards);
+ public Builder setName(IdentifierPatternWithWildcardsAndNegation name) {
+ this.name = ProguardNameMatcher.create(name);
return this;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardNameMatcher.java b/src/main/java/com/android/tools/r8/shaking/ProguardNameMatcher.java
index ecd43b1..f4eea02 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardNameMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardNameMatcher.java
@@ -6,8 +6,10 @@
import static com.google.common.base.Predicates.alwaysTrue;
import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
+import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcardsAndNegation;
import com.android.tools.r8.shaking.ProguardWildcard.BackReference;
import com.android.tools.r8.shaking.ProguardWildcard.Pattern;
+import com.android.tools.r8.utils.BooleanUtils;
import com.android.tools.r8.utils.IterableUtils;
import java.util.Collections;
import java.util.List;
@@ -21,15 +23,17 @@
private ProguardNameMatcher() {
}
- public static ProguardNameMatcher create(
- IdentifierPatternWithWildcards identifierPatternWithWildcards) {
- if (identifierPatternWithWildcards.isMatchAllNames()) {
- return MATCH_ALL_NAMES;
- } else if (identifierPatternWithWildcards.wildcards.isEmpty()) {
- return new MatchSpecificName(identifierPatternWithWildcards.pattern);
- } else {
- return new MatchNamePattern(identifierPatternWithWildcards);
+ public static ProguardNameMatcher create(IdentifierPatternWithWildcardsAndNegation pattern) {
+ if (!pattern.isNegated()) {
+ IdentifierPatternWithWildcards nonNegatedPattern = pattern.getNonNegatedPattern();
+ if (nonNegatedPattern.isMatchAllNames()) {
+ return MATCH_ALL_NAMES;
+ }
+ if (nonNegatedPattern.getWildcards().isEmpty()) {
+ return new MatchSpecificName(pattern.getStringPattern());
+ }
}
+ return new MatchNamePattern(pattern);
}
private static boolean matchFieldOrMethodNameImpl(
@@ -160,15 +164,21 @@
private final String pattern;
private final List<ProguardWildcard> wildcards;
+ private final boolean negated;
- MatchNamePattern(IdentifierPatternWithWildcards identifierPatternWithWildcards) {
- this.pattern = identifierPatternWithWildcards.pattern;
- this.wildcards = identifierPatternWithWildcards.wildcards;
+ MatchNamePattern(IdentifierPatternWithWildcardsAndNegation pattern) {
+ this(pattern.getStringPattern(), pattern.getWildcards(), pattern.isNegated());
+ }
+
+ MatchNamePattern(String pattern, List<ProguardWildcard> wildcards, boolean negated) {
+ this.pattern = pattern;
+ this.wildcards = wildcards;
+ this.negated = negated;
}
@Override
public boolean matches(String name) {
- boolean matched = matchFieldOrMethodNameImpl(pattern, 0, name, 0, wildcards, 0);
+ boolean matched = matchFieldOrMethodNameImpl(pattern, 0, name, 0, wildcards, 0) != negated;
if (!matched) {
wildcards.forEach(ProguardWildcard::clearCaptured);
}
@@ -185,14 +195,12 @@
protected MatchNamePattern materialize() {
List<ProguardWildcard> materializedWildcards =
wildcards.stream().map(ProguardWildcard::materialize).collect(Collectors.toList());
- IdentifierPatternWithWildcards identifierPatternWithMaterializedWildcards =
- new IdentifierPatternWithWildcards(pattern, materializedWildcards);
- return new MatchNamePattern(identifierPatternWithMaterializedWildcards);
+ return new MatchNamePattern(pattern, materializedWildcards, negated);
}
@Override
public String toString() {
- return pattern;
+ return (negated ? "!" : "") + pattern;
}
@Override
@@ -200,12 +208,16 @@
if (this == o) {
return true;
}
- return o instanceof MatchNamePattern && pattern.equals(((MatchNamePattern) o).pattern);
+ if (o == null || !(o instanceof MatchNamePattern)) {
+ return false;
+ }
+ MatchNamePattern other = (MatchNamePattern) o;
+ return pattern.equals(other.pattern) && negated == other.negated;
}
@Override
public int hashCode() {
- return pattern.hashCode();
+ return (pattern.hashCode() << 1) | BooleanUtils.intValue(negated);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardWildcard.java b/src/main/java/com/android/tools/r8/shaking/ProguardWildcard.java
index a672c96..814c502 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardWildcard.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardWildcard.java
@@ -36,6 +36,10 @@
this.pattern = pattern;
}
+ static Pattern create(String pattern, boolean isNonReferenceable) {
+ return isNonReferenceable ? new NonReferenceablePattern(pattern) : new Pattern(pattern);
+ }
+
@Override
synchronized void setCaptured(String captured) {
this.captured = captured;
@@ -71,12 +75,28 @@
return this;
}
+ boolean isNonReferenceable() {
+ return false;
+ }
+
@Override
public String toString() {
return pattern;
}
}
+ static class NonReferenceablePattern extends Pattern {
+
+ NonReferenceablePattern(String pattern) {
+ super(pattern);
+ }
+
+ @Override
+ boolean isNonReferenceable() {
+ return true;
+ }
+ }
+
public static class BackReference extends ProguardWildcard {
// Back-reference is not referable, hence the other type, Pattern, here.
Pattern reference;
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepRuleNegatedNameTest.java b/src/test/java/com/android/tools/r8/shaking/KeepRuleNegatedNameTest.java
new file mode 100644
index 0000000..05762b3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/KeepRuleNegatedNameTest.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2026, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilation;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class KeepRuleNegatedNameTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(Backend.DEX)
+ .addInnerClasses(getClass())
+ .addKeepRules("-keep class " + Main.class.getTypeName() + " {", " void !foo();", "}")
+ .setMinApi(AndroidApiLevel.N)
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClass = inspector.clazz(Main.class);
+ assertThat(mainClass, isPresent());
+ assertThat(mainClass.uniqueMethodWithOriginalName("foo"), isAbsent());
+ assertThat(mainClass.uniqueMethodWithOriginalName("bar"), isPresent());
+ assertThat(mainClass.uniqueMethodWithOriginalName("baz"), isPresent());
+ });
+ }
+
+ @Test
+ public void testIfRule() throws Exception {
+ testForR8(Backend.DEX)
+ .addInnerClasses(getClass())
+ .addKeepRules(
+ // Keep foo.
+ "-keep class " + Main.class.getTypeName() + " {",
+ " void foo();",
+ "}",
+ // If there exists a method not ending in "ar", then keep baz().
+ "-if class * {",
+ " void !*ar();",
+ "}",
+ "-keep class <1> {",
+ " void baz();",
+ "}")
+ .setMinApi(AndroidApiLevel.N)
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject mainClass = inspector.clazz(Main.class);
+ assertThat(mainClass, isPresent());
+ assertThat(mainClass.uniqueMethodWithOriginalName("foo"), isPresent());
+ assertThat(mainClass.uniqueMethodWithOriginalName("bar"), isAbsent());
+ assertThat(mainClass.uniqueMethodWithOriginalName("baz"), isPresent());
+ });
+ }
+
+ @Test
+ public void testIfRuleWithBackReference() throws Exception {
+ assertFailsCompilation(
+ () ->
+ testForR8(Backend.DEX)
+ .addInnerClasses(getClass())
+ .addKeepRules(
+ // Keep foo.
+ "-keep class " + Main.class.getTypeName() + " {",
+ " void foo();",
+ "}",
+ // If there exists a method not ending in "ar", then keep what's by the
+ // wildcard?!
+ "-if class * {",
+ " void !*ar();",
+ "}",
+ "-keep class <1> {",
+ " void <2>();",
+ "}")
+ .setMinApi(AndroidApiLevel.N)
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics
+ .assertOnlyErrors()
+ .assertErrorsMatch(
+ allOf(
+ diagnosticType(StringDiagnostic.class),
+ diagnosticMessage(
+ equalTo(
+ "Wildcard <2> is invalid (cannot reference negated"
+ + " matches)."))))));
+ }
+
+ static class Main {
+
+ public static void foo() {}
+
+ public static void bar() {}
+
+ public static void baz() {}
+ }
+}
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 50465ca..f21e321 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -37,7 +37,7 @@
import com.android.tools.r8.position.TextRange;
import com.android.tools.r8.shaking.ProguardClassNameList.SingleClassNameList;
import com.android.tools.r8.shaking.ProguardConfiguration.ProcessKotlinNullChecks;
-import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
+import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcardsAndNegation;
import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
import com.android.tools.r8.shaking.constructor.InitMatchingTest;
import com.android.tools.r8.utils.AbortException;
@@ -234,8 +234,9 @@
assertEquals("some.library.Class", rule.getInheritanceClassName().toString());
ProguardMemberRule memberRule = rule.getMemberRules().iterator().next();
assertTrue(memberRule.getAccessFlags().isProtected());
- assertEquals(ProguardNameMatcher.create(
- IdentifierPatternWithWildcards.withoutWildcards("getContents")), memberRule.getName());
+ assertEquals(
+ ProguardNameMatcher.create(new IdentifierPatternWithWildcardsAndNegation("getContents")),
+ memberRule.getName());
assertEquals("java.lang.Object[][]", memberRule.getType().toString());
assertEquals(ProguardMemberType.METHOD, memberRule.getRuleType());
assertEquals(0, memberRule.getArguments().size());
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java
index 5163bbd..673ba91 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardNameMatchingTest.java
@@ -7,7 +7,7 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
+import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcardsAndNegation;
import com.android.tools.r8.shaking.ProguardTypeMatcher.ClassOrType;
import com.android.tools.r8.shaking.ProguardWildcard.BackReference;
import com.android.tools.r8.shaking.ProguardWildcard.Pattern;
@@ -27,7 +27,9 @@
private static boolean matchTypeName(String typeName, String pattern) {
return ProguardTypeMatcher.create(
- toIdentifierPatternWithWildCards(pattern, false), ClassOrType.TYPE, dexItemFactory)
+ toIdentifierPatternWithWildcards(pattern, false).getNonNegatedPattern(),
+ ClassOrType.TYPE,
+ dexItemFactory)
.matches(dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(typeName)));
}
@@ -42,10 +44,12 @@
for (String pattern : patterns) {
boolean isNegated = pattern.startsWith("!");
String actualPattern = isNegated ? pattern.substring(1) : pattern;
- listBuilder.addClassName(isNegated,
+ listBuilder.addClassName(
+ isNegated,
ProguardTypeMatcher.create(
- toIdentifierPatternWithWildCards(actualPattern, false),
- ClassOrType.CLASS, dexItemFactory));
+ toIdentifierPatternWithWildcards(actualPattern, false).getNonNegatedPattern(),
+ ClassOrType.CLASS,
+ dexItemFactory));
}
builder.addPattern(listBuilder.build());
}
@@ -55,7 +59,7 @@
private static boolean matchMemberName(String pattern, String memberName) {
ProguardNameMatcher nameMatcher =
- ProguardNameMatcher.create(toIdentifierPatternWithWildCards(pattern, true));
+ ProguardNameMatcher.create(toIdentifierPatternWithWildcards(pattern, true));
return nameMatcher.matches(memberName);
}
@@ -178,7 +182,7 @@
assertFalse(matchMemberName("*foo<1>", "barfoobaz"));
}
- private static IdentifierPatternWithWildcards toIdentifierPatternWithWildCards(
+ private static IdentifierPatternWithWildcardsAndNegation toIdentifierPatternWithWildcards(
String pattern, boolean isForNameMatcher) {
ImmutableList.Builder<ProguardWildcard> builder = ImmutableList.builder();
String allPattern = "";
@@ -222,7 +226,7 @@
}
List<ProguardWildcard> wildcards = builder.build();
linkBackReferences(wildcards);
- return new IdentifierPatternWithWildcards(pattern, wildcards);
+ return new IdentifierPatternWithWildcardsAndNegation(pattern, wildcards);
}
private static void linkBackReferences(Iterable<ProguardWildcard> wildcards) {