|  | // Copyright (c) 2019, 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.graph.DexProgramClass.asProgramClassOrNull; | 
|  |  | 
|  | import com.android.tools.r8.graph.AppInfoWithClassHierarchy; | 
|  | import com.android.tools.r8.graph.AppView; | 
|  | import com.android.tools.r8.graph.DexClass; | 
|  | import com.android.tools.r8.graph.DexClassAndField; | 
|  | import com.android.tools.r8.graph.DexClassAndMethod; | 
|  | import com.android.tools.r8.graph.DexDefinition; | 
|  | import com.android.tools.r8.graph.DexEncodedField; | 
|  | import com.android.tools.r8.graph.DexEncodedMethod; | 
|  | import com.android.tools.r8.graph.DexItemFactory; | 
|  | import com.android.tools.r8.graph.DexProgramClass; | 
|  | import com.android.tools.r8.graph.DexReference; | 
|  | import com.android.tools.r8.graph.DexType; | 
|  | import com.android.tools.r8.graph.SubtypingInfo; | 
|  | import com.android.tools.r8.shaking.InlineRule.Type; | 
|  | import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSet; | 
|  | import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder; | 
|  | import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder; | 
|  | import com.android.tools.r8.utils.InternalOptions.TestingOptions.ProguardIfRuleEvaluationData; | 
|  | import com.android.tools.r8.utils.ThreadUtils; | 
|  | import com.google.common.base.Equivalence.Wrapper; | 
|  | import com.google.common.collect.ImmutableSet; | 
|  | import com.google.common.collect.Iterables; | 
|  | import com.google.common.collect.Sets; | 
|  | import java.util.ArrayList; | 
|  | import java.util.Collection; | 
|  | import java.util.Iterator; | 
|  | import java.util.List; | 
|  | import java.util.Map; | 
|  | import java.util.Set; | 
|  | import java.util.concurrent.ExecutionException; | 
|  | import java.util.concurrent.ExecutorService; | 
|  | import java.util.concurrent.Future; | 
|  | import java.util.stream.Collectors; | 
|  |  | 
|  | public class IfRuleEvaluator { | 
|  |  | 
|  | private final AppView<? extends AppInfoWithClassHierarchy> appView; | 
|  | private final SubtypingInfo subtypingInfo; | 
|  | private final Enqueuer enqueuer; | 
|  | private final ExecutorService executorService; | 
|  | private final List<Future<?>> futures = new ArrayList<>(); | 
|  | private final Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> ifRules; | 
|  | private final ConsequentRootSetBuilder rootSetBuilder; | 
|  |  | 
|  | IfRuleEvaluator( | 
|  | AppView<? extends AppInfoWithClassHierarchy> appView, | 
|  | SubtypingInfo subtypingInfo, | 
|  | Enqueuer enqueuer, | 
|  | ExecutorService executorService, | 
|  | Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> ifRules, | 
|  | ConsequentRootSetBuilder rootSetBuilder) { | 
|  | this.appView = appView; | 
|  | this.subtypingInfo = subtypingInfo; | 
|  | this.enqueuer = enqueuer; | 
|  | this.executorService = executorService; | 
|  | this.ifRules = ifRules; | 
|  | this.rootSetBuilder = rootSetBuilder; | 
|  | } | 
|  |  | 
|  | public ConsequentRootSet run() throws ExecutionException { | 
|  | appView.appInfo().app().timing.begin("Find consequent items for -if rules..."); | 
|  | try { | 
|  | if (ifRules != null && !ifRules.isEmpty()) { | 
|  | Iterator<Map.Entry<Wrapper<ProguardIfRule>, Set<ProguardIfRule>>> it = | 
|  | ifRules.entrySet().iterator(); | 
|  | while (it.hasNext()) { | 
|  | Map.Entry<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> ifRuleEntry = it.next(); | 
|  | ProguardIfRule ifRuleKey = ifRuleEntry.getKey().get(); | 
|  | Set<ProguardIfRule> ifRulesInEquivalence = ifRuleEntry.getValue(); | 
|  | ProguardIfRuleEvaluationData ifRuleEvaluationData = | 
|  | appView.options().testing.proguardIfRuleEvaluationData; | 
|  | List<ProguardIfRule> toRemove = new ArrayList<>(); | 
|  |  | 
|  | // Depending on which types that trigger the -if rule, the application of the subsequent | 
|  | // -keep rule may vary (due to back references). So, we need to try all pairs of -if | 
|  | // rule and live types. | 
|  | for (DexProgramClass clazz : | 
|  | ifRuleKey.relevantCandidatesForRule( | 
|  | appView, subtypingInfo, appView.appInfo().classes())) { | 
|  | if (!isEffectivelyLive(clazz)) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | // Check if the class matches the if-rule. | 
|  | if (appView.options().testing.measureProguardIfRuleEvaluations) { | 
|  | ifRuleEvaluationData.numberOfProguardIfRuleClassEvaluations++; | 
|  | } | 
|  | if (evaluateClassForIfRule(ifRuleKey, clazz)) { | 
|  | // When matching an if rule against a type, the if-rule are filled with the current | 
|  | // capture of wildcards. Propagate this down to member rules with same class part | 
|  | // equivalence. | 
|  | ifRulesInEquivalence.forEach( | 
|  | ifRule -> { | 
|  | registerClassCapture(ifRule, clazz, clazz); | 
|  | if (appView.options().testing.measureProguardIfRuleEvaluations) { | 
|  | ifRuleEvaluationData.numberOfProguardIfRuleMemberEvaluations++; | 
|  | } | 
|  | boolean matched = evaluateIfRuleMembersAndMaterialize(ifRule, clazz, clazz); | 
|  | if (matched && canRemoveSubsequentKeepRule(ifRule)) { | 
|  | toRemove.add(ifRule); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | // Check if one of the types that have been merged into `clazz` satisfies the if-rule. | 
|  | if (appView.verticallyMergedClasses() != null) { | 
|  | Iterable<DexType> sources = | 
|  | appView.verticallyMergedClasses().getSourcesFor(clazz.type); | 
|  | for (DexType sourceType : sources) { | 
|  | // Note that, although `sourceType` has been merged into `type`, the dex class for | 
|  | // `sourceType` is still available until the second round of tree shaking. This | 
|  | // way we can still retrieve the access flags of `sourceType`. | 
|  | DexProgramClass sourceClass = | 
|  | asProgramClassOrNull( | 
|  | appView.appInfo().definitionForWithoutExistenceAssert(sourceType)); | 
|  | if (sourceClass == null) { | 
|  | // TODO(b/266049507): The evaluation of -if rules in the final round of tree | 
|  | //  shaking and during -whyareyoukeeping should be the same. Currently the pruning | 
|  | //  of classes changes behavior. | 
|  | assert enqueuer.getMode().isWhyAreYouKeeping(); | 
|  | continue; | 
|  | } | 
|  | if (appView.options().testing.measureProguardIfRuleEvaluations) { | 
|  | ifRuleEvaluationData.numberOfProguardIfRuleClassEvaluations++; | 
|  | } | 
|  | if (evaluateClassForIfRule(ifRuleKey, sourceClass)) { | 
|  | ifRulesInEquivalence.forEach( | 
|  | ifRule -> { | 
|  | registerClassCapture(ifRule, sourceClass, clazz); | 
|  | if (appView.options().testing.measureProguardIfRuleEvaluations) { | 
|  | ifRuleEvaluationData.numberOfProguardIfRuleMemberEvaluations++; | 
|  | } | 
|  | if (evaluateIfRuleMembersAndMaterialize(ifRule, sourceClass, clazz) | 
|  | && canRemoveSubsequentKeepRule(ifRule)) { | 
|  | toRemove.add(ifRule); | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | if (ifRulesInEquivalence.size() == toRemove.size()) { | 
|  | it.remove(); | 
|  | } else if (!toRemove.isEmpty()) { | 
|  | ifRulesInEquivalence.removeAll(toRemove); | 
|  | } | 
|  | } | 
|  | ThreadUtils.awaitFutures(futures); | 
|  | } | 
|  | } finally { | 
|  | appView.appInfo().app().timing.end(); | 
|  | } | 
|  | return rootSetBuilder.buildConsequentRootSet(); | 
|  | } | 
|  |  | 
|  | private boolean canRemoveSubsequentKeepRule(ProguardIfRule rule) { | 
|  | return Iterables.isEmpty(rule.subsequentRule.getWildcards()); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * To ensure the matching work correctly going forward, when a class has matched, we have to run | 
|  | * the capturing again on the member rule to ensure the wild cards are correctly populated. | 
|  | * | 
|  | * @param memberRule The member rule to populate with wildcards. | 
|  | * @param source The source class. | 
|  | * @param target The target class that can be different when we have vertically merged classes. | 
|  | */ | 
|  | private void registerClassCapture(ProguardIfRule memberRule, DexClass source, DexClass target) { | 
|  | boolean classNameResult = memberRule.getClassNames().matches(source.type); | 
|  | assert classNameResult; | 
|  | if (memberRule.hasInheritanceClassName()) { | 
|  | boolean inheritanceResult = rootSetBuilder.satisfyInheritanceRule(target, memberRule); | 
|  | assert inheritanceResult; | 
|  | } | 
|  | } | 
|  |  | 
|  | private boolean isEffectivelyLive(DexProgramClass clazz) { | 
|  | // A type is effectively live if (1) it is truly live, (2) the value of one of its fields has | 
|  | // been inlined by the member value propagation, or (3) the return value of one of its methods | 
|  | // has been forwarded by the member value propagation. | 
|  | if (enqueuer.isTypeLive(clazz)) { | 
|  | return true; | 
|  | } | 
|  | for (DexEncodedField field : clazz.fields()) { | 
|  | if (field.getOptimizationInfo().valueHasBeenPropagated()) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | for (DexEncodedMethod method : clazz.methods()) { | 
|  | if (method.getOptimizationInfo().returnValueHasBeenPropagated()) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /** Determines if {@param clazz} satisfies the given if-rule class specification. */ | 
|  | private boolean evaluateClassForIfRule(ProguardIfRule rule, DexProgramClass clazz) { | 
|  | if (!RootSetBuilder.satisfyClassType(rule, clazz)) { | 
|  | return false; | 
|  | } | 
|  | if (!RootSetBuilder.satisfyAccessFlag(rule, clazz)) { | 
|  | return false; | 
|  | } | 
|  | AnnotationMatchResult annotationMatchResult = RootSetBuilder.satisfyAnnotation(rule, clazz); | 
|  | if (annotationMatchResult == null) { | 
|  | return false; | 
|  | } | 
|  | rootSetBuilder.handleMatchedAnnotation(annotationMatchResult); | 
|  | if (!rule.getClassNames().matches(clazz.type)) { | 
|  | return false; | 
|  | } | 
|  | if (rule.hasInheritanceClassName()) { | 
|  | // Try another live type since the current one doesn't satisfy the inheritance rule. | 
|  | return rootSetBuilder.satisfyInheritanceRule(clazz, rule); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | private boolean evaluateIfRuleMembersAndMaterialize( | 
|  | ProguardIfRule rule, DexClass sourceClass, DexClass targetClass) { | 
|  | Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules(); | 
|  | if (memberKeepRules.isEmpty()) { | 
|  | materializeIfRule(rule, ImmutableSet.of(sourceClass.getReference())); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | List<DexClassAndField> fieldsInlinedByJavaC = new ArrayList<>(); | 
|  | Set<DexDefinition> filteredMembers = Sets.newIdentityHashSet(); | 
|  | Iterables.addAll( | 
|  | filteredMembers, | 
|  | targetClass.fields( | 
|  | f -> { | 
|  | // Fields that are javac inlined are unsound as predicates for conditional rules. | 
|  | // Ignore any such field members and record it for possible reporting later. | 
|  | if (isFieldInlinedByJavaC(f)) { | 
|  | fieldsInlinedByJavaC.add(DexClassAndField.create(targetClass, f)); | 
|  | return false; | 
|  | } | 
|  | // Fields referenced only by -keep may not be referenced, we therefore have to | 
|  | // filter on both live and referenced. | 
|  | return (enqueuer.isFieldLive(f) | 
|  | || enqueuer.isFieldReferenced(f) | 
|  | || f.getOptimizationInfo().valueHasBeenPropagated()) | 
|  | && (appView.graphLens().getOriginalFieldSignature(f.getReference()).holder | 
|  | == sourceClass.type); | 
|  | })); | 
|  | Iterables.addAll( | 
|  | filteredMembers, | 
|  | targetClass.methods( | 
|  | m -> | 
|  | (enqueuer.isMethodLive(m) | 
|  | || enqueuer.isMethodTargeted(m) | 
|  | || m.getOptimizationInfo().returnValueHasBeenPropagated()) | 
|  | && appView.graphLens().getOriginalMethodSignature(m.getReference()).holder | 
|  | == sourceClass.type)); | 
|  |  | 
|  | // Check if the rule could hypothetically have matched a javac inlined field. | 
|  | // If so mark the rule. Reporting happens only if the rule is otherwise unused. | 
|  | if (!fieldsInlinedByJavaC.isEmpty()) { | 
|  | for (ProguardMemberRule memberRule : memberKeepRules) { | 
|  | if (!memberRule.getRuleType().includesFields()) { | 
|  | continue; | 
|  | } | 
|  | for (DexClassAndField field : fieldsInlinedByJavaC) { | 
|  | if (rootSetBuilder.sideEffectFreeIsRuleSatisfiedByField(memberRule, field)) { | 
|  | rule.addInlinableFieldMatchingPrecondition(field.getReference()); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // If the number of member rules to hold is more than live members, we can't make it. | 
|  | if (filteredMembers.size() < memberKeepRules.size()) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Depending on which members trigger the -if rule, the application of the subsequent | 
|  | // -keep rule may vary (due to back references). So, we need to try literally all | 
|  | // combinations of live members. But, we can at least limit the number of elements per | 
|  | // combination as the size of member rules to satisfy. | 
|  | // TODO(b/206086945): Consider ways of reducing the size of this set computation. | 
|  | for (Set<DexDefinition> combination : | 
|  | Sets.combinations(filteredMembers, memberKeepRules.size())) { | 
|  | Collection<DexClassAndField> fieldsInCombination = | 
|  | DexDefinition.filterDexEncodedField( | 
|  | combination.stream(), field -> DexClassAndField.create(targetClass, field)) | 
|  | .collect(Collectors.toList()); | 
|  | Collection<DexClassAndMethod> methodsInCombination = | 
|  | DexDefinition.filterDexEncodedMethod( | 
|  | combination.stream(), method -> DexClassAndMethod.create(targetClass, method)) | 
|  | .collect(Collectors.toList()); | 
|  | // Member rules are combined as AND logic: if found unsatisfied member rule, this | 
|  | // combination of live members is not a good fit. | 
|  | boolean satisfied = | 
|  | memberKeepRules.stream() | 
|  | .allMatch( | 
|  | memberRule -> | 
|  | rootSetBuilder.ruleSatisfiedByFields(memberRule, fieldsInCombination) | 
|  | || rootSetBuilder.ruleSatisfiedByMethods( | 
|  | memberRule, methodsInCombination)); | 
|  | if (satisfied) { | 
|  | materializeIfRule(rule, ImmutableSet.of(sourceClass.getReference())); | 
|  | if (canRemoveSubsequentKeepRule(rule)) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | private boolean isFieldInlinedByJavaC(DexEncodedField field) { | 
|  | if (enqueuer.getMode().isFinalTreeShaking()) { | 
|  | // Ignore any field value in the final tree shaking pass so it remains consistent with the | 
|  | // initial pass. | 
|  | return field.getIsInlinableByJavaC(); | 
|  | } | 
|  | return field.getOrComputeIsInlinableByJavaC(appView.dexItemFactory()); | 
|  | } | 
|  |  | 
|  | private void materializeIfRule(ProguardIfRule rule, Set<DexReference> preconditions) { | 
|  | DexItemFactory dexItemFactory = appView.dexItemFactory(); | 
|  | ProguardIfRule materializedRule = rule.materialize(dexItemFactory, preconditions); | 
|  |  | 
|  | if (enqueuer.getMode().isInitialTreeShaking() && !rule.isUsed()) { | 
|  | // We need to abort class inlining of classes that could be matched by the condition of this | 
|  | // -if rule. | 
|  | ClassInlineRule neverClassInlineRuleForCondition = | 
|  | materializedRule.neverClassInlineRuleForCondition(dexItemFactory); | 
|  | if (neverClassInlineRuleForCondition != null) { | 
|  | rootSetBuilder.runPerRule(executorService, futures, neverClassInlineRuleForCondition, null); | 
|  | } | 
|  |  | 
|  | InlineRule neverInlineForClassInliningRuleForCondition = | 
|  | materializedRule.neverInlineRuleForCondition(dexItemFactory, Type.NEVER_CLASS_INLINE); | 
|  | if (neverInlineForClassInliningRuleForCondition != null) { | 
|  | rootSetBuilder.runPerRule( | 
|  | executorService, futures, neverInlineForClassInliningRuleForCondition, null); | 
|  | } | 
|  |  | 
|  | // If the condition of the -if rule has any members, then we need to keep these members to | 
|  | // ensure that the subsequent rule will be applied again in the second round of tree | 
|  | // shaking. | 
|  | InlineRule neverInlineRuleForCondition = | 
|  | materializedRule.neverInlineRuleForCondition(dexItemFactory, Type.NEVER); | 
|  | if (neverInlineRuleForCondition != null) { | 
|  | rootSetBuilder.runPerRule(executorService, futures, neverInlineRuleForCondition, null); | 
|  | } | 
|  |  | 
|  | // Prevent horizontal class merging of any -if rule members. | 
|  | NoHorizontalClassMergingRule noHorizontalClassMergingRule = | 
|  | materializedRule.noHorizontalClassMergingRuleForCondition(dexItemFactory); | 
|  | if (noHorizontalClassMergingRule != null) { | 
|  | rootSetBuilder.runPerRule(executorService, futures, noHorizontalClassMergingRule, null); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Keep whatever is required by the -if rule. | 
|  | rootSetBuilder.runPerRule( | 
|  | executorService, futures, materializedRule.subsequentRule, materializedRule); | 
|  | rule.markAsUsed(); | 
|  | } | 
|  | } |