| // 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(); |
| } |
| } |