blob: 2aba15e0a1ad88e3ad49f21a0bfba00b5327856e [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.AppView;
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;
import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
import com.android.tools.r8.utils.StringUtils;
import com.google.common.collect.Iterables;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
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 ProguardMemberRuleReturnValue 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 setReturnValue(ProguardMemberRuleReturnValue 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,
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 ProguardMemberRuleReturnValue returnValue;
private ProguardMemberRule(
List<ProguardTypeMatcher> annotations,
ProguardAccessFlags accessFlags,
ProguardAccessFlags negatedAccessFlags,
ProguardMemberType ruleType,
ProguardTypeMatcher type,
ProguardNameMatcher name,
List<ProguardTypeMatcher> arguments,
ProguardMemberRuleReturnValue 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.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 ProguardTypeMatcher getType() {
return type;
}
public ProguardNameMatcher getName() {
return name;
}
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,
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.accessFlags)
|| !getNegatedAccessFlags().containsNone(field.accessFlags)) {
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.accessFlags)
|| !getNegatedAccessFlags().containsNone(field.accessFlags)) {
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(
DexEncodedMethod method,
AppView<?> appView,
Consumer<AnnotationMatchResult> matchedAnnotationsConsumer,
DexStringCache stringCache) {
DexMethod originalSignature =
appView.graphLens().getOriginalMethodSignature(method.getReference());
switch (getRuleType()) {
case ALL_METHODS:
if (method.isClassInitializer()) {
break;
}
// Fall through for all other methods.
case ALL:
{
// Access flags check.
if (!getAccessFlags().containsAll(method.accessFlags)
|| !getNegatedAccessFlags().containsNone(method.accessFlags)) {
break;
}
// Annotations check.
return RootSetBuilder.containsAllAnnotations(
annotations, method, matchedAnnotationsConsumer);
}
case METHOD:
// Check return type.
if (!type.matches(originalSignature.proto.returnType, 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.accessFlags)
|| !getNegatedAccessFlags().containsNone(method.accessFlags)) {
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.proto.parameters.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;
}
}
Iterable<ProguardWildcard> getWildcards() {
return Iterables.concat(
ProguardTypeMatcher.getWildcardsOrEmpty(annotations),
ProguardTypeMatcher.getWildcardsOrEmpty(type),
ProguardNameMatcher.getWildcardsOrEmpty(name),
arguments != null
? arguments.stream()
.map(ProguardTypeMatcher::getWildcards)
.flatMap(it -> StreamSupport.stream(it.spliterator(), false))
::iterator
: Collections::emptyIterator);
}
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()),
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 arguments != null ? arguments.equals(that.arguments) : that.arguments == null;
}
@Override
public int hashCode() {
int result = annotations.hashCode();
result = 31 * result + accessFlags.hashCode();
result = 31 * result + negatedAccessFlags.hashCode();
result = 31 * result + (ruleType != null ? ruleType.hashCode() : 0);
result = 31 * result + (type != null ? type.hashCode() : 0);
result = 31 * result + (name != null ? name.hashCode() : 0);
result = 31 * result + (arguments != null ? arguments.hashCode() : 0);
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('(');
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();
}
}