blob: f3e6c6f33732a685c346fca6559f19b67b7cb97f [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 com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexAccessFlags;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.ImmutableList;
import java.util.List;
public class ProguardMemberRule {
public static class Builder {
private ProguardTypeMatcher annotation;
private DexAccessFlags accessFlags = new DexAccessFlags(0);
private DexAccessFlags negatedAccessFlags = new DexAccessFlags(0);
private ProguardMemberType ruleType;
private ProguardNameMatcher name;
private ProguardTypeMatcher type;
private List<ProguardTypeMatcher> arguments;
private ProguardMemberRuleReturnValue returnValue;
private Builder() {}
public void setAnnotation(ProguardTypeMatcher annotation) {
this.annotation = annotation;
}
public DexAccessFlags getAccessFlags() {
return accessFlags;
}
public void setAccessFlags(DexAccessFlags flags) {
accessFlags = flags;
}
public DexAccessFlags getNegatedAccessFlags() {
return negatedAccessFlags;
}
public void setNegatedAccessFlags(DexAccessFlags flags) {
negatedAccessFlags = flags;
}
public void setRuleType(ProguardMemberType ruleType) {
this.ruleType = ruleType;
}
public void setName(String name) {
this.name = ProguardNameMatcher.create(name);
}
public ProguardTypeMatcher getTypeMatcher() {
return type;
}
public void setTypeMatcher(ProguardTypeMatcher type) {
this.type = type;
}
public void setArguments(List<ProguardTypeMatcher> arguments) {
this.arguments = arguments;
}
public void setReturnValue(ProguardMemberRuleReturnValue value) {
returnValue = value;
}
public boolean isValid() {
return ruleType != null;
}
public ProguardMemberRule build() {
assert isValid();
return new ProguardMemberRule(annotation, accessFlags, negatedAccessFlags, ruleType, name,
type, arguments, returnValue);
}
}
private final ProguardTypeMatcher annotation;
private final DexAccessFlags accessFlags;
private final DexAccessFlags negatedAccessFlags;
private final ProguardMemberType ruleType;
private final ProguardNameMatcher name;
private final ProguardTypeMatcher type;
private final List<ProguardTypeMatcher> arguments;
private final ProguardMemberRuleReturnValue returnValue;
private ProguardMemberRule(
ProguardTypeMatcher annotation,
DexAccessFlags accessFlags,
DexAccessFlags negatedAccessFlags,
ProguardMemberType ruleType,
ProguardNameMatcher name,
ProguardTypeMatcher type,
List<ProguardTypeMatcher> arguments,
ProguardMemberRuleReturnValue returnValue) {
this.annotation = annotation;
this.accessFlags = accessFlags;
this.negatedAccessFlags = negatedAccessFlags;
this.ruleType = ruleType;
this.name = name;
this.type = type;
this.arguments = arguments != null ? ImmutableList.copyOf(arguments) : null;
this.returnValue = returnValue;
}
/**
* Create a new empty builder.
*/
public static Builder builder() {
return new Builder();
}
public ProguardTypeMatcher getAnnotation() {
return annotation;
}
public DexAccessFlags getAccessFlags() {
return accessFlags;
}
public DexAccessFlags getNegatedAccessFlags() {
return negatedAccessFlags;
}
public ProguardMemberType getRuleType() {
return ruleType;
}
public ProguardNameMatcher getName() {
return name;
}
public ProguardTypeMatcher getType() {
return type;
}
public List<ProguardTypeMatcher> getArguments() {
return arguments;
}
public boolean hasReturnValue() {
return returnValue != null;
}
public ProguardMemberRuleReturnValue getReturnValue() {
return returnValue;
}
public ProguardTypeMatcher getTypeMatcher() {
return type;
}
public boolean matches(DexEncodedField field, RootSetBuilder builder) {
switch (getRuleType()) {
case ALL:
case ALL_FIELDS:
// Access flags check.
if (!field.accessFlags.containsAllOf(getAccessFlags()) ||
!field.accessFlags.containsNoneOf(getNegatedAccessFlags())) {
break;
}
// Annotations check.
return RootSetBuilder.containsAnnotation(annotation, field.annotations);
case FIELD:
// Name check.
String name = builder.lookupString(field.field.name);
if (!getName().matches(name)) {
break;
}
// Access flags check.
if (!(field.accessFlags.containsAllOf(getAccessFlags()) &&
field.accessFlags.containsNoneOf(getNegatedAccessFlags()))) {
break;
}
// Type check.
if (!this.type.matches(field.field.type)) {
break;
}
// Annotations check
if (!RootSetBuilder.containsAnnotation(annotation, field.annotations)) {
break;
}
return true;
case ALL_METHODS:
case INIT:
case CONSTRUCTOR:
case METHOD:
break;
}
return false;
}
public boolean matches(DexEncodedMethod method, RootSetBuilder builder) {
switch (getRuleType()) {
case ALL_METHODS:
if (method.isClassInitializer()) {
break;
}
case ALL:
// Access flags check.
if (!method.accessFlags.containsAllOf(getAccessFlags()) ||
!method.accessFlags.containsNoneOf(getNegatedAccessFlags())) {
break;
}
// Annotations check.
return RootSetBuilder.containsAnnotation(annotation, method.annotations);
case METHOD:
// Check return type.
if (!type.matches(method.method.proto.returnType)) {
break;
}
// Fall through for access flags, name and arguments.
case CONSTRUCTOR:
case INIT:
// Name check.
String name = builder.lookupString(method.method.name);
if (!getName().matches(name)) {
break;
}
// Access flags check.
if (!(method.accessFlags.containsAllOf(getAccessFlags()) &&
method.accessFlags.containsNoneOf(getNegatedAccessFlags()))) {
break;
}
// Annotations check.
if (!RootSetBuilder.containsAnnotation(annotation, method.annotations)) {
break;
}
// Parameter types check.
List<ProguardTypeMatcher> arguments = getArguments();
if (arguments.size() == 1 && arguments.get(0).isTripleDotPattern()) {
return true;
} else {
DexType[] parameters = method.method.proto.parameters.values;
if (parameters.length != arguments.size()) {
break;
}
int i = 0;
for (; i < parameters.length; i++) {
if (!arguments.get(i).matches(parameters[i])) {
break;
}
}
if (i == parameters.length) {
// All parameters matched.
return true;
}
}
break;
case ALL_FIELDS:
case FIELD:
break;
}
return false;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ProguardMemberRule)) {
return false;
}
ProguardMemberRule that = (ProguardMemberRule) o;
if (annotation != null ? !annotation.equals(that.annotation) : that.annotation != null) {
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 arguments != null ? arguments.equals(that.arguments) : that.arguments == null;
}
@Override
public int hashCode() {
int result = annotation != null ? annotation.hashCode() : 0;
result = 31 * result + accessFlags.hashCode();
result = 31 * result + negatedAccessFlags.hashCode();
result = 31 * result + (ruleType != null ? ruleType.hashCode() : 0);
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (type != null ? type.hashCode() : 0);
result = 31 * result + (arguments != null ? arguments.hashCode() : 0);
return result;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
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(' ');
case CONSTRUCTOR:
case INIT: {
result.append(getName());
result.append('(');
result.append(StringUtils.join(getArguments(), ","));
result.append(')');
break;
}
case FIELD: {
result.append(getType());
result.append(' ');
result.append(getName());
break;
}
case ALL: {
result.append("<all>");
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();
}
}