blob: 19bd876048c8ab6556595c03ed6eb4260415b656 [file] [log] [blame]
// Copyright (c) 2016, 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.utils.DescriptorUtils.javaTypeToDescriptor;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
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;
public abstract class ProguardTypeMatcher {
private static final String MATCH_ALL_PATTERN = "***";
private static final String MATCH_ANY_ARG_SEQUENCE_PATTERN = "...";
private static final String LEGACY_MATCH_CLASS_PATTERN = "*";
private static final String MATCH_CLASS_PATTERN = "**";
private static final String MATCH_BASIC_PATTERN = "%";
private ProguardTypeMatcher() {
}
enum ClassOrType {
CLASS,
TYPE
}
public MatchSpecificType asSpecificTypeMatcher() {
return null;
}
// Evaluates this matcher on the given type.
public abstract boolean matches(DexType type);
// Evaluates this matcher on the given type, and on all types that have been merged into the given
// type, if any.
public final boolean matches(DexType type, AppView<?> appView) {
if (matches(type)) {
return true;
}
if (appView.verticallyMergedClasses() != null) {
return appView.verticallyMergedClasses().getSourcesFor(type).stream().anyMatch(this::matches);
}
return false;
}
protected Iterable<ProguardWildcard> getWildcards() {
return Collections::emptyIterator;
}
static Iterable<ProguardWildcard> getWildcardsOrEmpty(ProguardTypeMatcher typeMatcher) {
return typeMatcher == null ? Collections::emptyIterator : typeMatcher.getWildcards();
}
static Iterable<ProguardWildcard> getWildcardsOrEmpty(List<ProguardTypeMatcher> typeMatchers) {
List<ProguardWildcard> result = new ArrayList<>();
for (ProguardTypeMatcher typeMatcher : typeMatchers) {
typeMatcher.getWildcards().forEach(result::add);
}
return result;
}
protected ProguardTypeMatcher materialize(DexItemFactory dexItemFactory) {
return this;
}
public static List<ProguardTypeMatcher> materializeList(
List<ProguardTypeMatcher> matchers, DexItemFactory dexItemFactory) {
if (matchers.isEmpty()) {
return Collections.emptyList();
}
ImmutableList.Builder<ProguardTypeMatcher> builder = ImmutableList.builder();
for (ProguardTypeMatcher matcher : matchers) {
builder.add(matcher.materialize(dexItemFactory));
}
return builder.build();
}
@Override
public abstract String toString();
public boolean isTripleDotPattern() {
return false;
}
public static ProguardTypeMatcher create(
IdentifierPatternWithWildcards identifierPatternWithWildcards,
ClassOrType kind,
DexItemFactory dexItemFactory) {
if (identifierPatternWithWildcards == null || identifierPatternWithWildcards.pattern == null) {
return null;
}
switch (identifierPatternWithWildcards.pattern) {
case MATCH_ALL_PATTERN:
return MatchAllTypes.MATCH_ALL_TYPES;
case MATCH_ANY_ARG_SEQUENCE_PATTERN:
return MatchAnyArgSequence.MATCH_ANY_ARG_SEQUENCE;
case MATCH_CLASS_PATTERN:
return MatchClassTypes.MATCH_CLASS_TYPES;
case LEGACY_MATCH_CLASS_PATTERN:
return MatchClassTypes.LEGACY_MATCH_CLASS_TYPES;
case MATCH_BASIC_PATTERN:
return MatchBasicTypes.MATCH_BASIC_TYPES;
default:
if (identifierPatternWithWildcards.wildcards.isEmpty()) {
return new MatchSpecificType(dexItemFactory.createType(
javaTypeToDescriptor(identifierPatternWithWildcards.pattern)));
}
return new MatchTypePattern(identifierPatternWithWildcards, kind);
}
}
public static ProguardTypeMatcher create(DexType type) {
return new MatchSpecificType(type);
}
public static ProguardTypeMatcher defaultAllMatcher() {
return MatchAllTypes.MATCH_ALL_TYPES;
}
@Override
public abstract boolean equals(Object o);
@Override
public abstract int hashCode();
public boolean hasSpecificType() {
return false;
}
public DexType getSpecificType() {
return null;
}
public final boolean matchesSpecificType() {
return getSpecificType() != null;
}
private static class MatchAllTypes extends ProguardTypeMatcher {
private static final ProguardTypeMatcher MATCH_ALL_TYPES = new MatchAllTypes();
private final ProguardWildcard wildcard;
MatchAllTypes() {
this(new Pattern(MATCH_ALL_PATTERN));
}
private MatchAllTypes(ProguardWildcard wildcard) {
this.wildcard = wildcard;
}
@Override
public boolean matches(DexType type) {
wildcard.setCaptured(type.toSourceString());
return true;
}
@Override
protected Iterable<ProguardWildcard> getWildcards() {
return ImmutableList.of(wildcard);
}
@Override
protected MatchAllTypes materialize(DexItemFactory dexItemFactory) {
return new MatchAllTypes(wildcard.materialize());
}
@Override
public String toString() {
return MATCH_ALL_PATTERN;
}
@Override
public boolean equals(Object o) {
return o instanceof MatchAllTypes;
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}
private static class MatchAnyArgSequence extends ProguardTypeMatcher {
private static final ProguardTypeMatcher MATCH_ANY_ARG_SEQUENCE = new MatchAnyArgSequence();
@Override
public boolean matches(DexType type) {
return true;
}
@Override
public String toString() {
return MATCH_ANY_ARG_SEQUENCE_PATTERN;
}
@Override
public boolean isTripleDotPattern() {
return true;
}
@Override
public boolean equals(Object o) {
return o instanceof MatchAnyArgSequence;
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}
private static class MatchClassTypes extends ProguardTypeMatcher {
private static final ProguardTypeMatcher MATCH_CLASS_TYPES =
new MatchClassTypes(MATCH_CLASS_PATTERN);
private static final ProguardTypeMatcher LEGACY_MATCH_CLASS_TYPES =
new MatchClassTypes(LEGACY_MATCH_CLASS_PATTERN);
private final String pattern;
private final ProguardWildcard wildcard;
private MatchClassTypes(String pattern) {
this(pattern, new Pattern(pattern));
}
private MatchClassTypes(String pattern, ProguardWildcard wildcard) {
assert pattern.equals(LEGACY_MATCH_CLASS_PATTERN) || pattern.equals(MATCH_CLASS_PATTERN);
this.pattern = pattern;
this.wildcard = wildcard;
}
@Override
public boolean matches(DexType type) {
if (type.isClassType()) {
wildcard.setCaptured(type.toSourceString());
return true;
}
return false;
}
@Override
protected Iterable<ProguardWildcard> getWildcards() {
return ImmutableList.of(wildcard);
}
@Override
protected MatchClassTypes materialize(DexItemFactory dexItemFactory) {
return new MatchClassTypes(pattern, wildcard.materialize());
}
@Override
public String toString() {
return pattern;
}
@Override
public boolean equals(Object o) {
return o instanceof MatchClassTypes && pattern.equals(((MatchClassTypes) o).pattern);
}
@Override
public int hashCode() {
return pattern.hashCode();
}
}
private static class MatchBasicTypes extends ProguardTypeMatcher {
private static final ProguardTypeMatcher MATCH_BASIC_TYPES = new MatchBasicTypes();
private final ProguardWildcard wildcard;
MatchBasicTypes() {
this(new Pattern(MATCH_BASIC_PATTERN));
}
private MatchBasicTypes(ProguardWildcard wildcard) {
this.wildcard = wildcard;
}
@Override
public boolean matches(DexType type) {
if (type.isPrimitiveType()) {
wildcard.setCaptured(type.toSourceString());
return true;
}
return false;
}
@Override
protected Iterable<ProguardWildcard> getWildcards() {
return ImmutableList.of(wildcard);
}
@Override
protected MatchBasicTypes materialize(DexItemFactory dexItemFactory) {
return new MatchBasicTypes(wildcard.materialize());
}
@Override
public String toString() {
return MATCH_BASIC_PATTERN;
}
@Override
public boolean equals(Object o) {
return o instanceof MatchBasicTypes;
}
@Override
public int hashCode() {
return getClass().hashCode();
}
}
public static class MatchSpecificType extends ProguardTypeMatcher {
public final DexType type;
private MatchSpecificType(DexType type) {
this.type = type;
}
@Override
public MatchSpecificType asSpecificTypeMatcher() {
return this;
}
@Override
public boolean matches(DexType type) {
return this.type == type;
}
@Override
public String toString() {
return type.toSourceString();
}
@Override
public boolean equals(Object o) {
if (o instanceof MatchSpecificType) {
return type.equals(((MatchSpecificType) o).type);
}
return false;
}
@Override
public int hashCode() {
return type.hashCode();
}
@Override
public boolean hasSpecificType() {
return true;
}
@Override
public DexType getSpecificType() {
return type;
}
}
private static class MatchTypePattern extends ProguardTypeMatcher {
private final String pattern;
private final List<ProguardWildcard> wildcards;
private final ClassOrType kind;
private MatchTypePattern(
IdentifierPatternWithWildcards identifierPatternWithWildcards, ClassOrType kind) {
this.pattern = identifierPatternWithWildcards.pattern;
this.wildcards = identifierPatternWithWildcards.wildcards;
this.kind = kind;
}
@Override
public boolean matches(DexType type) {
// TODO(herhut): Translate pattern to work on descriptors instead.
String typeName = type.toSourceString();
boolean matched = matchClassOrTypeNameImpl(pattern, 0, typeName, 0, wildcards, 0, kind);
if (!matched) {
wildcards.forEach(ProguardWildcard::clearCaptured);
}
return matched;
}
@Override
protected Iterable<ProguardWildcard> getWildcards() {
return wildcards;
}
@Override
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,
List<ProguardWildcard> wildcards, int wildcardIndex,
ClassOrType kind) {
ProguardWildcard wildcard;
Pattern wildcardPattern;
BackReference backReference;
for (int i = patternIndex; i < pattern.length(); i++) {
char patternChar = pattern.charAt(i);
switch (patternChar) {
case '*':
wildcard = wildcards.get(wildcardIndex);
assert wildcard.isPattern();
wildcardPattern = wildcard.asPattern();
boolean includeSeparators = pattern.length() > (i + 1) && pattern.charAt(i + 1) == '*';
boolean includeAll =
includeSeparators && pattern.length() > (i + 2) && pattern.charAt(i + 2) == '*';
int nextPatternIndex = i + 1;
if (includeAll) {
nextPatternIndex += 2;
} else if (includeSeparators) {
nextPatternIndex += 1;
}
// Fast cases for the common case where a pattern ends with '*', '**', or '***'.
if (nextPatternIndex == pattern.length()) {
wildcardPattern.setCaptured(name.substring(nameIndex));
if (includeAll) {
return true;
}
if (includeSeparators) {
return kind == ClassOrType.CLASS || !isArrayType(name);
}
boolean hasSeparators = containsSeparatorsStartingAt(name, nameIndex);
return !hasSeparators && (kind == ClassOrType.CLASS || !isArrayType(name));
}
// Match the rest of the pattern against the (non-empty) rest of the class name.
for (int nextNameIndex = nameIndex; nextNameIndex < name.length(); nextNameIndex++) {
wildcardPattern.setCaptured(name.substring(nameIndex, nextNameIndex));
if (!includeSeparators) {
if (name.charAt(nextNameIndex) == '.') {
return matchClassOrTypeNameImpl(
pattern,
nextPatternIndex,
name,
nextNameIndex,
wildcards,
wildcardIndex + 1,
kind);
}
}
if (kind == ClassOrType.TYPE && name.charAt(nextNameIndex) == '[') {
return matchClassOrTypeNameImpl(
pattern, nextPatternIndex, name, nextNameIndex, wildcards, wildcardIndex + 1,
kind);
}
if (matchClassOrTypeNameImpl(
pattern, nextPatternIndex, name, nextNameIndex, wildcards, wildcardIndex + 1,
kind)) {
return true;
}
}
// Finally, check the case where the '*', '**', or '***' eats all of the class name.
wildcardPattern.setCaptured(name.substring(nameIndex));
return matchClassOrTypeNameImpl(
pattern, nextPatternIndex, name, name.length(), wildcards, wildcardIndex + 1, kind);
case '?':
wildcard = wildcards.get(wildcardIndex);
assert wildcard.isPattern();
if (nameIndex == name.length() || name.charAt(nameIndex) == '.') {
return false;
}
wildcardPattern = wildcard.asPattern();
wildcardPattern.setCaptured(name.substring(nameIndex, nameIndex + 1));
nameIndex++;
wildcardIndex++;
break;
case '<':
wildcard = wildcards.get(wildcardIndex);
assert wildcard.isBackReference();
backReference = wildcard.asBackReference();
String captured = backReference.getCaptured();
if (captured == null
|| name.length() < nameIndex + captured.length()
|| !captured.equals(name.substring(nameIndex, nameIndex + captured.length()))) {
return false;
}
nameIndex = nameIndex + captured.length();
wildcardIndex++;
i = pattern.indexOf(">", i);
break;
default:
if (nameIndex == name.length() || patternChar != name.charAt(nameIndex++)) {
return false;
}
break;
}
}
return nameIndex == name.length();
}
private static boolean containsSeparatorsStartingAt(String className, int nameIndex) {
return className.indexOf('.', nameIndex) != -1;
}
private static boolean isArrayType(String type) {
int length = type.length();
if (length < 2) {
return false;
}
return type.charAt(length - 1) == ']' && type.charAt(length - 2) == '[';
}
@Override
public String toString() {
return pattern;
}
@Override
public boolean equals(Object o) {
if (o instanceof MatchTypePattern) {
MatchTypePattern that = (MatchTypePattern) o;
return kind.equals(that.kind) && pattern.equals(that.pattern);
}
return false;
}
@Override
public int hashCode() {
return pattern.hashCode() * 7 + kind.hashCode();
}
}
}