Remove back references from if-rules once materialized
Bug: 139851246
Change-Id: I5577ca9f2acff3e9e57d1311763bc8db9ac54adf
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index fb58973..9d61070 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
@@ -290,13 +291,14 @@
}
private void materializeIfRule(ProguardIfRule rule, Set<DexReference> preconditions) {
- ProguardIfRule materializedRule = rule.materialize(preconditions);
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ ProguardIfRule materializedRule = rule.materialize(dexItemFactory, preconditions);
if (mode.isInitialTreeShaking() && !rule.isUsed()) {
// We need to abort class inlining of classes that could be matched by the condition of this
// -if rule.
ClassInlineRule neverClassInlineRuleForCondition =
- materializedRule.neverClassInlineRuleForCondition();
+ materializedRule.neverClassInlineRuleForCondition(dexItemFactory);
if (neverClassInlineRuleForCondition != null) {
rootSetBuilder.runPerRule(executorService, futures, neverClassInlineRuleForCondition, null);
}
@@ -304,7 +306,8 @@
// If the condition of the -if rule has any members, then we need to keep these members to
// ensure that the subsequent rule will be applied again in the second round of tree
// shaking.
- InlineRule neverInlineRuleForCondition = materializedRule.neverInlineRuleForCondition();
+ InlineRule neverInlineRuleForCondition =
+ materializedRule.neverInlineRuleForCondition(dexItemFactory);
if (neverInlineRuleForCondition != null) {
rootSetBuilder.runPerRule(executorService, futures, neverInlineRuleForCondition, null);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
index 9d6826f..0b02b70 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
@@ -90,7 +91,7 @@
return nameList == null ? Collections::emptyIterator : nameList.getWildcards();
}
- protected ProguardClassNameList materialize() {
+ protected ProguardClassNameList materialize(DexItemFactory dexItemFactory) {
return this;
}
@@ -135,9 +136,9 @@
}
}
- private static class SingleClassNameList extends ProguardClassNameList {
+ static class SingleClassNameList extends ProguardClassNameList {
- private final ProguardTypeMatcher className;
+ final ProguardTypeMatcher className;
private SingleClassNameList(ProguardTypeMatcher className) {
this.className = className;
@@ -187,8 +188,8 @@
}
@Override
- protected SingleClassNameList materialize() {
- return new SingleClassNameList(className.materialize());
+ protected SingleClassNameList materialize(DexItemFactory dexItemFactory) {
+ return new SingleClassNameList(className.materialize(dexItemFactory));
}
@Override
@@ -262,9 +263,11 @@
}
@Override
- protected PositiveClassNameList materialize() {
+ protected PositiveClassNameList materialize(DexItemFactory dexItemFactory) {
return new PositiveClassNameList(
- classNames.stream().map(ProguardTypeMatcher::materialize).collect(Collectors.toList()));
+ classNames.stream()
+ .map(className -> className.materialize(dexItemFactory))
+ .collect(Collectors.toList()));
}
@Override
@@ -343,9 +346,10 @@
}
@Override
- protected ProguardClassNameList materialize() {
+ protected ProguardClassNameList materialize(DexItemFactory dexItemFactory) {
Builder builder = builder();
- classNames.forEach((m, negated) -> builder.addClassName(negated, m.materialize()));
+ classNames.forEach(
+ (m, negated) -> builder.addClassName(negated, m.materialize(dexItemFactory)));
return builder.build();
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index 31ea7c8..d4e2b1b 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
@@ -101,42 +102,51 @@
return Iterables.concat(super.getWildcards(), subsequentRule.getWildcards());
}
- protected ProguardIfRule materialize(Set<DexReference> preconditions) {
+ protected ProguardIfRule materialize(
+ DexItemFactory dexItemFactory, Set<DexReference> preconditions) {
return new ProguardIfRule(
getOrigin(),
getPosition(),
getSource(),
- getClassAnnotation() == null ? null : getClassAnnotation().materialize(),
+ getClassAnnotation() == null ? null : getClassAnnotation().materialize(dexItemFactory),
getClassAccessFlags(),
getNegatedClassAccessFlags(),
getClassTypeNegated(),
getClassType(),
- getClassNames().materialize(),
- getInheritanceAnnotation() == null ? null : getInheritanceAnnotation().materialize(),
- getInheritanceClassName() == null ? null : getInheritanceClassName().materialize(),
+ getClassNames().materialize(dexItemFactory),
+ getInheritanceAnnotation() == null
+ ? null
+ : getInheritanceAnnotation().materialize(dexItemFactory),
+ getInheritanceClassName() == null
+ ? null
+ : getInheritanceClassName().materialize(dexItemFactory),
getInheritanceIsExtends(),
getMemberRules() == null
? null
: getMemberRules().stream()
- .map(ProguardMemberRule::materialize)
+ .map(memberRule -> memberRule.materialize(dexItemFactory))
.collect(Collectors.toList()),
- subsequentRule.materialize(),
+ subsequentRule.materialize(dexItemFactory),
preconditions);
}
- protected ClassInlineRule neverClassInlineRuleForCondition() {
+ protected ClassInlineRule neverClassInlineRuleForCondition(DexItemFactory dexItemFactory) {
return new ClassInlineRule(
neverInlineOrigin,
Position.UNKNOWN,
null,
- getClassAnnotation() == null ? null : getClassAnnotation().materialize(),
+ getClassAnnotation() == null ? null : getClassAnnotation().materialize(dexItemFactory),
getClassAccessFlags(),
getNegatedClassAccessFlags(),
getClassTypeNegated(),
getClassType(),
- getClassNames().materialize(),
- getInheritanceAnnotation() == null ? null : getInheritanceAnnotation().materialize(),
- getInheritanceClassName() == null ? null : getInheritanceClassName().materialize(),
+ getClassNames().materialize(dexItemFactory),
+ getInheritanceAnnotation() == null
+ ? null
+ : getInheritanceAnnotation().materialize(dexItemFactory),
+ getInheritanceClassName() == null
+ ? null
+ : getInheritanceClassName().materialize(dexItemFactory),
getInheritanceIsExtends(),
ImmutableList.of(),
ClassInlineRule.Type.NEVER);
@@ -164,7 +174,7 @@
* <p>Therefore, each time the subsequent rule of an -if rule is applied, we also apply a
* -neverinline rule for the condition of the -if rule.
*/
- protected InlineRule neverInlineRuleForCondition() {
+ protected InlineRule neverInlineRuleForCondition(DexItemFactory dexItemFactory) {
if (getMemberRules() == null || getMemberRules().isEmpty()) {
return null;
}
@@ -172,18 +182,22 @@
neverInlineOrigin,
Position.UNKNOWN,
null,
- getClassAnnotation() == null ? null : getClassAnnotation().materialize(),
+ getClassAnnotation() == null ? null : getClassAnnotation().materialize(dexItemFactory),
getClassAccessFlags(),
getNegatedClassAccessFlags(),
getClassTypeNegated(),
getClassType(),
- getClassNames().materialize(),
- getInheritanceAnnotation() == null ? null : getInheritanceAnnotation().materialize(),
- getInheritanceClassName() == null ? null : getInheritanceClassName().materialize(),
+ getClassNames().materialize(dexItemFactory),
+ getInheritanceAnnotation() == null
+ ? null
+ : getInheritanceAnnotation().materialize(dexItemFactory),
+ getInheritanceClassName() == null
+ ? null
+ : getInheritanceClassName().materialize(dexItemFactory),
getInheritanceIsExtends(),
getMemberRules().stream()
.filter(rule -> rule.getRuleType().includesMethods())
- .map(ProguardMemberRule::materialize)
+ .map(memberRule -> memberRule.materialize(dexItemFactory))
.collect(Collectors.toList()),
InlineRule.Type.NEVER);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
index b835e20..5e382f9 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
import java.util.List;
@@ -58,24 +59,28 @@
return new Builder();
}
- protected ProguardKeepRule materialize() {
+ protected ProguardKeepRule materialize(DexItemFactory dexItemFactory) {
return new ProguardKeepRule(
getOrigin(),
getPosition(),
getSource(),
- getClassAnnotation() == null ? null : getClassAnnotation().materialize(),
+ getClassAnnotation() == null ? null : getClassAnnotation().materialize(dexItemFactory),
getClassAccessFlags(),
getNegatedClassAccessFlags(),
getClassTypeNegated(),
getClassType(),
- getClassNames() == null ? null : getClassNames().materialize(),
- getInheritanceAnnotation() == null ? null : getInheritanceAnnotation().materialize(),
- getInheritanceClassName() == null ? null : getInheritanceClassName().materialize(),
+ getClassNames() == null ? null : getClassNames().materialize(dexItemFactory),
+ getInheritanceAnnotation() == null
+ ? null
+ : getInheritanceAnnotation().materialize(dexItemFactory),
+ getInheritanceClassName() == null
+ ? null
+ : getInheritanceClassName().materialize(dexItemFactory),
getInheritanceIsExtends(),
getMemberRules() == null
? null
: getMemberRules().stream()
- .map(ProguardMemberRule::materialize)
+ .map(memberRule -> memberRule.materialize(dexItemFactory))
.collect(Collectors.toList()),
getType(),
getModifiers());
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 296c370..93f36a2 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
+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;
@@ -299,17 +300,19 @@
);
}
- ProguardMemberRule materialize() {
+ ProguardMemberRule materialize(DexItemFactory dexItemFactory) {
return new ProguardMemberRule(
- getAnnotation() == null ? null : getAnnotation().materialize(),
+ getAnnotation() == null ? null : getAnnotation().materialize(dexItemFactory),
getAccessFlags(),
getNegatedAccessFlags(),
getRuleType(),
- getType() == null ? null : getType().materialize(),
+ getType() == null ? null : getType().materialize(dexItemFactory),
getName() == null ? null : getName().materialize(),
- getArguments() == null ? null :
- getArguments().stream()
- .map(ProguardTypeMatcher::materialize).collect(Collectors.toList()),
+ getArguments() == null
+ ? null
+ : getArguments().stream()
+ .map(argument -> argument.materialize(dexItemFactory))
+ .collect(Collectors.toList()),
getReturnValue());
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
index cdec11f..a4db6a2 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
@@ -11,10 +11,14 @@
import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
import com.android.tools.r8.shaking.ProguardWildcard.BackReference;
import com.android.tools.r8.shaking.ProguardWildcard.Pattern;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.stream.Collectors;
public abstract class ProguardTypeMatcher {
@@ -55,7 +59,7 @@
return typeMatcher == null ? Collections::emptyIterator : typeMatcher.getWildcards();
}
- protected ProguardTypeMatcher materialize() {
+ protected ProguardTypeMatcher materialize(DexItemFactory dexItemFactory) {
return this;
}
@@ -145,7 +149,7 @@
}
@Override
- protected MatchAllTypes materialize() {
+ protected MatchAllTypes materialize(DexItemFactory dexItemFactory) {
return new MatchAllTypes(wildcard.materialize());
}
@@ -230,7 +234,7 @@
}
@Override
- protected MatchClassTypes materialize() {
+ protected MatchClassTypes materialize(DexItemFactory dexItemFactory) {
return new MatchClassTypes(pattern, wildcard.materialize());
}
@@ -279,7 +283,7 @@
}
@Override
- protected MatchBasicTypes materialize() {
+ protected MatchBasicTypes materialize(DexItemFactory dexItemFactory) {
return new MatchBasicTypes(wildcard.materialize());
}
@@ -371,14 +375,90 @@
}
@Override
- protected MatchTypePattern materialize() {
- List<ProguardWildcard> materializedWildcards =
- wildcards.stream().map(ProguardWildcard::materialize).collect(Collectors.toList());
+ protected ProguardTypeMatcher materialize(DexItemFactory dexItemFactory) {
+ Int2ReferenceMap<String> materializedBackReferences = new Int2ReferenceOpenHashMap<>();
+ List<ProguardWildcard> materializedWildcards = new ArrayList<>();
+ for (ProguardWildcard wildcard : wildcards) {
+ ProguardWildcard materializedWildcard = wildcard.materialize();
+ if (materializedWildcard.isBackReference()) {
+ BackReference materializedBackReference = materializedWildcard.asBackReference();
+ materializedBackReferences.put(
+ materializedBackReference.referenceIndex, materializedBackReference.getCaptured());
+ } else {
+ materializedWildcards.add(materializedWildcard);
+ }
+ }
+
+ if (!materializedBackReferences.isEmpty()) {
+ String newPattern =
+ removeMaterializedBackReferencesFromPattern(pattern, materializedBackReferences);
+ if (!newPattern.contains("*")) {
+ String descriptor = DescriptorUtils.javaTypeToDescriptor(newPattern);
+ DexType type = dexItemFactory.createType(descriptor);
+ return new MatchSpecificType(type);
+ }
+
+ IdentifierPatternWithWildcards identifierPatternWithMaterializedWildcards =
+ new IdentifierPatternWithWildcards(newPattern, materializedWildcards);
+ return new MatchTypePattern(identifierPatternWithMaterializedWildcards, kind);
+ }
+
IdentifierPatternWithWildcards identifierPatternWithMaterializedWildcards =
new IdentifierPatternWithWildcards(pattern, materializedWildcards);
return new MatchTypePattern(identifierPatternWithMaterializedWildcards, kind);
}
+ private static String removeMaterializedBackReferencesFromPattern(
+ String pattern, Int2ReferenceMap<String> materializedBackReferences) {
+ StringBuilder builder = new StringBuilder();
+ int startIndex = 0;
+ int currentIndex = 0;
+ for (; currentIndex < pattern.length(); currentIndex++) {
+ char c = pattern.charAt(currentIndex);
+ if (c == '<') {
+ int backReferenceEndIndex = currentIndex + 1;
+ while (backReferenceEndIndex < pattern.length()
+ && pattern.charAt(backReferenceEndIndex) != '>') {
+ backReferenceEndIndex++;
+ }
+ if (backReferenceEndIndex == pattern.length()) {
+ // Reached the end of the string without finding '>'.
+ break;
+ }
+
+ String reference = pattern.substring(currentIndex + 1, backReferenceEndIndex);
+ if (reference.isEmpty() || !StringUtils.onlyContainsDigits(reference)) {
+ continue;
+ }
+
+ String captured = materializedBackReferences.get(Integer.valueOf(reference).intValue());
+ if (captured == null) {
+ continue;
+ }
+
+ // Flush everything up until the back reference.
+ String before = pattern.substring(startIndex, currentIndex);
+ builder.append(before);
+
+ // Output the captured value.
+ builder.append(captured);
+
+ // Continue from the character that follows '>'.
+ startIndex = backReferenceEndIndex + 1;
+ currentIndex = backReferenceEndIndex;
+ }
+ }
+
+ assert currentIndex == pattern.length();
+
+ // Output everything that follows the last back reference.
+ if (startIndex < currentIndex) {
+ builder.append(pattern.substring(startIndex));
+ }
+
+ return builder.toString();
+ }
+
private static boolean matchClassOrTypeNameImpl(
String pattern, int patternIndex,
String name, int nameIndex,
diff --git a/src/main/java/com/android/tools/r8/utils/StringUtils.java b/src/main/java/com/android/tools/r8/utils/StringUtils.java
index fb5793a..cd47dc5 100644
--- a/src/main/java/com/android/tools/r8/utils/StringUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/StringUtils.java
@@ -294,4 +294,15 @@
return s;
}
}
+
+ /** Returns true if {@param s} only contains the characters [0-9]. */
+ public static boolean onlyContainsDigits(String s) {
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (!Character.isDigit(c)) {
+ return false;
+ }
+ }
+ return true;
+ }
}
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 b4e236f..fae56af 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -26,7 +26,9 @@
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.position.Position;
import com.android.tools.r8.position.TextRange;
+import com.android.tools.r8.shaking.ProguardClassNameList.SingleClassNameList;
import com.android.tools.r8.shaking.ProguardConfigurationParser.IdentifierPatternWithWildcards;
+import com.android.tools.r8.shaking.ProguardTypeMatcher.MatchSpecificType;
import com.android.tools.r8.shaking.constructor.InitMatchingTest;
import com.android.tools.r8.utils.AbortException;
import com.android.tools.r8.utils.FileUtils;
@@ -2839,4 +2841,35 @@
}
}
}
+
+ @Test
+ public void backReferenceElimination() {
+ DexItemFactory dexItemFactory = new DexItemFactory();
+ ProguardConfigurationParser parser = new ProguardConfigurationParser(dexItemFactory, reporter);
+ String configuration = StringUtils.lines("-if class *.*.*", "-keep class <1>.<2>$<3>");
+ parser.parse(createConfigurationForTesting(ImmutableList.of(configuration)));
+ verifyParserEndsCleanly();
+
+ ProguardConfiguration config = parser.getConfig();
+ assertEquals(1, config.getRules().size());
+
+ ProguardIfRule ifRule = (ProguardIfRule) config.getRules().iterator().next();
+
+ // Evaluate the class name matcher against foo.bar.Baz.
+ DexType type = dexItemFactory.createType("Lfoo/bar/Baz;");
+ ifRule.getClassNames().matches(type);
+
+ // Materialize the subsequent rule.
+ ProguardKeepRule materializedSubsequentRule = ifRule.subsequentRule.materialize(dexItemFactory);
+
+ // Verify that the class name matcher of the materialized rule has a specific type.
+ ProguardClassNameList classNameList = materializedSubsequentRule.getClassNames();
+ assertTrue(classNameList instanceof SingleClassNameList);
+
+ SingleClassNameList singleClassNameList = (SingleClassNameList) classNameList;
+ assertTrue(singleClassNameList.className instanceof MatchSpecificType);
+
+ MatchSpecificType specificTypeMatcher = (MatchSpecificType) singleClassNameList.className;
+ assertEquals("foo.bar$Baz", specificTypeMatcher.type.toSourceString());
+ }
}
\ No newline at end of file