| // 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.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull; |
| import static com.android.tools.r8.utils.LensUtils.rewriteAndApplyIfNotPrimitiveType; |
| |
| import com.android.tools.r8.dex.Constants; |
| import com.android.tools.r8.errors.AssumeNoSideEffectsRuleForObjectMembersDiagnostic; |
| 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.BottomUpClassHierarchyTraversal; |
| import com.android.tools.r8.graph.DexAnnotation; |
| import com.android.tools.r8.graph.DexAnnotationSet; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexDefinition; |
| import com.android.tools.r8.graph.DexDefinitionSupplier; |
| import com.android.tools.r8.graph.DexEncodedField; |
| import com.android.tools.r8.graph.DexEncodedMember; |
| import com.android.tools.r8.graph.DexEncodedMethod; |
| import com.android.tools.r8.graph.DexField; |
| import com.android.tools.r8.graph.DexLibraryClass; |
| import com.android.tools.r8.graph.DexMember; |
| import com.android.tools.r8.graph.DexMethod; |
| 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.DirectMappedDexApplication; |
| import com.android.tools.r8.graph.GraphLens; |
| import com.android.tools.r8.graph.ProgramMember; |
| import com.android.tools.r8.graph.ProgramMethod; |
| import com.android.tools.r8.graph.PrunedItems; |
| import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult; |
| import com.android.tools.r8.graph.SubtypingInfo; |
| import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker; |
| import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple; |
| import com.android.tools.r8.logging.Log; |
| import com.android.tools.r8.shaking.AnnotationMatchResult.AnnotationsIgnoredMatchResult; |
| import com.android.tools.r8.shaking.AnnotationMatchResult.ConcreteAnnotationMatchResult; |
| import com.android.tools.r8.shaking.DelayedRootSetActionItem.InterfaceMethodSyntheticBridgeAction; |
| import com.android.tools.r8.utils.ArrayUtils; |
| import com.android.tools.r8.utils.Consumer3; |
| import com.android.tools.r8.utils.InternalOptions; |
| import com.android.tools.r8.utils.MethodSignatureEquivalence; |
| import com.android.tools.r8.utils.OriginWithPosition; |
| import com.android.tools.r8.utils.PredicateSet; |
| import com.android.tools.r8.utils.StringDiagnostic; |
| import com.android.tools.r8.utils.ThreadUtils; |
| import com.google.common.base.Equivalence.Wrapper; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| import com.google.common.collect.Streams; |
| import java.io.PrintStream; |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Deque; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Queue; |
| import java.util.Set; |
| import java.util.Stack; |
| import java.util.TreeSet; |
| import java.util.concurrent.ConcurrentLinkedQueue; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Future; |
| import java.util.function.BiConsumer; |
| import java.util.function.Consumer; |
| import java.util.function.Predicate; |
| import java.util.stream.Collectors; |
| |
| public class RootSetUtils { |
| |
| public static class RootSetBuilder { |
| |
| private final AppView<? extends AppInfoWithClassHierarchy> appView; |
| private final SubtypingInfo subtypingInfo; |
| private final DirectMappedDexApplication application; |
| private final Iterable<? extends ProguardConfigurationRule> rules; |
| private final MutableItemsWithRules noShrinking = new MutableItemsWithRules(); |
| private final MutableItemsWithRules softPinned = new MutableItemsWithRules(); |
| private final Set<DexReference> noObfuscation = Sets.newIdentityHashSet(); |
| private final LinkedHashMap<DexReference, DexReference> reasonAsked = new LinkedHashMap<>(); |
| private final LinkedHashMap<DexReference, DexReference> checkDiscarded = new LinkedHashMap<>(); |
| private final Set<DexMethod> alwaysInline = Sets.newIdentityHashSet(); |
| private final Set<DexMethod> forceInline = Sets.newIdentityHashSet(); |
| private final Set<DexMethod> neverInline = Sets.newIdentityHashSet(); |
| private final Set<DexMethod> neverInlineDueToSingleCaller = Sets.newIdentityHashSet(); |
| private final Set<DexMethod> bypassClinitforInlining = Sets.newIdentityHashSet(); |
| private final Set<DexMethod> whyAreYouNotInlining = Sets.newIdentityHashSet(); |
| private final Set<DexMethod> keepParametersWithConstantValue = Sets.newIdentityHashSet(); |
| private final Set<DexMethod> keepUnusedArguments = Sets.newIdentityHashSet(); |
| private final Set<DexMethod> reprocess = Sets.newIdentityHashSet(); |
| private final Set<DexMethod> neverReprocess = Sets.newIdentityHashSet(); |
| private final PredicateSet<DexType> alwaysClassInline = new PredicateSet<>(); |
| private final Set<DexType> neverClassInline = Sets.newIdentityHashSet(); |
| private final Set<DexType> noUnusedInterfaceRemoval = Sets.newIdentityHashSet(); |
| private final Set<DexType> noVerticalClassMerging = Sets.newIdentityHashSet(); |
| private final Set<DexType> noHorizontalClassMerging = Sets.newIdentityHashSet(); |
| private final Set<DexReference> neverPropagateValue = Sets.newIdentityHashSet(); |
| private final Map<DexReference, MutableItemsWithRules> dependentNoShrinking = |
| new IdentityHashMap<>(); |
| private final Map<DexReference, MutableItemsWithRules> dependentSoftPinned = |
| new IdentityHashMap<>(); |
| private final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule = |
| new IdentityHashMap<>(); |
| private final Map<DexReference, ProguardMemberRule> mayHaveSideEffects = |
| new IdentityHashMap<>(); |
| private final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects = new IdentityHashMap<>(); |
| private final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues = new IdentityHashMap<>(); |
| private final Set<DexReference> identifierNameStrings = Sets.newIdentityHashSet(); |
| private final Queue<DelayedRootSetActionItem> delayedRootSetActionItems = |
| new ConcurrentLinkedQueue<>(); |
| private final InternalOptions options; |
| |
| private final DexStringCache dexStringCache = new DexStringCache(); |
| private final Set<ProguardIfRule> ifRules = Sets.newIdentityHashSet(); |
| |
| private final Map<OriginWithPosition, Set<DexMethod>> assumeNoSideEffectsWarnings = |
| new LinkedHashMap<>(); |
| |
| private final OptimizationFeedbackSimple feedback = OptimizationFeedbackSimple.getInstance(); |
| |
| private RootSetBuilder( |
| AppView<? extends AppInfoWithClassHierarchy> appView, |
| SubtypingInfo subtypingInfo, |
| Iterable<? extends ProguardConfigurationRule> rules) { |
| this.appView = appView; |
| this.subtypingInfo = subtypingInfo; |
| this.application = appView.appInfo().app().asDirect(); |
| this.rules = rules; |
| this.options = appView.options(); |
| } |
| |
| private RootSetBuilder( |
| AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) { |
| this(appView, subtypingInfo, null); |
| } |
| |
| void handleMatchedAnnotation(AnnotationMatchResult annotation) { |
| // Intentionally empty. |
| } |
| |
| // Process a class with the keep rule. |
| private void process(DexClass clazz, ProguardConfigurationRule rule, ProguardIfRule ifRule) { |
| if (!satisfyClassType(rule, clazz)) { |
| return; |
| } |
| if (!satisfyAccessFlag(rule, clazz)) { |
| return; |
| } |
| AnnotationMatchResult annotationMatchResult = satisfyAnnotation(rule, clazz); |
| if (annotationMatchResult == null) { |
| return; |
| } |
| handleMatchedAnnotation(annotationMatchResult); |
| // In principle it should make a difference whether the user specified in a class |
| // spec that a class either extends or implements another type. However, proguard |
| // seems not to care, so users have started to use this inconsistently. We are thus |
| // inconsistent, as well, but tell them. |
| // TODO(herhut): One day make this do what it says. |
| if (rule.hasInheritanceClassName() && !satisfyInheritanceRule(clazz, rule)) { |
| return; |
| } |
| |
| if (!rule.getClassNames().matches(clazz.type)) { |
| return; |
| } |
| |
| Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules(); |
| Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier; |
| if (rule instanceof ProguardKeepRule) { |
| if (clazz.isNotProgramClass()) { |
| return; |
| } |
| switch (((ProguardKeepRule) rule).getType()) { |
| case KEEP_CLASS_MEMBERS: |
| // Members mentioned at -keepclassmembers always depend on their holder. |
| preconditionSupplier = ImmutableMap.of(definition -> true, clazz); |
| markMatchingVisibleMethods( |
| clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule); |
| markMatchingVisibleFields( |
| clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule); |
| break; |
| case KEEP_CLASSES_WITH_MEMBERS: |
| if (!allRulesSatisfied(memberKeepRules, clazz)) { |
| break; |
| } |
| // fall through; |
| case KEEP: |
| markClass(clazz, rule, ifRule); |
| preconditionSupplier = new HashMap<>(); |
| if (ifRule != null) { |
| // Static members in -keep are pinned no matter what. |
| preconditionSupplier.put(DexDefinition::isStaticMember, null); |
| // Instance members may need to be kept even though the holder is not instantiated. |
| preconditionSupplier.put(definition -> !definition.isStaticMember(), clazz); |
| } else { |
| // Members mentioned at -keep should always be pinned as long as that -keep rule is |
| // not triggered conditionally. |
| preconditionSupplier.put((definition -> true), null); |
| } |
| markMatchingVisibleMethods( |
| clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule); |
| markMatchingVisibleFields( |
| clazz, memberKeepRules, rule, preconditionSupplier, false, ifRule); |
| break; |
| case CONDITIONAL: |
| throw new Unreachable("-if rule will be evaluated separately, not here."); |
| } |
| return; |
| } |
| // Only the ordinary keep rules are supported in a conditional rule. |
| assert ifRule == null; |
| if (rule instanceof ProguardIfRule) { |
| throw new Unreachable("-if rule will be evaluated separately, not here."); |
| } else if (rule instanceof ProguardCheckDiscardRule) { |
| if (memberKeepRules.isEmpty()) { |
| markClass(clazz, rule, ifRule); |
| } else { |
| preconditionSupplier = ImmutableMap.of((definition -> true), clazz); |
| markMatchingVisibleMethods( |
| clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule); |
| markMatchingVisibleFields( |
| clazz, memberKeepRules, rule, preconditionSupplier, true, ifRule); |
| } |
| } else if (rule instanceof ProguardWhyAreYouKeepingRule) { |
| markClass(clazz, rule, ifRule); |
| markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule); |
| markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule); |
| } else if (rule instanceof ProguardAssumeMayHaveSideEffectsRule |
| || rule instanceof ProguardAssumeNoSideEffectRule |
| || rule instanceof ProguardAssumeValuesRule) { |
| markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule); |
| markMatchingOverriddenMethods( |
| appView.appInfo(), clazz, memberKeepRules, rule, null, true, ifRule); |
| markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule); |
| } else if (rule instanceof InlineRule |
| || rule instanceof ConstantArgumentRule |
| || rule instanceof UnusedArgumentRule |
| || rule instanceof ReprocessMethodRule |
| || rule instanceof WhyAreYouNotInliningRule) { |
| markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule); |
| } else if (rule instanceof ClassInlineRule |
| || rule instanceof NoUnusedInterfaceRemovalRule |
| || rule instanceof NoVerticalClassMergingRule |
| || rule instanceof NoHorizontalClassMergingRule |
| || rule instanceof ReprocessClassInitializerRule) { |
| if (allRulesSatisfied(memberKeepRules, clazz)) { |
| markClass(clazz, rule, ifRule); |
| } |
| } else if (rule instanceof MemberValuePropagationRule) { |
| markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule); |
| markMatchingVisibleFields(clazz, memberKeepRules, rule, null, true, ifRule); |
| } else { |
| assert rule instanceof ProguardIdentifierNameStringRule; |
| markMatchingFields(clazz, memberKeepRules, rule, null, ifRule); |
| markMatchingMethods(clazz, memberKeepRules, rule, null, ifRule); |
| } |
| } |
| |
| void runPerRule( |
| ExecutorService executorService, |
| List<Future<?>> futures, |
| ProguardConfigurationRule rule, |
| ProguardIfRule ifRule) { |
| List<DexType> specifics = rule.getClassNames().asSpecificDexTypes(); |
| if (specifics != null) { |
| // This keep rule only lists specific type matches. |
| // This means there is no need to iterate over all classes. |
| for (DexType type : specifics) { |
| DexClass clazz = application.definitionFor(type); |
| // Ignore keep rule iff it does not reference a class in the app. |
| if (clazz != null) { |
| process(clazz, rule, ifRule); |
| } |
| } |
| return; |
| } |
| |
| futures.add( |
| executorService.submit( |
| () -> { |
| for (DexProgramClass clazz : |
| rule.relevantCandidatesForRule(appView, subtypingInfo, application.classes())) { |
| process(clazz, rule, ifRule); |
| } |
| if (rule.applyToNonProgramClasses()) { |
| for (DexLibraryClass clazz : application.libraryClasses()) { |
| process(clazz, rule, ifRule); |
| } |
| } |
| })); |
| } |
| |
| public RootSet build(ExecutorService executorService) throws ExecutionException { |
| application.timing.begin("Build root set..."); |
| try { |
| List<Future<?>> futures = new ArrayList<>(); |
| // Mark all the things explicitly listed in keep rules. |
| if (rules != null) { |
| for (ProguardConfigurationRule rule : rules) { |
| if (rule instanceof ProguardIfRule) { |
| ProguardIfRule ifRule = (ProguardIfRule) rule; |
| ifRules.add(ifRule); |
| } else { |
| runPerRule(executorService, futures, rule, null); |
| } |
| } |
| ThreadUtils.awaitFutures(futures); |
| } |
| } finally { |
| application.timing.end(); |
| } |
| generateAssumeNoSideEffectsWarnings(); |
| if (!noSideEffects.isEmpty() || !assumedValues.isEmpty()) { |
| BottomUpClassHierarchyTraversal.forAllClasses(appView, subtypingInfo) |
| .visit(appView.appInfo().classes(), this::propagateAssumeRules); |
| } |
| if (appView.options().protoShrinking().enableGeneratedMessageLiteBuilderShrinking) { |
| GeneratedMessageLiteBuilderShrinker.addInliningHeuristicsForBuilderInlining( |
| appView, |
| subtypingInfo, |
| alwaysClassInline, |
| noVerticalClassMerging, |
| noHorizontalClassMerging, |
| alwaysInline, |
| bypassClinitforInlining); |
| } |
| assert Sets.intersection(neverInline, alwaysInline).isEmpty() |
| && Sets.intersection(neverInline, forceInline).isEmpty() |
| : "A method cannot be marked as both -neverinline and -forceinline/-alwaysinline."; |
| assert appView.options().isMinificationEnabled() || noObfuscation.isEmpty(); |
| return new RootSet( |
| noShrinking, |
| softPinned, |
| noObfuscation, |
| ImmutableList.copyOf(reasonAsked.values()), |
| ImmutableList.copyOf(checkDiscarded.values()), |
| alwaysInline, |
| forceInline, |
| neverInline, |
| neverInlineDueToSingleCaller, |
| bypassClinitforInlining, |
| whyAreYouNotInlining, |
| keepParametersWithConstantValue, |
| keepUnusedArguments, |
| reprocess, |
| neverReprocess, |
| alwaysClassInline, |
| neverClassInline, |
| noUnusedInterfaceRemoval, |
| noVerticalClassMerging, |
| noHorizontalClassMerging, |
| neverPropagateValue, |
| mayHaveSideEffects, |
| noSideEffects, |
| assumedValues, |
| dependentNoShrinking, |
| dependentSoftPinned, |
| dependentKeepClassCompatRule, |
| identifierNameStrings, |
| ifRules, |
| Lists.newArrayList(delayedRootSetActionItems)); |
| } |
| |
| private void propagateAssumeRules(DexClass clazz) { |
| Set<DexType> subTypes = subtypingInfo.allImmediateSubtypes(clazz.type); |
| if (subTypes.isEmpty()) { |
| return; |
| } |
| for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) { |
| // If the method has a body, it may have side effects. Don't do bottom-up propagation. |
| if (encodedMethod.hasCode()) { |
| assert !encodedMethod.shouldNotHaveCode(); |
| continue; |
| } |
| propagateAssumeRules(clazz.type, encodedMethod.getReference(), subTypes, noSideEffects); |
| propagateAssumeRules(clazz.type, encodedMethod.getReference(), subTypes, assumedValues); |
| } |
| } |
| |
| private void propagateAssumeRules( |
| DexType type, |
| DexMethod reference, |
| Set<DexType> subTypes, |
| Map<DexMember<?, ?>, ProguardMemberRule> assumeRulePool) { |
| ProguardMemberRule ruleToBePropagated = null; |
| for (DexType subType : subTypes) { |
| DexMethod referenceInSubType = |
| appView.dexItemFactory().createMethod(subType, reference.proto, reference.name); |
| // Those rules are bound to definitions, not references. If the current subtype does not |
| // override the method, and when the retrieval of bound rule fails, it is unclear whether it |
| // is due to the lack of the definition or it indeed means no matching rules. Similar to how |
| // we apply those assume rules, here we use a resolved target. |
| DexEncodedMethod target = |
| appView |
| .appInfo() |
| .unsafeResolveMethodDueToDexFormat(referenceInSubType) |
| .getSingleTarget(); |
| // But, the resolution should not be landed on the current type we are visiting. |
| if (target == null || target.getHolderType() == type) { |
| continue; |
| } |
| ProguardMemberRule ruleInSubType = assumeRulePool.get(target.getReference()); |
| // We are looking for the greatest lower bound of assume rules from all sub types. |
| // If any subtype doesn't have a matching assume rule, the lower bound is literally nothing. |
| if (ruleInSubType == null) { |
| ruleToBePropagated = null; |
| break; |
| } |
| if (ruleToBePropagated == null) { |
| ruleToBePropagated = ruleInSubType; |
| } else { |
| // TODO(b/133208961): Introduce comparison/meet of assume rules. |
| if (!ruleToBePropagated.equals(ruleInSubType)) { |
| ruleToBePropagated = null; |
| break; |
| } |
| } |
| } |
| if (ruleToBePropagated != null) { |
| assumeRulePool.put(reference, ruleToBePropagated); |
| } |
| } |
| |
| ConsequentRootSet buildConsequentRootSet() { |
| return new ConsequentRootSet( |
| neverInline, |
| neverInlineDueToSingleCaller, |
| neverClassInline, |
| noShrinking, |
| softPinned, |
| noObfuscation, |
| dependentNoShrinking, |
| dependentSoftPinned, |
| dependentKeepClassCompatRule, |
| Lists.newArrayList(delayedRootSetActionItems)); |
| } |
| |
| private static DexDefinition testAndGetPrecondition( |
| DexDefinition definition, |
| Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier) { |
| if (preconditionSupplier == null) { |
| return null; |
| } |
| DexDefinition precondition = null; |
| boolean conditionEverMatched = false; |
| for (Entry<Predicate<DexDefinition>, DexDefinition> entry : preconditionSupplier.entrySet()) { |
| if (entry.getKey().test(definition)) { |
| precondition = entry.getValue(); |
| conditionEverMatched = true; |
| break; |
| } |
| } |
| // If precondition-supplier is given, there should be at least one predicate that holds. |
| // Actually, there should be only one predicate as we break the loop when it is found. |
| assert conditionEverMatched; |
| return precondition; |
| } |
| |
| private void markMatchingVisibleMethods( |
| DexClass clazz, |
| Collection<ProguardMemberRule> memberKeepRules, |
| ProguardConfigurationRule rule, |
| Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier, |
| boolean includeLibraryClasses, |
| ProguardIfRule ifRule) { |
| Set<Wrapper<DexMethod>> methodsMarked = |
| options.forceProguardCompatibility ? null : new HashSet<>(); |
| Stack<DexClass> worklist = new Stack<>(); |
| worklist.add(clazz); |
| while (!worklist.isEmpty()) { |
| DexClass currentClass = worklist.pop(); |
| if (!includeLibraryClasses && currentClass.isNotProgramClass()) { |
| break; |
| } |
| // In compat mode traverse all direct methods in the hierarchy. |
| if (currentClass == clazz || options.forceProguardCompatibility) { |
| currentClass |
| .directMethods() |
| .forEach( |
| method -> { |
| DexDefinition precondition = |
| testAndGetPrecondition(method, preconditionSupplier); |
| markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule); |
| }); |
| } |
| currentClass |
| .virtualMethods() |
| .forEach( |
| method -> { |
| DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier); |
| markMethod(method, memberKeepRules, methodsMarked, rule, precondition, ifRule); |
| }); |
| if (currentClass.superType != null) { |
| DexClass dexClass = application.definitionFor(currentClass.superType); |
| if (dexClass != null) { |
| worklist.add(dexClass); |
| } |
| } |
| } |
| // TODO(b/143643942): Generalize the below approach to also work for subtyping hierarchies in |
| // fullmode. |
| if (clazz.isProgramClass() |
| && rule.isProguardKeepRule() |
| && !rule.asProguardKeepRule().getModifiers().allowsShrinking) { |
| new SynthesizeMissingInterfaceMethodsForMemberRules( |
| clazz.asProgramClass(), memberKeepRules, rule, preconditionSupplier, ifRule) |
| .run(); |
| } |
| } |
| |
| /** |
| * Utility class for visiting all super interfaces to ensure we keep method definitions |
| * specified by proguard rules. If possible, we generate a forwarding bridge to the resolved |
| * target. If not, we specifically synthesize a keep rule for the interface method. |
| */ |
| private class SynthesizeMissingInterfaceMethodsForMemberRules { |
| |
| private final DexProgramClass originalClazz; |
| private final Collection<ProguardMemberRule> memberKeepRules; |
| private final ProguardConfigurationRule context; |
| private final Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier; |
| private final ProguardIfRule ifRule; |
| private final Set<Wrapper<DexMethod>> seenMethods = Sets.newHashSet(); |
| private final Set<DexType> seenTypes = Sets.newIdentityHashSet(); |
| |
| private SynthesizeMissingInterfaceMethodsForMemberRules( |
| DexProgramClass originalClazz, |
| Collection<ProguardMemberRule> memberKeepRules, |
| ProguardConfigurationRule context, |
| Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier, |
| ProguardIfRule ifRule) { |
| assert context.isProguardKeepRule(); |
| assert !context.asProguardKeepRule().getModifiers().allowsShrinking; |
| this.originalClazz = originalClazz; |
| this.memberKeepRules = memberKeepRules; |
| this.context = context; |
| this.preconditionSupplier = preconditionSupplier; |
| this.ifRule = ifRule; |
| } |
| |
| void handleMatchedAnnotation(AnnotationMatchResult annotationMatchResult) { |
| // Intentionally empty. |
| } |
| |
| void run() { |
| visitAllSuperInterfaces(originalClazz.type); |
| } |
| |
| private void visitAllSuperInterfaces(DexType type) { |
| DexClass clazz = appView.definitionFor(type); |
| if (clazz == null || clazz.isNotProgramClass() || !seenTypes.add(type)) { |
| return; |
| } |
| for (DexType iface : clazz.interfaces.values) { |
| visitAllSuperInterfaces(iface); |
| } |
| if (!clazz.isInterface()) { |
| visitAllSuperInterfaces(clazz.superType); |
| return; |
| } |
| if (originalClazz == clazz) { |
| return; |
| } |
| for (DexEncodedMethod method : clazz.virtualMethods()) { |
| // Check if we already added this. |
| Wrapper<DexMethod> wrapped = MethodSignatureEquivalence.get().wrap(method.getReference()); |
| if (!seenMethods.add(wrapped)) { |
| continue; |
| } |
| for (ProguardMemberRule rule : memberKeepRules) { |
| if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) { |
| tryAndKeepMethodOnClass(method, rule); |
| } |
| } |
| } |
| } |
| |
| private void tryAndKeepMethodOnClass(DexEncodedMethod method, ProguardMemberRule rule) { |
| SingleResolutionResult resolutionResult = |
| appView |
| .appInfo() |
| .resolveMethodOn(originalClazz, method.getReference()) |
| .asSingleResolution(); |
| if (resolutionResult == null || !resolutionResult.isVirtualTarget()) { |
| return; |
| } |
| if (resolutionResult.getResolvedHolder() == originalClazz |
| || resolutionResult.getResolvedHolder().isNotProgramClass()) { |
| return; |
| } |
| if (!resolutionResult.getResolvedHolder().isInterface()) { |
| // TODO(b/143643942): For fullmode, this check should probably be removed. |
| return; |
| } |
| ProgramMethod resolutionMethod = |
| new ProgramMethod( |
| resolutionResult.getResolvedHolder().asProgramClass(), |
| resolutionResult.getResolvedMethod()); |
| ProgramMethod methodToKeep = |
| canInsertForwardingMethod(originalClazz, resolutionMethod.getDefinition()) |
| ? new ProgramMethod( |
| originalClazz, |
| resolutionMethod.getDefinition().toForwardingMethod(originalClazz, appView)) |
| : resolutionMethod; |
| |
| delayedRootSetActionItems.add( |
| new InterfaceMethodSyntheticBridgeAction( |
| methodToKeep, |
| resolutionMethod, |
| (rootSetBuilder) -> { |
| if (Log.ENABLED) { |
| Log.verbose( |
| getClass(), |
| "Marking method `%s` due to `%s { %s }`.", |
| methodToKeep, |
| context, |
| rule); |
| } |
| DexDefinition precondition = |
| testAndGetPrecondition(methodToKeep.getDefinition(), preconditionSupplier); |
| rootSetBuilder.addItemToSets( |
| methodToKeep.getDefinition(), context, rule, precondition, ifRule); |
| })); |
| } |
| } |
| |
| private boolean canInsertForwardingMethod(DexClass holder, DexEncodedMethod target) { |
| return appView.options().isGeneratingDex() |
| || ArrayUtils.contains(holder.interfaces.values, target.getHolderType()); |
| } |
| |
| private void markMatchingOverriddenMethods( |
| AppInfoWithClassHierarchy appInfoWithSubtyping, |
| DexClass clazz, |
| Collection<ProguardMemberRule> memberKeepRules, |
| ProguardConfigurationRule rule, |
| Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier, |
| boolean onlyIncludeProgramClasses, |
| ProguardIfRule ifRule) { |
| Set<DexType> visited = new HashSet<>(); |
| Deque<DexType> worklist = new ArrayDeque<>(); |
| // Intentionally skip the current `clazz`, assuming it's covered by |
| // markMatchingVisibleMethods. |
| worklist.addAll(subtypingInfo.allImmediateSubtypes(clazz.type)); |
| |
| while (!worklist.isEmpty()) { |
| DexType currentType = worklist.poll(); |
| if (!visited.add(currentType)) { |
| continue; |
| } |
| DexClass currentClazz = appView.definitionFor(currentType); |
| if (currentClazz == null) { |
| continue; |
| } |
| if (!onlyIncludeProgramClasses && currentClazz.isNotProgramClass()) { |
| continue; |
| } |
| currentClazz |
| .virtualMethods() |
| .forEach( |
| method -> { |
| DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier); |
| markMethod(method, memberKeepRules, null, rule, precondition, ifRule); |
| }); |
| worklist.addAll(subtypingInfo.allImmediateSubtypes(currentClazz.type)); |
| } |
| } |
| |
| private void markMatchingMethods( |
| DexClass clazz, |
| Collection<ProguardMemberRule> memberKeepRules, |
| ProguardConfigurationRule rule, |
| Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier, |
| ProguardIfRule ifRule) { |
| clazz.forEachMethod( |
| method -> { |
| DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier); |
| markMethod(method, memberKeepRules, null, rule, precondition, ifRule); |
| }); |
| } |
| |
| private void markMatchingVisibleFields( |
| DexClass clazz, |
| Collection<ProguardMemberRule> memberKeepRules, |
| ProguardConfigurationRule rule, |
| Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier, |
| boolean includeLibraryClasses, |
| ProguardIfRule ifRule) { |
| while (clazz != null) { |
| if (!includeLibraryClasses && clazz.isNotProgramClass()) { |
| return; |
| } |
| clazz.forEachField( |
| field -> { |
| DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier); |
| markField(field, memberKeepRules, rule, precondition, ifRule); |
| }); |
| clazz = clazz.superType == null ? null : application.definitionFor(clazz.superType); |
| } |
| } |
| |
| private void markMatchingFields( |
| DexClass clazz, |
| Collection<ProguardMemberRule> memberKeepRules, |
| ProguardConfigurationRule rule, |
| Map<Predicate<DexDefinition>, DexDefinition> preconditionSupplier, |
| ProguardIfRule ifRule) { |
| clazz.forEachField( |
| field -> { |
| DexDefinition precondition = testAndGetPrecondition(field, preconditionSupplier); |
| markField(field, memberKeepRules, rule, precondition, ifRule); |
| }); |
| } |
| |
| // TODO(b/67934426): Test this code. |
| public static void writeSeeds( |
| AppInfoWithLiveness appInfo, PrintStream out, Predicate<DexType> include) { |
| appInfo |
| .getKeepInfo() |
| .forEachPinnedType( |
| type -> { |
| if (include.test(type)) { |
| out.println(type.toSourceString()); |
| } |
| }); |
| appInfo |
| .getKeepInfo() |
| .forEachPinnedField( |
| field -> { |
| if (include.test(field.holder)) { |
| out.println( |
| field.holder.toSourceString() |
| + ": " |
| + field.type.toSourceString() |
| + " " |
| + field.name.toSourceString()); |
| } |
| }); |
| appInfo |
| .getKeepInfo() |
| .forEachPinnedMethod( |
| method -> { |
| if (!include.test(method.holder)) { |
| return; |
| } |
| DexProgramClass holder = asProgramClassOrNull(appInfo.definitionForHolder(method)); |
| DexEncodedMethod definition = method.lookupOnClass(holder); |
| if (definition == null) { |
| assert method.match(appInfo.dexItemFactory().deserializeLambdaMethod); |
| return; |
| } |
| out.print(method.holder.toSourceString() + ": "); |
| if (definition.isClassInitializer()) { |
| out.print(Constants.CLASS_INITIALIZER_NAME); |
| } else if (definition.isInstanceInitializer()) { |
| String holderName = method.holder.toSourceString(); |
| String constrName = holderName.substring(holderName.lastIndexOf('.') + 1); |
| out.print(constrName); |
| } else { |
| out.print( |
| method.proto.returnType.toSourceString() |
| + " " |
| + method.name.toSourceString()); |
| } |
| boolean first = true; |
| out.print("("); |
| for (DexType param : method.proto.parameters.values) { |
| if (!first) { |
| out.print(","); |
| } |
| first = false; |
| out.print(param.toSourceString()); |
| } |
| out.println(")"); |
| }); |
| out.close(); |
| } |
| |
| static boolean satisfyClassType(ProguardConfigurationRule rule, DexClass clazz) { |
| return rule.getClassType().matches(clazz) != rule.getClassTypeNegated(); |
| } |
| |
| static boolean satisfyAccessFlag(ProguardConfigurationRule rule, DexClass clazz) { |
| return rule.getClassAccessFlags().containsAll(clazz.accessFlags) |
| && rule.getNegatedClassAccessFlags().containsNone(clazz.accessFlags); |
| } |
| |
| static AnnotationMatchResult satisfyAnnotation(ProguardConfigurationRule rule, DexClass clazz) { |
| return containsAllAnnotations(rule.getClassAnnotations(), clazz); |
| } |
| |
| boolean satisfyInheritanceRule(DexClass clazz, ProguardConfigurationRule rule) { |
| if (satisfyExtendsRule(clazz, rule)) { |
| return true; |
| } |
| |
| return satisfyImplementsRule(clazz, rule); |
| } |
| |
| boolean satisfyExtendsRule(DexClass clazz, ProguardConfigurationRule rule) { |
| if (anySuperTypeMatchesExtendsRule(clazz.superType, rule)) { |
| return true; |
| } |
| // It is possible that this class used to inherit from another class X, but no longer does it, |
| // because X has been merged into `clazz`. |
| return anySourceMatchesInheritanceRuleDirectly(clazz, rule, false); |
| } |
| |
| boolean anySuperTypeMatchesExtendsRule(DexType type, ProguardConfigurationRule rule) { |
| while (type != null) { |
| DexClass clazz = application.definitionFor(type); |
| if (clazz == null) { |
| // TODO(herhut): Warn about broken supertype chain? |
| return false; |
| } |
| // TODO(b/110141157): Should the vertical class merger move annotations from the source to |
| // the target class? If so, it is sufficient only to apply the annotation-matcher to the |
| // annotations of `class`. |
| if (rule.getInheritanceClassName().matches(clazz.type, appView)) { |
| AnnotationMatchResult annotationMatchResult = |
| containsAllAnnotations(rule.getInheritanceAnnotations(), clazz); |
| if (annotationMatchResult != null) { |
| handleMatchedAnnotation(annotationMatchResult); |
| return true; |
| } |
| } |
| type = clazz.superType; |
| } |
| return false; |
| } |
| |
| boolean satisfyImplementsRule(DexClass clazz, ProguardConfigurationRule rule) { |
| if (anyImplementedInterfaceMatchesImplementsRule(clazz, rule)) { |
| return true; |
| } |
| // It is possible that this class used to implement an interface I, but no longer does it, |
| // because I has been merged into `clazz`. |
| return anySourceMatchesInheritanceRuleDirectly(clazz, rule, true); |
| } |
| |
| private boolean anyImplementedInterfaceMatchesImplementsRule( |
| DexClass clazz, ProguardConfigurationRule rule) { |
| // TODO(herhut): Maybe it would be better to do this breadth first. |
| if (clazz == null) { |
| return false; |
| } |
| for (DexType iface : clazz.interfaces.values) { |
| DexClass ifaceClass = application.definitionFor(iface); |
| if (ifaceClass == null) { |
| // TODO(herhut): Warn about broken supertype chain? |
| return false; |
| } |
| // TODO(b/110141157): Should the vertical class merger move annotations from the source to |
| // the target class? If so, it is sufficient only to apply the annotation-matcher to the |
| // annotations of `ifaceClass`. |
| if (rule.getInheritanceClassName().matches(iface, appView)) { |
| AnnotationMatchResult annotationMatchResult = |
| containsAllAnnotations(rule.getInheritanceAnnotations(), ifaceClass); |
| if (annotationMatchResult != null) { |
| handleMatchedAnnotation(annotationMatchResult); |
| return true; |
| } |
| } |
| if (anyImplementedInterfaceMatchesImplementsRule(ifaceClass, rule)) { |
| return true; |
| } |
| } |
| if (clazz.superType == null) { |
| return false; |
| } |
| DexClass superClass = application.definitionFor(clazz.superType); |
| if (superClass == null) { |
| // TODO(herhut): Warn about broken supertype chain? |
| return false; |
| } |
| return anyImplementedInterfaceMatchesImplementsRule(superClass, rule); |
| } |
| |
| private boolean anySourceMatchesInheritanceRuleDirectly( |
| DexClass clazz, ProguardConfigurationRule rule, boolean isInterface) { |
| // TODO(b/110141157): Figure out what to do with annotations. Should the annotations of |
| // the DexClass corresponding to `sourceType` satisfy the `annotation`-matcher? |
| return appView.verticallyMergedClasses() != null |
| && appView.verticallyMergedClasses().getSourcesFor(clazz.type).stream() |
| .filter( |
| sourceType -> |
| appView.definitionFor(sourceType).accessFlags.isInterface() == isInterface) |
| .anyMatch(rule.getInheritanceClassName()::matches); |
| } |
| |
| private boolean allRulesSatisfied( |
| Collection<ProguardMemberRule> memberKeepRules, DexClass clazz) { |
| for (ProguardMemberRule rule : memberKeepRules) { |
| if (!ruleSatisfied(rule, clazz)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Checks whether the given rule is satisfied by this clazz, not taking superclasses into |
| * account. |
| */ |
| private boolean ruleSatisfied(ProguardMemberRule rule, DexClass clazz) { |
| return ruleSatisfiedByMethods(rule, clazz.directMethods()) |
| || ruleSatisfiedByMethods(rule, clazz.virtualMethods()) |
| || ruleSatisfiedByFields(rule, clazz.staticFields()) |
| || ruleSatisfiedByFields(rule, clazz.instanceFields()); |
| } |
| |
| boolean ruleSatisfiedByMethods(ProguardMemberRule rule, Iterable<DexEncodedMethod> methods) { |
| if (rule.getRuleType().includesMethods()) { |
| for (DexEncodedMethod method : methods) { |
| if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| boolean ruleSatisfiedByFields(ProguardMemberRule rule, Iterable<DexEncodedField> fields) { |
| if (rule.getRuleType().includesFields()) { |
| for (DexEncodedField field : fields) { |
| if (rule.matches(field, appView, this::handleMatchedAnnotation, dexStringCache)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| static AnnotationMatchResult containsAllAnnotations( |
| List<ProguardTypeMatcher> annotationMatchers, DexClass clazz) { |
| return containsAllAnnotations(annotationMatchers, clazz.annotations()); |
| } |
| |
| static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> |
| boolean containsAllAnnotations( |
| List<ProguardTypeMatcher> annotationMatchers, |
| DexEncodedMember<D, R> member, |
| Consumer<AnnotationMatchResult> matchedAnnotationsConsumer) { |
| AnnotationMatchResult annotationMatchResult = |
| containsAllAnnotations(annotationMatchers, member.annotations()); |
| if (annotationMatchResult != null) { |
| matchedAnnotationsConsumer.accept(annotationMatchResult); |
| return true; |
| } |
| if (member.isDexEncodedMethod()) { |
| DexEncodedMethod method = member.asDexEncodedMethod(); |
| for (int i = 0; i < method.parameterAnnotationsList.size(); i++) { |
| annotationMatchResult = |
| containsAllAnnotations(annotationMatchers, method.parameterAnnotationsList.get(i)); |
| if (annotationMatchResult != null) { |
| matchedAnnotationsConsumer.accept(annotationMatchResult); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private static AnnotationMatchResult containsAllAnnotations( |
| List<ProguardTypeMatcher> annotationMatchers, DexAnnotationSet annotations) { |
| if (annotationMatchers.isEmpty()) { |
| return AnnotationsIgnoredMatchResult.getInstance(); |
| } |
| List<DexAnnotation> matchedAnnotations = new ArrayList<>(); |
| for (ProguardTypeMatcher annotationMatcher : annotationMatchers) { |
| DexAnnotation matchedAnnotation = |
| getFirstAnnotationThatMatches(annotationMatcher, annotations); |
| if (matchedAnnotation == null) { |
| return null; |
| } |
| matchedAnnotations.add(matchedAnnotation); |
| } |
| return new ConcreteAnnotationMatchResult(matchedAnnotations); |
| } |
| |
| private static DexAnnotation getFirstAnnotationThatMatches( |
| ProguardTypeMatcher annotationMatcher, DexAnnotationSet annotations) { |
| for (DexAnnotation annotation : annotations.annotations) { |
| if (annotationMatcher.matches(annotation.getAnnotationType())) { |
| return annotation; |
| } |
| } |
| return null; |
| } |
| |
| private void markMethod( |
| DexEncodedMethod method, |
| Collection<ProguardMemberRule> rules, |
| Set<Wrapper<DexMethod>> methodsMarked, |
| ProguardConfigurationRule context, |
| DexDefinition precondition, |
| ProguardIfRule ifRule) { |
| if (methodsMarked != null |
| && methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.getReference()))) { |
| // Ignore, method is overridden in sub class. |
| return; |
| } |
| for (ProguardMemberRule rule : rules) { |
| if (rule.matches(method, appView, this::handleMatchedAnnotation, dexStringCache)) { |
| if (Log.ENABLED) { |
| Log.verbose( |
| getClass(), "Marking method `%s` due to `%s { %s }`.", method, context, rule); |
| } |
| if (methodsMarked != null) { |
| methodsMarked.add(MethodSignatureEquivalence.get().wrap(method.getReference())); |
| } |
| addItemToSets(method, context, rule, precondition, ifRule); |
| } |
| } |
| } |
| |
| private void markField( |
| DexEncodedField field, |
| Collection<ProguardMemberRule> rules, |
| ProguardConfigurationRule context, |
| DexDefinition precondition, |
| ProguardIfRule ifRule) { |
| for (ProguardMemberRule rule : rules) { |
| if (rule.matches(field, appView, this::handleMatchedAnnotation, dexStringCache)) { |
| if (Log.ENABLED) { |
| Log.verbose(getClass(), "Marking field `%s` due to `%s { %s }`.", field, context, rule); |
| } |
| addItemToSets(field, context, rule, precondition, ifRule); |
| } |
| } |
| } |
| |
| private void markClass(DexClass clazz, ProguardConfigurationRule rule, ProguardIfRule ifRule) { |
| if (Log.ENABLED) { |
| Log.verbose(getClass(), "Marking class `%s` due to `%s`.", clazz.type, rule); |
| } |
| addItemToSets(clazz, rule, null, null, ifRule); |
| } |
| |
| private void includeDescriptor(DexDefinition item, DexType type, ProguardKeepRuleBase context) { |
| if (type.isVoidType()) { |
| return; |
| } |
| if (type.isArrayType()) { |
| type = type.toBaseType(appView.dexItemFactory()); |
| } |
| if (type.isPrimitiveType()) { |
| return; |
| } |
| DexClass definition = appView.definitionFor(type); |
| if (definition == null || definition.isNotProgramClass()) { |
| return; |
| } |
| // Keep the type if the item is also kept. |
| dependentNoShrinking |
| .computeIfAbsent(item.getReference(), x -> new MutableItemsWithRules()) |
| .addClassWithRule(type, context); |
| // Unconditionally add to no-obfuscation, as that is only checked for surviving items. |
| if (appView.options().isMinificationEnabled()) { |
| noObfuscation.add(type); |
| } |
| } |
| |
| private void includeDescriptorClasses(DexDefinition item, ProguardKeepRuleBase context) { |
| if (item.isDexEncodedMethod()) { |
| DexMethod method = item.asDexEncodedMethod().getReference(); |
| includeDescriptor(item, method.proto.returnType, context); |
| for (DexType value : method.proto.parameters.values) { |
| includeDescriptor(item, value, context); |
| } |
| } else if (item.isDexEncodedField()) { |
| DexField field = item.asDexEncodedField().getReference(); |
| includeDescriptor(item, field.type, context); |
| } else { |
| assert item.isDexClass(); |
| } |
| } |
| |
| private synchronized void addItemToSets( |
| DexDefinition item, |
| ProguardConfigurationRule context, |
| ProguardMemberRule rule, |
| DexDefinition precondition, |
| ProguardIfRule ifRule) { |
| if (context instanceof ProguardKeepRule) { |
| if (item.isDexEncodedField()) { |
| DexEncodedField encodedField = item.asDexEncodedField(); |
| if (encodedField.getOptimizationInfo().cannotBeKept()) { |
| // We should only ever get here with if rules. |
| assert ifRule != null; |
| return; |
| } |
| } else if (item.isDexEncodedMethod()) { |
| DexEncodedMethod encodedMethod = item.asDexEncodedMethod(); |
| if (encodedMethod.isClassInitializer() && !options.debug) { |
| // Don't keep class initializers. |
| return; |
| } |
| if (encodedMethod.getOptimizationInfo().cannotBeKept()) { |
| // We should only ever get here with if rules. |
| assert ifRule != null; |
| return; |
| } |
| if (options.isGeneratingDex() |
| && encodedMethod.getReference().isLambdaDeserializeMethod(appView.dexItemFactory())) { |
| // Don't keep lambda deserialization methods. |
| return; |
| } |
| // If desugaring is enabled, private and static interface methods will be moved to a |
| // companion class. So we don't need to add them to the root set in the beginning. |
| if (options.isInterfaceMethodDesugaringEnabled() |
| && encodedMethod.hasCode() |
| && (encodedMethod.isPrivateMethod() || encodedMethod.isStaticMember())) { |
| DexClass holder = appView.definitionFor(encodedMethod.getHolderType()); |
| if (holder != null && holder.isInterface()) { |
| if (rule.isSpecific()) { |
| options.reporter.warning( |
| new StringDiagnostic( |
| "The rule `" |
| + rule |
| + "` is ignored because the targeting interface method `" |
| + encodedMethod.getReference().toSourceString() |
| + "` will be desugared.")); |
| } |
| return; |
| } |
| } |
| } |
| |
| // The reason for keeping should link to the conditional rule as a whole, if present. |
| ProguardKeepRuleBase keepRule = ifRule != null ? ifRule : (ProguardKeepRuleBase) context; |
| // The modifiers are specified on the actual keep rule (ie, the consequent/context). |
| ProguardKeepRuleModifiers modifiers = ((ProguardKeepRule) context).getModifiers(); |
| // In compatibility mode, for a match on instance members a referenced class becomes live. |
| if (options.forceProguardCompatibility |
| && !modifiers.allowsShrinking |
| && precondition != null |
| && precondition.isDexClass()) { |
| if (!item.isDexClass() && !item.isStaticMember()) { |
| dependentKeepClassCompatRule |
| .computeIfAbsent(precondition.asDexClass().getType(), i -> new HashSet<>()) |
| .add(keepRule); |
| context.markAsUsed(); |
| } |
| } |
| if (!modifiers.allowsShrinking) { |
| if (precondition != null) { |
| dependentNoShrinking |
| .computeIfAbsent(precondition.getReference(), x -> new MutableItemsWithRules()) |
| .addReferenceWithRule(item.getReference(), keepRule); |
| } else { |
| noShrinking.addReferenceWithRule(item.getReference(), keepRule); |
| } |
| context.markAsUsed(); |
| } else if (!modifiers.allowsOptimization) { |
| if (precondition != null) { |
| dependentSoftPinned |
| .computeIfAbsent(precondition.getReference(), x -> new MutableItemsWithRules()) |
| .addReferenceWithRule(item.getReference(), keepRule); |
| } else { |
| softPinned.addReferenceWithRule(item.getReference(), keepRule); |
| } |
| } |
| if (!modifiers.allowsOptimization) { |
| // The -dontoptimize flag has only effect through the keep all rule, but we still |
| // need to mark the rule as used. |
| context.markAsUsed(); |
| } |
| |
| if (appView.options().isMinificationEnabled() && !modifiers.allowsObfuscation) { |
| noObfuscation.add(item.getReference()); |
| context.markAsUsed(); |
| } |
| if (modifiers.includeDescriptorClasses) { |
| includeDescriptorClasses(item, keepRule); |
| context.markAsUsed(); |
| } |
| } else if (context instanceof ProguardAssumeMayHaveSideEffectsRule) { |
| mayHaveSideEffects.put(item.getReference(), rule); |
| context.markAsUsed(); |
| } else if (context instanceof ProguardAssumeNoSideEffectRule) { |
| if (item.isDexEncodedMember()) { |
| DexEncodedMember<?, ?> member = item.asDexEncodedMember(); |
| if (member.getHolderType() == appView.dexItemFactory().objectType) { |
| assert member.isDexEncodedMethod(); |
| reportAssumeNoSideEffectsWarningForJavaLangClassMethod( |
| member.asDexEncodedMethod(), (ProguardAssumeNoSideEffectRule) context); |
| } else { |
| noSideEffects.put(member.getReference(), rule); |
| if (member.isDexEncodedMethod()) { |
| DexEncodedMethod method = member.asDexEncodedMethod(); |
| if (method.isClassInitializer()) { |
| feedback.classInitializerMayBePostponed(method); |
| } |
| } |
| } |
| context.markAsUsed(); |
| } |
| } else if (context instanceof ProguardWhyAreYouKeepingRule) { |
| reasonAsked.computeIfAbsent(item.getReference(), i -> i); |
| context.markAsUsed(); |
| } else if (context instanceof ProguardAssumeValuesRule) { |
| if (item.isDexEncodedMember()) { |
| assumedValues.put(item.asDexEncodedMember().getReference(), rule); |
| context.markAsUsed(); |
| } |
| } else if (context instanceof ProguardCheckDiscardRule) { |
| checkDiscarded.computeIfAbsent(item.getReference(), i -> i); |
| context.markAsUsed(); |
| } else if (context instanceof InlineRule) { |
| if (item.isDexEncodedMethod()) { |
| DexMethod reference = item.asDexEncodedMethod().getReference(); |
| switch (((InlineRule) context).getType()) { |
| case ALWAYS: |
| alwaysInline.add(reference); |
| break; |
| case FORCE: |
| forceInline.add(reference); |
| break; |
| case NEVER: |
| neverInline.add(reference); |
| break; |
| case NEVER_SINGLE_CALLER: |
| neverInlineDueToSingleCaller.add(reference); |
| break; |
| default: |
| throw new Unreachable(); |
| } |
| context.markAsUsed(); |
| } |
| } else if (context instanceof WhyAreYouNotInliningRule) { |
| if (!item.isDexEncodedMethod()) { |
| throw new Unreachable(); |
| } |
| whyAreYouNotInlining.add(item.asDexEncodedMethod().getReference()); |
| context.markAsUsed(); |
| } else if (context.isClassInlineRule()) { |
| ClassInlineRule classInlineRule = context.asClassInlineRule(); |
| DexClass clazz = item.asDexClass(); |
| if (clazz == null) { |
| throw new IllegalStateException( |
| "Unexpected -" |
| + classInlineRule.typeString() |
| + " rule for a non-class type: `" |
| + item.getReference().toSourceString() |
| + "`"); |
| } |
| switch (classInlineRule.getType()) { |
| case ALWAYS: |
| alwaysClassInline.addElement(item.asDexClass().type); |
| break; |
| case NEVER: |
| neverClassInline.add(item.asDexClass().type); |
| break; |
| default: |
| throw new Unreachable(); |
| } |
| context.markAsUsed(); |
| } else if (context instanceof NoUnusedInterfaceRemovalRule) { |
| noUnusedInterfaceRemoval.add(item.asDexClass().type); |
| context.markAsUsed(); |
| } else if (context instanceof NoVerticalClassMergingRule) { |
| noVerticalClassMerging.add(item.asDexClass().type); |
| context.markAsUsed(); |
| } else if (context instanceof NoHorizontalClassMergingRule) { |
| noHorizontalClassMerging.add(item.asDexClass().type); |
| context.markAsUsed(); |
| } else if (context instanceof MemberValuePropagationRule) { |
| switch (((MemberValuePropagationRule) context).getType()) { |
| case NEVER: |
| // Only add members from propgram classes to `neverPropagateValue` since class member |
| // values from library types are not propagated by default. |
| if (item.isDexEncodedField()) { |
| DexEncodedField field = item.asDexEncodedField(); |
| if (field.isProgramField(appView)) { |
| neverPropagateValue.add(item.asDexEncodedField().getReference()); |
| context.markAsUsed(); |
| } |
| } else if (item.isDexEncodedMethod()) { |
| DexEncodedMethod method = item.asDexEncodedMethod(); |
| if (method.isProgramMethod(appView)) { |
| neverPropagateValue.add(item.asDexEncodedMethod().getReference()); |
| context.markAsUsed(); |
| } |
| } |
| break; |
| default: |
| throw new Unreachable(); |
| } |
| } else if (context instanceof ProguardIdentifierNameStringRule) { |
| if (item.isDexEncodedField()) { |
| identifierNameStrings.add(item.asDexEncodedField().getReference()); |
| context.markAsUsed(); |
| } else if (item.isDexEncodedMethod()) { |
| identifierNameStrings.add(item.asDexEncodedMethod().getReference()); |
| context.markAsUsed(); |
| } |
| } else if (context instanceof ConstantArgumentRule) { |
| if (item.isDexEncodedMethod()) { |
| keepParametersWithConstantValue.add(item.asDexEncodedMethod().getReference()); |
| context.markAsUsed(); |
| } |
| } else if (context instanceof ReprocessClassInitializerRule) { |
| DexProgramClass clazz = item.asProgramClass(); |
| if (clazz != null && clazz.hasClassInitializer()) { |
| switch (context.asReprocessClassInitializerRule().getType()) { |
| case ALWAYS: |
| reprocess.add(clazz.getClassInitializer().getReference()); |
| break; |
| case NEVER: |
| neverReprocess.add(clazz.getClassInitializer().getReference()); |
| break; |
| default: |
| throw new Unreachable(); |
| } |
| context.markAsUsed(); |
| } |
| } else if (context.isReprocessMethodRule()) { |
| if (item.isDexEncodedMethod()) { |
| DexEncodedMethod method = item.asDexEncodedMethod(); |
| switch (context.asReprocessMethodRule().getType()) { |
| case ALWAYS: |
| reprocess.add(method.getReference()); |
| break; |
| case NEVER: |
| neverReprocess.add(method.getReference()); |
| break; |
| default: |
| throw new Unreachable(); |
| } |
| context.markAsUsed(); |
| } |
| } else if (context instanceof UnusedArgumentRule) { |
| if (item.isDexEncodedMethod()) { |
| keepUnusedArguments.add(item.asDexEncodedMethod().getReference()); |
| context.markAsUsed(); |
| } |
| } else { |
| throw new Unreachable(); |
| } |
| } |
| |
| private void reportAssumeNoSideEffectsWarningForJavaLangClassMethod( |
| DexEncodedMethod method, ProguardAssumeNoSideEffectRule context) { |
| assert method.getHolderType() == options.dexItemFactory().objectType; |
| OriginWithPosition key = new OriginWithPosition(context.getOrigin(), context.getPosition()); |
| assumeNoSideEffectsWarnings |
| .computeIfAbsent(key, ignore -> new TreeSet<>(DexMethod::compareTo)) |
| .add(method.getReference()); |
| } |
| |
| private boolean isWaitOrNotifyMethod(DexMethod method) { |
| return method.name == options.itemFactory.waitMethodName |
| || method.name == options.itemFactory.notifyMethodName |
| || method.name == options.itemFactory.notifyAllMethodName; |
| } |
| |
| private void generateAssumeNoSideEffectsWarnings() { |
| if (appView.getDontWarnConfiguration().matches(options.itemFactory.objectType)) { |
| // Don't report any warnings since we don't apply -assumenosideeffects rules to notify() or |
| // wait() anyway. |
| return; |
| } |
| |
| assumeNoSideEffectsWarnings.forEach( |
| (originWithPosition, methods) -> { |
| boolean matchesWaitOrNotifyMethods = |
| methods.stream().anyMatch(this::isWaitOrNotifyMethod); |
| if (!matchesWaitOrNotifyMethods) { |
| // We model the remaining methods on java.lang.Object, and thus there should be no |
| // need |
| // to warn in this case. |
| return; |
| } |
| options.reporter.warning( |
| new AssumeNoSideEffectsRuleForObjectMembersDiagnostic.Builder() |
| .addMatchedMethods(methods) |
| .setOrigin(originWithPosition.getOrigin()) |
| .setPosition(originWithPosition.getPosition()) |
| .build()); |
| }); |
| } |
| } |
| |
| abstract static class RootSetBase { |
| |
| final Set<DexMethod> neverInline; |
| final Set<DexMethod> neverInlineDueToSingleCaller; |
| final Set<DexType> neverClassInline; |
| final MutableItemsWithRules noShrinking; |
| final MutableItemsWithRules softPinned; |
| final Set<DexReference> noObfuscation; |
| final Map<DexReference, MutableItemsWithRules> dependentNoShrinking; |
| final Map<DexReference, MutableItemsWithRules> dependentSoftPinned; |
| final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule; |
| final List<DelayedRootSetActionItem> delayedRootSetActionItems; |
| |
| RootSetBase( |
| Set<DexMethod> neverInline, |
| Set<DexMethod> neverInlineDueToSingleCaller, |
| Set<DexType> neverClassInline, |
| MutableItemsWithRules noShrinking, |
| MutableItemsWithRules softPinned, |
| Set<DexReference> noObfuscation, |
| Map<DexReference, MutableItemsWithRules> dependentNoShrinking, |
| Map<DexReference, MutableItemsWithRules> dependentSoftPinned, |
| Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule, |
| List<DelayedRootSetActionItem> delayedRootSetActionItems) { |
| this.neverInline = neverInline; |
| this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller; |
| this.neverClassInline = neverClassInline; |
| this.noShrinking = noShrinking; |
| this.softPinned = softPinned; |
| this.noObfuscation = noObfuscation; |
| this.dependentNoShrinking = dependentNoShrinking; |
| this.dependentSoftPinned = dependentSoftPinned; |
| this.dependentKeepClassCompatRule = dependentKeepClassCompatRule; |
| this.delayedRootSetActionItems = delayedRootSetActionItems; |
| } |
| |
| public void forEachClassWithDependentItems( |
| DexDefinitionSupplier definitions, Consumer<DexProgramClass> consumer) { |
| for (DexReference reference : dependentNoShrinking.keySet()) { |
| if (reference.isDexType()) { |
| DexType type = reference.asDexType(); |
| DexProgramClass clazz = asProgramClassOrNull(definitions.definitionFor(type)); |
| if (clazz != null) { |
| consumer.accept(clazz); |
| } |
| } |
| } |
| } |
| |
| public void forEachMemberWithDependentItems( |
| DexDefinitionSupplier definitions, |
| BiConsumer<DexEncodedMember<?, ?>, ItemsWithRules> consumer) { |
| dependentNoShrinking.forEach( |
| (reference, dependentItems) -> { |
| if (reference.isDexMember()) { |
| DexMember<?, ?> member = reference.asDexMember(); |
| DexProgramClass holder = |
| asProgramClassOrNull(definitions.definitionForHolder(member)); |
| if (holder != null) { |
| DexEncodedMember<?, ?> definition = holder.lookupMember(member); |
| if (definition != null) { |
| consumer.accept(definition, dependentItems); |
| } |
| } |
| } |
| }); |
| } |
| |
| public void forEachDependentInstanceConstructor( |
| DexProgramClass clazz, |
| AppView<?> appView, |
| BiConsumer<ProgramMethod, Set<ProguardKeepRuleBase>> fn) { |
| getDependentItems(clazz) |
| .forEachMethod( |
| (reference, reasons) -> { |
| DexProgramClass holder = |
| asProgramClassOrNull(appView.definitionForHolder(reference)); |
| if (holder != null) { |
| ProgramMethod method = holder.lookupProgramMethod(reference); |
| if (method != null && method.getDefinition().isInstanceInitializer()) { |
| fn.accept(method, reasons); |
| } |
| } |
| }); |
| } |
| |
| public void forEachDependentMember( |
| DexDefinition item, |
| AppView<?> appView, |
| Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) { |
| getDependentItems(item) |
| .forEachMember( |
| (reference, reasons) -> { |
| DexProgramClass holder = |
| asProgramClassOrNull(appView.definitionForHolder(reference)); |
| if (holder != null) { |
| ProgramMember<?, ?> member = holder.lookupProgramMember(reference); |
| if (member != null) { |
| fn.accept(item, member, reasons); |
| } |
| } |
| }); |
| } |
| |
| public void forEachDependentNonStaticMember( |
| DexDefinition item, |
| AppView<?> appView, |
| Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) { |
| forEachDependentMember( |
| item, |
| appView, |
| (precondition, member, reasons) -> { |
| if (!member.getDefinition().isStatic()) { |
| fn.accept(precondition, member, reasons); |
| } |
| }); |
| } |
| |
| public void forEachDependentStaticMember( |
| DexDefinition item, |
| AppView<?> appView, |
| Consumer3<DexDefinition, ProgramMember<?, ?>, Set<ProguardKeepRuleBase>> fn) { |
| forEachDependentMember( |
| item, |
| appView, |
| (precondition, member, reasons) -> { |
| if (member.getDefinition().isStatic()) { |
| fn.accept(precondition, member, reasons); |
| } |
| }); |
| } |
| |
| ItemsWithRules getDependentItems(DexDefinition item) { |
| ItemsWithRules found = dependentNoShrinking.get(item.getReference()); |
| return found != null ? found : ItemsWithRules.empty(); |
| } |
| |
| Set<ProguardKeepRuleBase> getDependentKeepClassCompatRule(DexType type) { |
| return dependentKeepClassCompatRule.get(type); |
| } |
| } |
| |
| abstract static class ItemsWithRules { |
| |
| public static ItemsWithRules empty() { |
| return MutableItemsWithRules.EMPTY; |
| } |
| |
| public abstract boolean containsClass(DexType type); |
| |
| public abstract boolean containsField(DexField field); |
| |
| public abstract boolean containsMethod(DexMethod method); |
| |
| public final boolean containsReference(DexReference reference) { |
| return reference.apply(this::containsClass, this::containsField, this::containsMethod); |
| } |
| |
| public abstract void forEachClass(Consumer<? super DexType> consumer); |
| |
| public abstract void forEachClass( |
| BiConsumer<? super DexType, Set<ProguardKeepRuleBase>> consumer); |
| |
| public abstract void forEachField(Consumer<? super DexField> consumer); |
| |
| public abstract void forEachField( |
| BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer); |
| |
| public abstract void forEachMember(Consumer<? super DexMember<?, ?>> consumer); |
| |
| public abstract void forEachMember( |
| BiConsumer<? super DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer); |
| |
| public abstract void forEachMethod(Consumer<? super DexMethod> consumer); |
| |
| public abstract void forEachMethod( |
| BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer); |
| |
| public abstract Set<ProguardKeepRuleBase> getRulesForClass(DexType type); |
| |
| public abstract Set<ProguardKeepRuleBase> getRulesForField(DexField field); |
| |
| public abstract Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method); |
| |
| public final Set<ProguardKeepRuleBase> getRulesForReference(DexReference reference) { |
| return reference.apply( |
| this::getRulesForClass, this::getRulesForField, this::getRulesForMethod); |
| } |
| } |
| |
| static class MutableItemsWithRules extends ItemsWithRules { |
| |
| private static final ItemsWithRules EMPTY = |
| new MutableItemsWithRules( |
| Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap()); |
| |
| final Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules; |
| final Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules; |
| final Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules; |
| |
| MutableItemsWithRules() { |
| this(new IdentityHashMap<>(), new IdentityHashMap<>(), new IdentityHashMap<>()); |
| } |
| |
| private MutableItemsWithRules( |
| Map<DexType, Set<ProguardKeepRuleBase>> classesWithRules, |
| Map<DexField, Set<ProguardKeepRuleBase>> fieldsWithRules, |
| Map<DexMethod, Set<ProguardKeepRuleBase>> methodsWithRules) { |
| this.classesWithRules = classesWithRules; |
| this.fieldsWithRules = fieldsWithRules; |
| this.methodsWithRules = methodsWithRules; |
| } |
| |
| public void addAll(ItemsWithRules items) { |
| items.forEachClass(this::addClassWithRules); |
| items.forEachField(this::addFieldWithRules); |
| items.forEachMethod(this::addMethodWithRules); |
| } |
| |
| public void addClassWithRule(DexType type, ProguardKeepRuleBase rule) { |
| classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).add(rule); |
| } |
| |
| public void addClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) { |
| classesWithRules.computeIfAbsent(type, ignore -> new HashSet<>()).addAll(rules); |
| } |
| |
| public void addFieldWithRule(DexField field, ProguardKeepRuleBase rule) { |
| fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).add(rule); |
| } |
| |
| public void addFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) { |
| fieldsWithRules.computeIfAbsent(field, ignore -> new HashSet<>()).addAll(rules); |
| } |
| |
| public void addMethodWithRule(DexMethod method, ProguardKeepRuleBase rule) { |
| methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).add(rule); |
| } |
| |
| public void addMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) { |
| methodsWithRules.computeIfAbsent(method, ignore -> new HashSet<>()).addAll(rules); |
| } |
| |
| public void addReferenceWithRule(DexReference reference, ProguardKeepRuleBase rule) { |
| reference.accept( |
| this::addClassWithRule, this::addFieldWithRule, this::addMethodWithRule, rule); |
| } |
| |
| public void addReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) { |
| reference.accept( |
| this::addClassWithRules, this::addFieldWithRules, this::addMethodWithRules, rules); |
| } |
| |
| @Override |
| public boolean containsClass(DexType type) { |
| return classesWithRules.containsKey(type); |
| } |
| |
| @Override |
| public boolean containsField(DexField field) { |
| return fieldsWithRules.containsKey(field); |
| } |
| |
| @Override |
| public boolean containsMethod(DexMethod method) { |
| return methodsWithRules.containsKey(method); |
| } |
| |
| public void forEachReference(Consumer<DexReference> consumer) { |
| forEachClass(consumer); |
| forEachMember(consumer); |
| } |
| |
| @Override |
| public void forEachClass(Consumer<? super DexType> consumer) { |
| classesWithRules.keySet().forEach(consumer); |
| } |
| |
| @Override |
| public void forEachClass(BiConsumer<? super DexType, Set<ProguardKeepRuleBase>> consumer) { |
| classesWithRules.forEach(consumer); |
| } |
| |
| @Override |
| public void forEachField(Consumer<? super DexField> consumer) { |
| fieldsWithRules.keySet().forEach(consumer); |
| } |
| |
| @Override |
| public void forEachField(BiConsumer<? super DexField, Set<ProguardKeepRuleBase>> consumer) { |
| fieldsWithRules.forEach(consumer); |
| } |
| |
| @Override |
| public void forEachMember(Consumer<? super DexMember<?, ?>> consumer) { |
| forEachField(consumer); |
| forEachMethod(consumer); |
| } |
| |
| @Override |
| public void forEachMember( |
| BiConsumer<? super DexMember<?, ?>, Set<ProguardKeepRuleBase>> consumer) { |
| forEachField(consumer); |
| forEachMethod(consumer); |
| } |
| |
| @Override |
| public void forEachMethod(Consumer<? super DexMethod> consumer) { |
| methodsWithRules.keySet().forEach(consumer); |
| } |
| |
| @Override |
| public void forEachMethod(BiConsumer<? super DexMethod, Set<ProguardKeepRuleBase>> consumer) { |
| methodsWithRules.forEach(consumer); |
| } |
| |
| @Override |
| public Set<ProguardKeepRuleBase> getRulesForClass(DexType type) { |
| return classesWithRules.get(type); |
| } |
| |
| @Override |
| public Set<ProguardKeepRuleBase> getRulesForField(DexField field) { |
| return fieldsWithRules.get(field); |
| } |
| |
| @Override |
| public Set<ProguardKeepRuleBase> getRulesForMethod(DexMethod method) { |
| return methodsWithRules.get(method); |
| } |
| |
| public void removeClass(DexType type) { |
| classesWithRules.remove(type); |
| } |
| |
| public void removeField(DexField field) { |
| fieldsWithRules.remove(field); |
| } |
| |
| public void removeMethod(DexMethod method) { |
| methodsWithRules.remove(method); |
| } |
| |
| public void removeReference(DexReference reference) { |
| reference.accept(this::removeClass, this::removeField, this::removeMethod); |
| } |
| |
| public void putAll(ItemsWithRules items) { |
| items.forEachClass(this::putClassWithRules); |
| items.forEachField(this::putFieldWithRules); |
| items.forEachMethod(this::putMethodWithRules); |
| } |
| |
| public void putClassWithRules(DexType type, Set<ProguardKeepRuleBase> rules) { |
| classesWithRules.put(type, rules); |
| } |
| |
| public void putFieldWithRules(DexField field, Set<ProguardKeepRuleBase> rules) { |
| fieldsWithRules.put(field, rules); |
| } |
| |
| public void putMethodWithRules(DexMethod method, Set<ProguardKeepRuleBase> rules) { |
| methodsWithRules.put(method, rules); |
| } |
| |
| public void putReferenceWithRules(DexReference reference, Set<ProguardKeepRuleBase> rules) { |
| reference.accept( |
| this::putClassWithRules, this::putFieldWithRules, this::putMethodWithRules, rules); |
| } |
| |
| public int size() { |
| return classesWithRules.size() + fieldsWithRules.size() + methodsWithRules.size(); |
| } |
| |
| public void forEachReference( |
| BiConsumer<? super DexReference, Set<ProguardKeepRuleBase>> consumer) { |
| forEachClass(consumer); |
| forEachMember(consumer); |
| } |
| |
| private MutableItemsWithRules prune(Set<DexType> prunedClasses) { |
| MutableItemsWithRules prunedItemsWithRules = new MutableItemsWithRules(); |
| forEachReference( |
| (reference, rules) -> { |
| if (!prunedClasses.contains(reference.getContextType())) { |
| prunedItemsWithRules.addReferenceWithRules(reference, rules); |
| } |
| }); |
| return prunedItemsWithRules; |
| } |
| |
| private MutableItemsWithRules rewrittenWithLens(GraphLens graphLens) { |
| if (graphLens.isIdentityLens()) { |
| return this; |
| } |
| MutableItemsWithRules rewrittenItemsWithRules = new MutableItemsWithRules(); |
| forEachReference( |
| (reference, rules) -> |
| rewriteAndApplyIfNotPrimitiveType( |
| graphLens, |
| reference, |
| rewritten -> rewrittenItemsWithRules.addReferenceWithRules(rewritten, rules))); |
| return rewrittenItemsWithRules; |
| } |
| } |
| |
| public static class RootSet extends RootSetBase { |
| |
| public final ImmutableList<DexReference> reasonAsked; |
| public final ImmutableList<DexReference> checkDiscarded; |
| public final Set<DexMethod> alwaysInline; |
| public final Set<DexMethod> forceInline; |
| public final Set<DexMethod> bypassClinitForInlining; |
| public final Set<DexMethod> whyAreYouNotInlining; |
| public final Set<DexMethod> keepConstantArguments; |
| public final Set<DexMethod> keepUnusedArguments; |
| public final Set<DexMethod> reprocess; |
| public final Set<DexMethod> neverReprocess; |
| public final PredicateSet<DexType> alwaysClassInline; |
| public final Set<DexType> noUnusedInterfaceRemoval; |
| public final Set<DexType> noVerticalClassMerging; |
| public final Set<DexType> noHorizontalClassMerging; |
| public final Set<DexReference> neverPropagateValue; |
| public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects; |
| public final Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects; |
| public final Map<DexMember<?, ?>, ProguardMemberRule> assumedValues; |
| public final Set<DexReference> identifierNameStrings; |
| public final Set<ProguardIfRule> ifRules; |
| |
| private RootSet( |
| MutableItemsWithRules noShrinking, |
| MutableItemsWithRules softPinned, |
| Set<DexReference> noObfuscation, |
| ImmutableList<DexReference> reasonAsked, |
| ImmutableList<DexReference> checkDiscarded, |
| Set<DexMethod> alwaysInline, |
| Set<DexMethod> forceInline, |
| Set<DexMethod> neverInline, |
| Set<DexMethod> neverInlineDueToSingleCaller, |
| Set<DexMethod> bypassClinitForInlining, |
| Set<DexMethod> whyAreYouNotInlining, |
| Set<DexMethod> keepConstantArguments, |
| Set<DexMethod> keepUnusedArguments, |
| Set<DexMethod> reprocess, |
| Set<DexMethod> neverReprocess, |
| PredicateSet<DexType> alwaysClassInline, |
| Set<DexType> neverClassInline, |
| Set<DexType> noUnusedInterfaceRemoval, |
| Set<DexType> noVerticalClassMerging, |
| Set<DexType> noHorizontalClassMerging, |
| Set<DexReference> neverPropagateValue, |
| Map<DexReference, ProguardMemberRule> mayHaveSideEffects, |
| Map<DexMember<?, ?>, ProguardMemberRule> noSideEffects, |
| Map<DexMember<?, ?>, ProguardMemberRule> assumedValues, |
| Map<DexReference, MutableItemsWithRules> dependentNoShrinking, |
| Map<DexReference, MutableItemsWithRules> dependentSoftPinned, |
| Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule, |
| Set<DexReference> identifierNameStrings, |
| Set<ProguardIfRule> ifRules, |
| List<DelayedRootSetActionItem> delayedRootSetActionItems) { |
| super( |
| neverInline, |
| neverInlineDueToSingleCaller, |
| neverClassInline, |
| noShrinking, |
| softPinned, |
| noObfuscation, |
| dependentNoShrinking, |
| dependentSoftPinned, |
| dependentKeepClassCompatRule, |
| delayedRootSetActionItems); |
| this.reasonAsked = reasonAsked; |
| this.checkDiscarded = checkDiscarded; |
| this.alwaysInline = alwaysInline; |
| this.forceInline = forceInline; |
| this.bypassClinitForInlining = bypassClinitForInlining; |
| this.whyAreYouNotInlining = whyAreYouNotInlining; |
| this.keepConstantArguments = keepConstantArguments; |
| this.keepUnusedArguments = keepUnusedArguments; |
| this.reprocess = reprocess; |
| this.neverReprocess = neverReprocess; |
| this.alwaysClassInline = alwaysClassInline; |
| this.noUnusedInterfaceRemoval = noUnusedInterfaceRemoval; |
| this.noVerticalClassMerging = noVerticalClassMerging; |
| this.noHorizontalClassMerging = noHorizontalClassMerging; |
| this.neverPropagateValue = neverPropagateValue; |
| this.mayHaveSideEffects = mayHaveSideEffects; |
| this.noSideEffects = noSideEffects; |
| this.assumedValues = assumedValues; |
| this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings); |
| this.ifRules = Collections.unmodifiableSet(ifRules); |
| } |
| |
| public void checkAllRulesAreUsed(InternalOptions options) { |
| List<ProguardConfigurationRule> rules = options.getProguardConfiguration().getRules(); |
| if (rules != null) { |
| for (ProguardConfigurationRule rule : rules) { |
| if (!rule.isUsed()) { |
| String message = |
| "Proguard configuration rule does not match anything: `" + rule.toString() + "`"; |
| StringDiagnostic diagnostic = new StringDiagnostic(message, rule.getOrigin()); |
| if (options.testing.reportUnusedProguardConfigurationRules) { |
| options.reporter.info(diagnostic); |
| } |
| } |
| } |
| } |
| } |
| |
| void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) { |
| neverInline.addAll(consequentRootSet.neverInline); |
| neverInlineDueToSingleCaller.addAll(consequentRootSet.neverInlineDueToSingleCaller); |
| neverClassInline.addAll(consequentRootSet.neverClassInline); |
| noObfuscation.addAll(consequentRootSet.noObfuscation); |
| if (addNoShrinking) { |
| noShrinking.addAll(consequentRootSet.noShrinking); |
| } |
| addDependentItems(consequentRootSet.dependentNoShrinking, dependentNoShrinking); |
| addDependentItems(consequentRootSet.dependentSoftPinned, dependentSoftPinned); |
| consequentRootSet.dependentKeepClassCompatRule.forEach( |
| (type, rules) -> |
| dependentKeepClassCompatRule |
| .computeIfAbsent(type, k -> new HashSet<>()) |
| .addAll(rules)); |
| delayedRootSetActionItems.addAll(consequentRootSet.delayedRootSetActionItems); |
| } |
| |
| // Add dependent items that depend on -if rules. |
| static void addDependentItems( |
| Map<DexReference, ? extends ItemsWithRules> dependentItemsToAdd, |
| Map<DexReference, MutableItemsWithRules> dependentItemsToAddTo) { |
| dependentItemsToAdd.forEach( |
| (reference, dependence) -> |
| dependentItemsToAddTo |
| .computeIfAbsent(reference, x -> new MutableItemsWithRules()) |
| .putAll(dependence)); |
| } |
| |
| public void copy(DexReference original, DexReference rewritten) { |
| if (noShrinking.containsReference(original)) { |
| noShrinking.putReferenceWithRules(rewritten, noShrinking.getRulesForReference(original)); |
| } |
| if (noObfuscation.contains(original)) { |
| noObfuscation.add(rewritten); |
| } |
| if (original.isDexMember()) { |
| assert rewritten.isDexMember(); |
| DexMember<?, ?> originalMember = original.asDexMember(); |
| if (noSideEffects.containsKey(originalMember)) { |
| noSideEffects.put(rewritten.asDexMember(), noSideEffects.get(originalMember)); |
| } |
| if (assumedValues.containsKey(originalMember)) { |
| assumedValues.put(rewritten.asDexMember(), assumedValues.get(originalMember)); |
| } |
| } |
| } |
| |
| public void prune(DexReference reference) { |
| noShrinking.removeReference(reference); |
| noObfuscation.remove(reference); |
| noSideEffects.remove(reference); |
| assumedValues.remove(reference); |
| } |
| |
| public void pruneDeadItems(DexDefinitionSupplier definitions, Enqueuer enqueuer) { |
| pruneDeadReferences(noUnusedInterfaceRemoval, definitions, enqueuer); |
| pruneDeadReferences(noVerticalClassMerging, definitions, enqueuer); |
| pruneDeadReferences(noHorizontalClassMerging, definitions, enqueuer); |
| pruneDeadReferences(alwaysInline, definitions, enqueuer); |
| pruneDeadReferences(noSideEffects.keySet(), definitions, enqueuer); |
| } |
| |
| private static void pruneDeadReferences( |
| Set<? extends DexReference> references, |
| DexDefinitionSupplier definitions, |
| Enqueuer enqueuer) { |
| references.removeIf( |
| reference -> { |
| if (reference.isDexType()) { |
| DexClass definition = definitions.definitionFor(reference.asDexType()); |
| return definition == null || !enqueuer.isTypeLive(definition); |
| } |
| |
| assert reference.isDexMember(); |
| |
| DexMember<?, ?> member = reference.asDexMember(); |
| DexClass holder = definitions.definitionForHolder(member); |
| DexEncodedMember<?, ?> definition = member.lookupOnClass(holder); |
| if (definition == null) { |
| return true; |
| } |
| if (holder.isProgramClass()) { |
| if (definition.isDexEncodedField()) { |
| DexEncodedField field = definition.asDexEncodedField(); |
| return !enqueuer.isFieldReferenced(field); |
| } |
| assert definition.isDexEncodedMethod(); |
| DexEncodedMethod method = definition.asDexEncodedMethod(); |
| return !enqueuer.isMethodLive(method) && !enqueuer.isMethodTargeted(method); |
| } |
| return !enqueuer.isNonProgramTypeLive(holder); |
| }); |
| } |
| |
| public void move(DexReference original, DexReference rewritten) { |
| copy(original, rewritten); |
| prune(original); |
| } |
| |
| void shouldNotBeMinified(DexReference reference) { |
| noObfuscation.add(reference); |
| } |
| |
| public boolean verifyKeptFieldsAreAccessedAndLive(AppInfoWithLiveness appInfo) { |
| noShrinking.forEachField( |
| reference -> { |
| DexClass holder = appInfo.definitionForHolder(reference); |
| DexEncodedField field = reference.lookupOnClass(holder); |
| if (field != null |
| && (field.isStatic() |
| || isKeptDirectlyOrIndirectly(field.getHolderType(), appInfo))) { |
| assert appInfo.isFieldRead(field) |
| : "Expected kept field `" + field.toSourceString() + "` to be read"; |
| assert appInfo.isFieldWritten(field) |
| : "Expected kept field `" + field.toSourceString() + "` to be written"; |
| } |
| }); |
| return true; |
| } |
| |
| public boolean verifyKeptMethodsAreTargetedAndLive(AppInfoWithLiveness appInfo) { |
| noShrinking.forEachMethod( |
| reference -> { |
| assert appInfo.isTargetedMethod(reference) |
| : "Expected kept method `" + reference.toSourceString() + "` to be targeted"; |
| DexEncodedMethod method = |
| appInfo.definitionForHolder(reference).lookupMethod(reference); |
| if (!method.isAbstract() |
| && isKeptDirectlyOrIndirectly(method.getHolderType(), appInfo)) { |
| assert appInfo.isLiveMethod(reference) |
| : "Expected non-abstract kept method `" |
| + reference.toSourceString() |
| + "` to be live"; |
| } |
| }); |
| return true; |
| } |
| |
| public boolean verifyKeptTypesAreLive(AppInfoWithLiveness appInfo) { |
| noShrinking.forEachClass( |
| type -> { |
| assert appInfo.isLiveProgramType(type) |
| : "Expected kept type `" + type.toSourceString() + "` to be live"; |
| }); |
| return true; |
| } |
| |
| private boolean isKeptDirectlyOrIndirectly(DexType type, AppInfoWithLiveness appInfo) { |
| if (noShrinking.containsClass(type)) { |
| return true; |
| } |
| DexClass clazz = appInfo.definitionFor(type); |
| if (clazz == null) { |
| return false; |
| } |
| if (clazz.superType != null) { |
| return isKeptDirectlyOrIndirectly(clazz.superType, appInfo); |
| } |
| return false; |
| } |
| |
| public boolean verifyKeptItemsAreKept(AppView<? extends AppInfoWithClassHierarchy> appView) { |
| AppInfoWithClassHierarchy appInfo = appView.appInfo(); |
| GraphLens lens = appView.graphLens(); |
| // Create a mapping from each required type to the set of required members on that type. |
| Map<DexType, Set<DexMember<?, ?>>> requiredMembersPerType = new IdentityHashMap<>(); |
| noShrinking.forEachClass( |
| type -> { |
| DexType rewrittenType = lens.lookupType(type); |
| assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(rewrittenType) |
| : "Expected reference `" + rewrittenType.toSourceString() + "` to be pinned"; |
| requiredMembersPerType.computeIfAbsent(rewrittenType, key -> Sets.newIdentityHashSet()); |
| }); |
| noShrinking.forEachMember( |
| member -> { |
| DexMember<?, ?> rewrittenMember = lens.getRenamedMemberSignature(member); |
| assert !appInfo.hasLiveness() || appInfo.withLiveness().isPinned(rewrittenMember) |
| : "Expected reference `" + rewrittenMember.toSourceString() + "` to be pinned"; |
| requiredMembersPerType |
| .computeIfAbsent(rewrittenMember.holder, key -> Sets.newIdentityHashSet()) |
| .add(rewrittenMember); |
| }); |
| |
| // Run through each class in the program and check that it has members it must have. |
| for (DexProgramClass clazz : appView.appInfo().classes()) { |
| Set<DexMember<?, ?>> requiredMembers = |
| requiredMembersPerType.getOrDefault(clazz.type, ImmutableSet.of()); |
| |
| Set<DexField> fields = null; |
| Set<DexMethod> methods = null; |
| |
| for (DexMember<?, ?> requiredMember : requiredMembers) { |
| if (requiredMember.isDexField()) { |
| DexField requiredField = requiredMember.asDexField(); |
| if (fields == null) { |
| // Create a Set of the fields to avoid quadratic behavior. |
| fields = |
| Streams.stream(clazz.fields()) |
| .map(DexEncodedField::getReference) |
| .collect(Collectors.toSet()); |
| } |
| assert fields.contains(requiredField) |
| : "Expected field `" |
| + requiredField.toSourceString() |
| + "` from the root set to be present"; |
| } else { |
| DexMethod requiredMethod = requiredMember.asDexMethod(); |
| if (methods == null) { |
| // Create a Set of the methods to avoid quadratic behavior. |
| methods = |
| Streams.stream(clazz.methods()) |
| .map(DexEncodedMethod::getReference) |
| .collect(Collectors.toSet()); |
| } |
| assert methods.contains(requiredMethod) |
| : "Expected method `" |
| + requiredMethod.toSourceString() |
| + "` from the root set to be present"; |
| } |
| } |
| requiredMembersPerType.remove(clazz.type); |
| } |
| |
| // If the map is non-empty, then a type in the root set was not in the application. |
| if (!requiredMembersPerType.isEmpty()) { |
| DexType type = requiredMembersPerType.keySet().iterator().next(); |
| DexClass clazz = appView.definitionFor(type); |
| assert clazz == null || clazz.isProgramClass() |
| : "Unexpected library type in root set: `" + type + "`"; |
| assert requiredMembersPerType.isEmpty() |
| : "Expected type `" + type.toSourceString() + "` to be present"; |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder builder = new StringBuilder(); |
| builder.append("RootSet"); |
| builder.append("\nnoShrinking: " + noShrinking.size()); |
| builder.append("\nnoObfuscation: " + noObfuscation.size()); |
| builder.append("\nreasonAsked: " + reasonAsked.size()); |
| builder.append("\ncheckDiscarded: " + checkDiscarded.size()); |
| builder.append("\nnoSideEffects: " + noSideEffects.size()); |
| builder.append("\nassumedValues: " + assumedValues.size()); |
| builder.append("\ndependentNoShrinking: " + dependentNoShrinking.size()); |
| builder.append("\nidentifierNameStrings: " + identifierNameStrings.size()); |
| builder.append("\nifRules: " + ifRules.size()); |
| return builder.toString(); |
| } |
| |
| public static RootSetBuilder builder( |
| AppView<? extends AppInfoWithClassHierarchy> appView, SubtypingInfo subtypingInfo) { |
| return new RootSetBuilder(appView, subtypingInfo); |
| } |
| |
| public static RootSetBuilder builder( |
| AppView<? extends AppInfoWithClassHierarchy> appView, |
| SubtypingInfo subtypingInfo, |
| Iterable<? extends ProguardConfigurationRule> rules) { |
| return new RootSetBuilder(appView, subtypingInfo, rules); |
| } |
| } |
| |
| static class ConsequentRootSetBuilder extends RootSetBuilder { |
| |
| private final Enqueuer enqueuer; |
| |
| private ConsequentRootSetBuilder( |
| AppView<? extends AppInfoWithClassHierarchy> appView, |
| SubtypingInfo subtypingInfo, |
| Enqueuer enqueuer) { |
| super(appView, subtypingInfo, null); |
| this.enqueuer = enqueuer; |
| } |
| |
| @Override |
| void handleMatchedAnnotation(AnnotationMatchResult annotationMatchResult) { |
| if (enqueuer.getMode().isInitialTreeShaking() |
| && annotationMatchResult.isConcreteAnnotationMatchResult()) { |
| enqueuer.retainAnnotationForFinalTreeShaking( |
| annotationMatchResult.asConcreteAnnotationMatchResult().getMatchedAnnotations()); |
| } |
| } |
| } |
| |
| // A partial RootSet that becomes live due to the enabled -if rule or the addition of interface |
| // keep rules. |
| public static class ConsequentRootSet extends RootSetBase { |
| |
| ConsequentRootSet( |
| Set<DexMethod> neverInline, |
| Set<DexMethod> neverInlineDueToSingleCaller, |
| Set<DexType> neverClassInline, |
| MutableItemsWithRules noShrinking, |
| MutableItemsWithRules softPinned, |
| Set<DexReference> noObfuscation, |
| Map<DexReference, MutableItemsWithRules> dependentNoShrinking, |
| Map<DexReference, MutableItemsWithRules> dependentSoftPinned, |
| Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule, |
| List<DelayedRootSetActionItem> delayedRootSetActionItems) { |
| super( |
| neverInline, |
| neverInlineDueToSingleCaller, |
| neverClassInline, |
| noShrinking, |
| softPinned, |
| noObfuscation, |
| dependentNoShrinking, |
| dependentSoftPinned, |
| dependentKeepClassCompatRule, |
| delayedRootSetActionItems); |
| } |
| |
| static ConsequentRootSetBuilder builder( |
| AppView<? extends AppInfoWithClassHierarchy> appView, |
| SubtypingInfo subtypingInfo, |
| Enqueuer enqueuer) { |
| return new ConsequentRootSetBuilder(appView, subtypingInfo, enqueuer); |
| } |
| } |
| |
| public static class MainDexRootSetBuilder extends RootSetBuilder { |
| |
| private MainDexRootSetBuilder( |
| AppView<? extends AppInfoWithClassHierarchy> appView, |
| SubtypingInfo subtypingInfo, |
| Iterable<? extends ProguardConfigurationRule> rules) { |
| super(appView, subtypingInfo, rules); |
| } |
| |
| @Override |
| public MainDexRootSet build(ExecutorService executorService) throws ExecutionException { |
| // Call the super builder to have if-tests calculated automatically. |
| RootSet rootSet = super.build(executorService); |
| return new MainDexRootSet( |
| rootSet.noShrinking, |
| rootSet.reasonAsked, |
| rootSet.checkDiscarded, |
| rootSet.dependentNoShrinking, |
| rootSet.ifRules, |
| rootSet.delayedRootSetActionItems); |
| } |
| } |
| |
| public static class MainDexRootSet extends RootSet { |
| |
| public MainDexRootSet( |
| MutableItemsWithRules noShrinking, |
| ImmutableList<DexReference> reasonAsked, |
| ImmutableList<DexReference> checkDiscarded, |
| Map<DexReference, MutableItemsWithRules> dependentNoShrinking, |
| Set<ProguardIfRule> ifRules, |
| List<DelayedRootSetActionItem> delayedRootSetActionItems) { |
| super( |
| noShrinking, |
| new MutableItemsWithRules(), |
| Collections.emptySet(), |
| reasonAsked, |
| checkDiscarded, |
| Collections.emptySet(), |
| Collections.emptySet(), |
| Collections.emptySet(), |
| Collections.emptySet(), |
| Collections.emptySet(), |
| Collections.emptySet(), |
| Collections.emptySet(), |
| Collections.emptySet(), |
| Collections.emptySet(), |
| Collections.emptySet(), |
| PredicateSet.empty(), |
| Collections.emptySet(), |
| Collections.emptySet(), |
| Collections.emptySet(), |
| Collections.emptySet(), |
| Collections.emptySet(), |
| Collections.emptyMap(), |
| Collections.emptyMap(), |
| Collections.emptyMap(), |
| dependentNoShrinking, |
| Collections.emptyMap(), |
| Collections.emptyMap(), |
| Collections.emptySet(), |
| ifRules, |
| delayedRootSetActionItems); |
| } |
| |
| @Override |
| void addConsequentRootSet(ConsequentRootSet consequentRootSet, boolean addNoShrinking) { |
| if (addNoShrinking) { |
| noShrinking.addAll(consequentRootSet.noShrinking); |
| } |
| addDependentItems(consequentRootSet.dependentNoShrinking, dependentNoShrinking); |
| consequentRootSet.dependentKeepClassCompatRule.forEach( |
| (type, rules) -> |
| dependentKeepClassCompatRule |
| .computeIfAbsent(type, k -> new HashSet<>()) |
| .addAll(rules)); |
| } |
| |
| public static MainDexRootSetBuilder builder( |
| AppView<? extends AppInfoWithClassHierarchy> appView, |
| SubtypingInfo subtypingInfo, |
| Iterable<? extends ProguardConfigurationRule> rules) { |
| return new MainDexRootSetBuilder(appView, subtypingInfo, rules); |
| } |
| |
| @Override |
| void shouldNotBeMinified(DexReference reference) { |
| // Do nothing. |
| } |
| |
| public MainDexRootSet rewrittenWithLens(GraphLens graphLens) { |
| if (graphLens.isIdentityLens()) { |
| return this; |
| } |
| Map<DexReference, MutableItemsWithRules> rewrittenDependent = new IdentityHashMap<>(); |
| dependentNoShrinking.forEach( |
| (reference, rules) -> { |
| // Rewriting a reference can result in us having to merge items with rules. |
| rewriteAndApplyIfNotPrimitiveType( |
| graphLens, |
| reference, |
| rewritten -> { |
| MutableItemsWithRules rewrittenRules = |
| rewrittenDependent.computeIfAbsent( |
| graphLens.lookupReference(reference), |
| rewrittenRef -> new MutableItemsWithRules()); |
| rewrittenRules.addAll(rules.rewrittenWithLens(graphLens)); |
| }); |
| }); |
| |
| ImmutableList.Builder<DexReference> rewrittenCheckDiscarded = ImmutableList.builder(); |
| checkDiscarded.forEach( |
| reference -> |
| rewriteAndApplyIfNotPrimitiveType( |
| graphLens, reference, rewrittenCheckDiscarded::add)); |
| ImmutableList.Builder<DexReference> rewrittenReasonAsked = ImmutableList.builder(); |
| reasonAsked.forEach( |
| reference -> |
| rewriteAndApplyIfNotPrimitiveType(graphLens, reference, rewrittenReasonAsked::add)); |
| // TODO(b/164019179): If rules can now reference dead items. These should be pruned or |
| // rewritten |
| ifRules.forEach(ProguardIfRule::canReferenceDeadTypes); |
| // All delayed root set actions should have been processed at this point. |
| assert delayedRootSetActionItems.isEmpty(); |
| return new MainDexRootSet( |
| noShrinking.rewrittenWithLens(graphLens), |
| rewrittenReasonAsked.build(), |
| rewrittenCheckDiscarded.build(), |
| rewrittenDependent, |
| ifRules, |
| delayedRootSetActionItems); |
| } |
| |
| public MainDexRootSet withoutPrunedItems(PrunedItems prunedItems) { |
| if (prunedItems.isEmpty()) { |
| return this; |
| } |
| Map<DexReference, MutableItemsWithRules> prunedDependent = new IdentityHashMap<>(); |
| dependentNoShrinking.forEach( |
| (ref, rules) -> { |
| if (prunedItems.getRemovedClasses().contains(ref.getContextType())) { |
| // The dependent reference has been pruned and cannot lead to any additional items |
| return; |
| } |
| prunedDependent.put(ref, rules.prune(prunedItems.getRemovedClasses())); |
| }); |
| // TODO(b/164019179): If rules can now reference dead items. These should be pruned or |
| // rewritten |
| ifRules.forEach(ProguardIfRule::canReferenceDeadTypes); |
| // All delayed root set actions should have been processed at this point. |
| assert delayedRootSetActionItems.isEmpty(); |
| return new MainDexRootSet( |
| noShrinking.prune(prunedItems.getRemovedClasses()), |
| reasonAsked, |
| checkDiscarded, |
| prunedDependent, |
| ifRules, |
| delayedRootSetActionItems); |
| } |
| } |
| } |