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