blob: 8974c5c33ad3f4cd8a004bf84af37303cab28415 [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.google.common.base.Predicates.alwaysTrue;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexClassAndMethod;
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;
import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.ObjectUtils;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class ProguardMemberRule {
public static class Builder {
private List<ProguardTypeMatcher> annotations = Collections.emptyList();
private ProguardAccessFlags accessFlags = new ProguardAccessFlags();
private ProguardAccessFlags negatedAccessFlags = new ProguardAccessFlags();
private ProguardMemberType ruleType;
private ProguardTypeMatcher type;
private ProguardNameMatcher name;
private List<ProguardTypeMatcher> arguments;
private List<ProguardMemberRuleValue> preconditions;
private ProguardMemberRuleValue returnValue;
private Builder() {}
public void setAnnotations(List<ProguardTypeMatcher> annotations) {
assert annotations != null;
this.annotations = annotations;
}
public ProguardAccessFlags getAccessFlags() {
return accessFlags;
}
public Builder setAccessFlags(ProguardAccessFlags flags) {
accessFlags = flags;
return this;
}
public ProguardAccessFlags getNegatedAccessFlags() {
return negatedAccessFlags;
}
public void setNegatedAccessFlags(ProguardAccessFlags flags) {
negatedAccessFlags = flags;
}
public Builder setRuleType(ProguardMemberType ruleType) {
this.ruleType = ruleType;
return this;
}
public ProguardTypeMatcher getTypeMatcher() {
return type;
}
public Builder setTypeMatcher(ProguardTypeMatcher type) {
this.type = type;
return this;
}
public Builder setName(IdentifierPatternWithWildcards identifierPatternWithWildcards) {
this.name = ProguardNameMatcher.create(identifierPatternWithWildcards);
return this;
}
public Builder setArguments(List<ProguardTypeMatcher> arguments) {
this.arguments = arguments;
return this;
}
public Builder setPrecondition(int i, ProguardMemberRuleValue precondition) {
if (preconditions == null) {
preconditions = new ArrayList<>();
}
assert preconditions.size() <= i;
while (preconditions.size() < i) {
preconditions.add(null);
}
preconditions.add(precondition);
return this;
}
public Builder setReturnValue(ProguardMemberRuleValue value) {
returnValue = value;
return this;
}
public boolean isValid() {
return ruleType != null;
}
public ProguardMemberRule build() {
assert isValid();
return new ProguardMemberRule(
annotations,
accessFlags,
negatedAccessFlags,
ruleType,
type,
name,
arguments,
preconditions,
returnValue);
}
}
private final List<ProguardTypeMatcher> annotations;
private final ProguardAccessFlags accessFlags;
private final ProguardAccessFlags negatedAccessFlags;
private final ProguardMemberType ruleType;
private final ProguardTypeMatcher type;
private final ProguardNameMatcher name;
private final List<ProguardTypeMatcher> arguments;
private final List<ProguardMemberRuleValue> preconditions;
private final ProguardMemberRuleValue returnValue;
public ProguardMemberRule(
List<ProguardTypeMatcher> annotations,
ProguardAccessFlags accessFlags,
ProguardAccessFlags negatedAccessFlags,
ProguardMemberType ruleType,
ProguardTypeMatcher type,
ProguardNameMatcher name,
List<ProguardTypeMatcher> arguments,
List<ProguardMemberRuleValue> preconditions,
ProguardMemberRuleValue returnValue) {
this.annotations = annotations;
this.accessFlags = accessFlags;
this.negatedAccessFlags = negatedAccessFlags;
this.ruleType = ruleType;
this.type = type;
this.name = name;
this.arguments = arguments != null ? Collections.unmodifiableList(arguments) : null;
this.preconditions = preconditions;
this.returnValue = returnValue;
}
/**
* Create a new empty builder.
*/
public static Builder builder() {
return new Builder();
}
public List<ProguardTypeMatcher> getAnnotations() {
return annotations;
}
public ProguardAccessFlags getAccessFlags() {
return accessFlags;
}
public ProguardAccessFlags getNegatedAccessFlags() {
return negatedAccessFlags;
}
public ProguardMemberType getRuleType() {
return ruleType;
}
public boolean hasType() {
return type != null;
}
public ProguardTypeMatcher getType() {
return type;
}
public ProguardNameMatcher getName() {
return name;
}
public List<ProguardTypeMatcher> getArguments() {
return arguments;
}
public boolean hasReturnValue() {
return returnValue != null;
}
public boolean hasPreconditions() {
assert preconditions == null
|| Iterables.any(preconditions, precondition -> precondition != null);
return preconditions != null;
}
public List<ProguardMemberRuleValue> getPreconditions() {
return preconditions;
}
/**
* Resolves preconditions on the form {@code foo.bar.Baz.FIELD} to constants if the given field
* refers to a static final field with a constant value.
*/
public List<ProguardMemberRuleValue> getResolvedPreconditions(
AppView<? extends AppInfoWithClassHierarchy> appView, DexClassAndMethod method) {
List<ProguardMemberRuleValue> resolvedPreconditions = null;
for (int i = 0; i < preconditions.size(); i++) {
ProguardMemberRuleValue precondition = preconditions.get(i);
ProguardMemberRuleValue resolvedPrecondition =
precondition != null && precondition.isField()
? precondition.resolveFieldValue(appView, method.getParameter(i))
: precondition;
if (resolvedPreconditions == null
&& ObjectUtils.identical(resolvedPrecondition, precondition)) {
continue;
}
if (resolvedPreconditions == null) {
resolvedPreconditions = new ArrayList<>(preconditions.size());
for (int j = 0; j < i; j++) {
resolvedPreconditions.add(preconditions.get(j));
}
}
resolvedPreconditions.add(resolvedPrecondition);
}
return resolvedPreconditions != null ? resolvedPreconditions : preconditions;
}
public ProguardMemberRuleValue getReturnValue() {
return returnValue;
}
public boolean matches(
DexClassAndField field,
AppView<?> appView,
Consumer<AnnotationMatchResult> matchedAnnotationsConsumer,
DexStringCache stringCache) {
DexField originalSignature =
appView.graphLens().getOriginalFieldSignature(field.getReference());
switch (getRuleType()) {
case ALL:
case ALL_FIELDS:
{
// Access flags check.
if (!getAccessFlags().containsAll(field.getAccessFlags())
|| !getNegatedAccessFlags().containsNone(field.getAccessFlags())) {
break;
}
// Annotations check.
return RootSetBuilder.containsAllAnnotations(
annotations, field, matchedAnnotationsConsumer);
}
case FIELD:
{
// Name check.
String name = stringCache.lookupString(originalSignature.name);
if (!getName().matches(name)) {
break;
}
// Access flags check.
if (!getAccessFlags().containsAll(field.getAccessFlags())
|| !getNegatedAccessFlags().containsNone(field.getAccessFlags())) {
break;
}
// Type check.
if (!getType().matches(originalSignature.type, appView)) {
break;
}
// Annotations check
return RootSetBuilder.containsAllAnnotations(
annotations, field, matchedAnnotationsConsumer);
}
case ALL_METHODS:
case CLINIT:
case INIT:
case CONSTRUCTOR:
case METHOD:
break;
}
return false;
}
public boolean matches(
DexClassAndMethod method,
AppView<?> appView,
Consumer<AnnotationMatchResult> matchedAnnotationsConsumer,
DexStringCache stringCache) {
DexMethod originalSignature =
appView.graphLens().getOriginalMethodSignature(method.getReference());
switch (getRuleType()) {
case ALL_METHODS:
if (method.getDefinition().isClassInitializer()) {
break;
}
// Fall through for all other methods.
case ALL:
{
// Access flags check.
if (!getAccessFlags().containsAll(method.getAccessFlags())
|| !getNegatedAccessFlags().containsNone(method.getAccessFlags())) {
break;
}
// Annotations check.
return RootSetBuilder.containsAllAnnotations(
annotations, method, matchedAnnotationsConsumer);
}
case METHOD:
// Check return type.
if (!type.matches(originalSignature.getReturnType(), appView)) {
break;
}
// 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)) {
break;
}
// Access flags check.
if (!getAccessFlags().containsAll(method.getAccessFlags())
|| !getNegatedAccessFlags().containsNone(method.getAccessFlags())) {
break;
}
// Annotations check.
if (!RootSetBuilder.containsAllAnnotations(
annotations, method, matchedAnnotationsConsumer)) {
return false;
}
// Parameter types check.
List<ProguardTypeMatcher> arguments = getArguments();
if (arguments.size() == 1 && arguments.get(0).isTripleDotPattern()) {
return true;
}
DexType[] parameters = originalSignature.getParameters().values;
if (parameters.length != arguments.size()) {
break;
}
for (int i = 0; i < parameters.length; i++) {
if (!arguments.get(i).matches(parameters[i], appView)) {
return false;
}
}
// All parameters matched.
return true;
}
case ALL_FIELDS:
case FIELD:
break;
}
return false;
}
public boolean isSpecific() {
switch (getRuleType()) {
case ALL:
// fall through
case ALL_FIELDS:
// fall through
case ALL_METHODS:
return false;
default:
return Iterables.size(getWildcards()) == 0;
}
}
public boolean hasBackReference() {
return IterableUtils.hasNext(getWildcardsThatMatches(ProguardWildcard::isBackReference));
}
final Iterable<ProguardWildcard> getWildcards() {
return getWildcardsThatMatches(alwaysTrue());
}
<T extends ProguardWildcard> Iterable<T> getWildcardsThatMatches(
Predicate<? super ProguardWildcard> predicate) {
return Iterables.concat(
ProguardTypeMatcher.getWildcardsThatMatchesOrEmpty(annotations, predicate),
ProguardTypeMatcher.getWildcardsThatMatchesOrEmpty(type, predicate),
ProguardNameMatcher.getWildcardsThatMatchesOrEmpty(name, predicate),
arguments == null
? IterableUtils.empty()
: IterableUtils.flatMap(
arguments, argument -> argument.getWildcardsThatMatches(predicate)));
}
ProguardMemberRule materialize(DexItemFactory dexItemFactory) {
return new ProguardMemberRule(
ProguardTypeMatcher.materializeList(getAnnotations(), dexItemFactory),
getAccessFlags(),
getNegatedAccessFlags(),
getRuleType(),
getType() == null ? null : getType().materialize(dexItemFactory),
getName() == null ? null : getName().materialize(),
getArguments() == null
? null
: getArguments().stream()
.map(argument -> argument.materialize(dexItemFactory))
.collect(Collectors.toList()),
getPreconditions(),
getReturnValue());
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ProguardMemberRule)) {
return false;
}
ProguardMemberRule that = (ProguardMemberRule) o;
if (!annotations.equals(that.annotations)) {
return false;
}
if (!accessFlags.equals(that.accessFlags)) {
return false;
}
if (!negatedAccessFlags.equals(that.negatedAccessFlags)) {
return false;
}
if (ruleType != that.ruleType) {
return false;
}
if (name != null ? !name.equals(that.name) : that.name != null) {
return false;
}
if (type != null ? !type.equals(that.type) : that.type != null) {
return false;
}
return Objects.equals(arguments, that.arguments)
&& Objects.equals(preconditions, that.preconditions);
}
@Override
public int hashCode() {
int result = annotations.hashCode();
result = 31 * result + accessFlags.hashCode();
result = 31 * result + negatedAccessFlags.hashCode();
result = 31 * result + Objects.hashCode(ruleType);
result = 31 * result + Objects.hashCode(type);
result = 31 * result + Objects.hashCode(name);
result = 31 * result + Objects.hashCode(arguments);
result = 31 * result + Objects.hashCode(preconditions);
return result;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
for (ProguardTypeMatcher annotation : annotations) {
ProguardKeepRule.appendNonEmpty(result, "@", annotation, " ");
}
ProguardKeepRule.appendNonEmpty(result, null, accessFlags, " ");
ProguardKeepRule
.appendNonEmpty(result, null, negatedAccessFlags.toString().replace(" ", " !"), " ");
switch (getRuleType()) {
case ALL_FIELDS:
result.append("<fields>");
break;
case ALL_METHODS:
result.append("<methods>");
break;
case METHOD:
result.append(getType());
result.append(' ');
// Fall through for rest of method signature.
case CONSTRUCTOR:
case CLINIT:
case INIT: {
result.append(getName());
result.append('(');
if (hasPreconditions()) {
assert getPreconditions().size() <= getArguments().size();
int i = 0;
for (; i < getPreconditions().size(); i++) {
if (i > 0) {
result.append(',');
}
result.append(getArguments().get(i));
if (getPreconditions().get(i) != null) {
result.append(" = ").append(getPreconditions().get(i).getValueString());
}
}
for (; i < getArguments().size(); i++) {
result.append(',').append(getArguments().get(i));
}
} else {
result.append(StringUtils.join(",", getArguments()));
}
result.append(')');
break;
}
case FIELD: {
result.append(getType());
result.append(' ');
result.append(getName());
break;
}
case ALL: {
result.append("*");
break;
}
default:
throw new Unreachable("Unknown kind of member rule");
}
if (hasReturnValue()) {
result.append(returnValue.toString());
}
return result.toString();
}
public static ProguardMemberRule defaultKeepAllRule() {
ProguardMemberRule.Builder ruleBuilder = new ProguardMemberRule.Builder();
ruleBuilder.setRuleType(ProguardMemberType.ALL);
return ruleBuilder.build();
}
}