|  | // Copyright (c) 2020, 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.shaking.MainDexInfo.MainDexGroup.MAIN_DEX_ROOT; | 
|  | import static com.android.tools.r8.utils.LensUtils.rewriteAndApplyIfNotPrimitiveType; | 
|  |  | 
|  | import com.android.tools.r8.graph.AppInfoWithClassHierarchy; | 
|  | import com.android.tools.r8.graph.AppView; | 
|  | 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.ProgramDefinition; | 
|  | import com.android.tools.r8.graph.ProgramMethod; | 
|  | import com.android.tools.r8.graph.PrunedItems; | 
|  | import com.android.tools.r8.graph.lens.GraphLens; | 
|  | import com.android.tools.r8.synthesis.SyntheticItems; | 
|  | import com.android.tools.r8.utils.ConsumerUtils; | 
|  | import com.android.tools.r8.utils.Timing; | 
|  | import com.google.common.collect.Sets; | 
|  | import java.util.Collections; | 
|  | import java.util.Set; | 
|  | import java.util.function.Consumer; | 
|  |  | 
|  | public class MainDexInfo { | 
|  |  | 
|  | private static final MainDexInfo NONE = | 
|  | new MainDexInfo( | 
|  | Collections.emptySet(), | 
|  | Collections.emptySet(), | 
|  | Collections.emptySet(), | 
|  | Collections.emptySet(), | 
|  | false); | 
|  |  | 
|  | public enum MainDexGroup { | 
|  | MAIN_DEX_LIST, | 
|  | MAIN_DEX_ROOT, | 
|  | MAIN_DEX_DEPENDENCY, | 
|  | NOT_IN_MAIN_DEX | 
|  | } | 
|  |  | 
|  | // Specific set of classes specified to be in main-dex | 
|  | private final Set<DexType> classList; | 
|  | // Traced roots are traced main dex references. | 
|  | private final Set<DexType> tracedRoots; | 
|  | // Traced method roots are the methods visited from an initial main dex root set. The set is | 
|  | // cleared after the mergers has run. | 
|  | private Set<DexMethod> tracedMethodRoots; | 
|  | // Traced dependencies are those classes that are directly referenced from traced roots, but will | 
|  | // not be loaded before loading the remaining dex files. | 
|  | private final Set<DexType> tracedDependencies; | 
|  | // Bit indicating if the traced methods are cleared. | 
|  | private boolean tracedMethodRootsCleared = false; | 
|  |  | 
|  | private MainDexInfo(Set<DexType> classList) { | 
|  | this( | 
|  | classList, | 
|  | Collections.emptySet(), | 
|  | Collections.emptySet(), | 
|  | Collections.emptySet(), | 
|  | false); | 
|  | } | 
|  |  | 
|  | private MainDexInfo( | 
|  | Set<DexType> classList, | 
|  | Set<DexType> tracedRoots, | 
|  | Set<DexMethod> tracedMethodRoots, | 
|  | Set<DexType> tracedDependencies, | 
|  | boolean tracedMethodRootsCleared) { | 
|  | this.classList = classList; | 
|  | this.tracedRoots = tracedRoots; | 
|  | this.tracedMethodRoots = tracedMethodRoots; | 
|  | this.tracedDependencies = tracedDependencies; | 
|  | this.tracedMethodRootsCleared = tracedMethodRootsCleared; | 
|  | assert tracedDependencies.stream().noneMatch(tracedRoots::contains); | 
|  | } | 
|  |  | 
|  | // TODO(b/181858113): Remove once deprecated main-dex-list is removed. | 
|  | public boolean isSyntheticContextOnMainDexList(DexType syntheticContextType) { | 
|  | return classList.contains(syntheticContextType); | 
|  | } | 
|  |  | 
|  | public boolean isNone() { | 
|  | assert none() == NONE; | 
|  | return this == NONE; | 
|  | } | 
|  |  | 
|  | public boolean isFromList(ProgramDefinition definition, SyntheticItems synthetics) { | 
|  | return isFromList(definition.getContextType(), synthetics); | 
|  | } | 
|  |  | 
|  | private boolean isFromList(DexReference reference, SyntheticItems synthetics) { | 
|  | return isContainedOrHasContainedContext(reference, classList, synthetics); | 
|  | } | 
|  |  | 
|  | public boolean isTracedRoot(ProgramDefinition definition, SyntheticItems synthetics) { | 
|  | return isTracedRoot(definition.getContextType(), synthetics); | 
|  | } | 
|  |  | 
|  | public boolean isTracedMethodRoot(DexMethod method) { | 
|  | assert !tracedMethodRootsCleared : "Traced method roots are cleared after mergers has run"; | 
|  | return tracedMethodRoots.contains(method); | 
|  | } | 
|  |  | 
|  | private boolean isTracedRoot(DexReference reference, SyntheticItems synthetics) { | 
|  | return isContainedOrHasContainedContext(reference, tracedRoots, synthetics); | 
|  | } | 
|  |  | 
|  | private boolean isContainedOrHasContainedContext( | 
|  | DexReference reference, Set<DexType> items, SyntheticItems synthetics) { | 
|  | if (items.isEmpty()) { | 
|  | return false; | 
|  | } | 
|  | DexType type = reference.getContextType(); | 
|  | for (DexType context : synthetics.getSynthesizingContextTypes(type)) { | 
|  | if (items.contains(context)) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return items.contains(type); | 
|  | } | 
|  |  | 
|  | private boolean isDependency(DexReference reference) { | 
|  | return tracedDependencies.contains(reference.getContextType()); | 
|  | } | 
|  |  | 
|  | public boolean isTracedMethodRootsCleared() { | 
|  | return tracedMethodRootsCleared; | 
|  | } | 
|  |  | 
|  | public void clearTracedMethodRoots() { | 
|  | this.tracedMethodRootsCleared = true; | 
|  | this.tracedMethodRoots = Sets.newIdentityHashSet(); | 
|  | } | 
|  |  | 
|  | public boolean canRebindReference( | 
|  | ProgramMethod context, DexReference referenceToTarget, SyntheticItems synthetics) { | 
|  | MainDexGroup holderGroup = getMainDexGroupInternal(context, synthetics); | 
|  | if (holderGroup == MainDexGroup.NOT_IN_MAIN_DEX | 
|  | || holderGroup == MainDexGroup.MAIN_DEX_DEPENDENCY) { | 
|  | // We are always free to rebind/inline into something not in main-dex or traced dependencies. | 
|  | return true; | 
|  | } | 
|  | if (holderGroup == MainDexGroup.MAIN_DEX_LIST) { | 
|  | // If the holder is in the class list, we are not allowed to make any assumptions on the | 
|  | // holder being a root or a dependency. Therefore we cannot merge. | 
|  | return false; | 
|  | } | 
|  | assert holderGroup == MAIN_DEX_ROOT; | 
|  | // Otherwise we allow if either is both root. | 
|  | return getMainDexGroupInternal(referenceToTarget, synthetics) == MAIN_DEX_ROOT; | 
|  | } | 
|  |  | 
|  | public boolean canMerge(ProgramDefinition candidate, SyntheticItems synthetics) { | 
|  | return !isFromList(candidate, synthetics); | 
|  | } | 
|  |  | 
|  | public boolean canMerge( | 
|  | ProgramDefinition source, ProgramDefinition target, SyntheticItems synthetics) { | 
|  | return canMerge(source.getContextType(), target.getContextType(), synthetics); | 
|  | } | 
|  |  | 
|  | private boolean canMerge(DexReference source, DexReference target, SyntheticItems synthetics) { | 
|  | MainDexGroup sourceGroup = getMainDexGroupInternal(source, synthetics); | 
|  | MainDexGroup targetGroup = getMainDexGroupInternal(target, synthetics); | 
|  | if (sourceGroup != targetGroup) { | 
|  | return false; | 
|  | } | 
|  | // If the holder is in the class list, we are not allowed to make any assumptions on the holder | 
|  | // being a root or a dependency. Therefore we cannot merge. | 
|  | return sourceGroup != MainDexGroup.MAIN_DEX_LIST; | 
|  | } | 
|  |  | 
|  | public MainDexGroup getMergeKey(ProgramDefinition mergeCandidate, SyntheticItems synthetics) { | 
|  | assert canMerge(mergeCandidate, synthetics); | 
|  | MainDexGroup mainDexGroupInternal = getMainDexGroupInternal(mergeCandidate, synthetics); | 
|  | return mainDexGroupInternal == MainDexGroup.MAIN_DEX_LIST ? null : mainDexGroupInternal; | 
|  | } | 
|  |  | 
|  | private MainDexGroup getMainDexGroupInternal( | 
|  | ProgramDefinition definition, SyntheticItems synthetics) { | 
|  | return getMainDexGroupInternal(definition.getReference(), synthetics); | 
|  | } | 
|  |  | 
|  | private MainDexGroup getMainDexGroupInternal(DexReference reference, SyntheticItems synthetics) { | 
|  | if (isFromList(reference, synthetics)) { | 
|  | return MainDexGroup.MAIN_DEX_LIST; | 
|  | } | 
|  | if (isTracedRoot(reference, synthetics)) { | 
|  | return MAIN_DEX_ROOT; | 
|  | } | 
|  | if (isDependency(reference)) { | 
|  | return MainDexGroup.MAIN_DEX_DEPENDENCY; | 
|  | } | 
|  | return MainDexGroup.NOT_IN_MAIN_DEX; | 
|  | } | 
|  |  | 
|  | @SuppressWarnings("ReferenceEquality") | 
|  | public boolean disallowInliningIntoContext( | 
|  | AppView<? extends AppInfoWithClassHierarchy> appView, | 
|  | ProgramDefinition context, | 
|  | ProgramMethod method) { | 
|  | if (context.getContextType() == method.getContextType()) { | 
|  | return false; | 
|  | } | 
|  | SyntheticItems synthetics = appView.getSyntheticItems(); | 
|  | MainDexGroup mainDexGroupInternal = getMainDexGroupInternal(context, synthetics); | 
|  | if (mainDexGroupInternal == MainDexGroup.NOT_IN_MAIN_DEX | 
|  | || mainDexGroupInternal == MainDexGroup.MAIN_DEX_DEPENDENCY) { | 
|  | return false; | 
|  | } | 
|  | if (mainDexGroupInternal == MainDexGroup.MAIN_DEX_LIST) { | 
|  | return MainDexDirectReferenceTracer.hasReferencesOutsideMainDexClasses( | 
|  | appView, method, t -> !isFromList(t, synthetics)); | 
|  | } | 
|  | assert mainDexGroupInternal == MAIN_DEX_ROOT; | 
|  | return MainDexDirectReferenceTracer.hasReferencesOutsideMainDexClasses( | 
|  | appView, method, t -> !isTracedRoot(t, synthetics)); | 
|  | } | 
|  |  | 
|  | public boolean isEmpty() { | 
|  | assert !tracedRoots.isEmpty() || tracedDependencies.isEmpty(); | 
|  | return tracedRoots.isEmpty() && classList.isEmpty(); | 
|  | } | 
|  |  | 
|  | public static MainDexInfo none() { | 
|  | return NONE; | 
|  | } | 
|  |  | 
|  | public int size() { | 
|  | return classList.size() + tracedRoots.size() + tracedDependencies.size(); | 
|  | } | 
|  |  | 
|  | public void forEachExcludingDependencies(Consumer<DexType> fn) { | 
|  | // Prevent seeing duplicates in the list and roots. | 
|  | Set<DexType> seen = Sets.newIdentityHashSet(); | 
|  | classList.forEach(ConsumerUtils.acceptIfNotSeen(fn, seen)); | 
|  | tracedRoots.forEach(ConsumerUtils.acceptIfNotSeen(fn, seen)); | 
|  | } | 
|  |  | 
|  | public void forEach(Consumer<DexType> fn) { | 
|  | // Prevent seeing duplicates in the list and roots. | 
|  | Set<DexType> seen = Sets.newIdentityHashSet(); | 
|  | classList.forEach(ConsumerUtils.acceptIfNotSeen(fn, seen)); | 
|  | tracedRoots.forEach(ConsumerUtils.acceptIfNotSeen(fn, seen)); | 
|  | tracedDependencies.forEach(ConsumerUtils.acceptIfNotSeen(fn, seen)); | 
|  | } | 
|  |  | 
|  | public MainDexInfo withoutPrunedItems(PrunedItems prunedItems) { | 
|  | if (prunedItems.isEmpty()) { | 
|  | return this; | 
|  | } | 
|  | Set<DexType> removedClasses = prunedItems.getRemovedClasses(); | 
|  | Set<DexType> modifiedClassList = Sets.newIdentityHashSet(); | 
|  | classList.forEach(type -> ifNotRemoved(type, removedClasses, modifiedClassList::add)); | 
|  | MainDexInfo.Builder builder = builder(); | 
|  | tracedRoots.forEach(type -> ifNotRemoved(type, removedClasses, builder::addRoot)); | 
|  | // TODO(b/169927809): Methods could be pruned without the holder being pruned, however, one has | 
|  | //  to have a reference for querying a root. | 
|  | tracedMethodRoots.forEach( | 
|  | method -> | 
|  | ifNotRemoved( | 
|  | method.getHolderType(), removedClasses, ignored -> builder.addRoot(method))); | 
|  | tracedDependencies.forEach(type -> ifNotRemoved(type, removedClasses, builder::addDependency)); | 
|  | return builder.build(modifiedClassList); | 
|  | } | 
|  |  | 
|  | private void ifNotRemoved( | 
|  | DexType type, Set<DexType> removedClasses, Consumer<DexType> notRemoved) { | 
|  | if (!removedClasses.contains(type)) { | 
|  | notRemoved.accept(type); | 
|  | } | 
|  | } | 
|  |  | 
|  | public MainDexInfo rewrittenWithLens( | 
|  | SyntheticItems syntheticItems, GraphLens lens, Timing timing) { | 
|  | return timing.time("Rewrite MainDexInfo", () -> rewrittenWithLens(syntheticItems, lens)); | 
|  | } | 
|  |  | 
|  | private MainDexInfo rewrittenWithLens(SyntheticItems syntheticItems, GraphLens lens) { | 
|  | Set<DexType> modifiedClassList = Sets.newIdentityHashSet(); | 
|  | classList.forEach( | 
|  | type -> rewriteAndApplyIfNotPrimitiveType(lens, type, modifiedClassList::add)); | 
|  | MainDexInfo.Builder builder = builder(); | 
|  | tracedRoots.forEach(type -> rewriteAndApplyIfNotPrimitiveType(lens, type, builder::addRoot)); | 
|  | tracedMethodRoots.forEach(method -> builder.addRoot(lens.getRenamedMethodSignature(method))); | 
|  | tracedDependencies.forEach( | 
|  | type -> { | 
|  | if (lens.isSyntheticFinalizationGraphLens()) { | 
|  | // Synthetic finalization is allowed to merge identical classes into the same class. The | 
|  | // rewritten type of a traced dependency can therefore be finalized with a traced root. | 
|  | rewriteAndApplyIfNotPrimitiveType(lens, type, builder::addDependencyIfNotRoot); | 
|  | } else if (syntheticItems.isFinalized()) { | 
|  | rewriteAndApplyIfNotPrimitiveType( | 
|  | lens, type, builder.addDependencyAllowSyntheticRoot(syntheticItems)); | 
|  | } else { | 
|  | rewriteAndApplyIfNotPrimitiveType(lens, type, builder::addDependency); | 
|  | } | 
|  | }); | 
|  | return builder.build(modifiedClassList); | 
|  | } | 
|  |  | 
|  | public Builder builder() { | 
|  | return new Builder(tracedMethodRootsCleared); | 
|  | } | 
|  |  | 
|  | public static class Builder { | 
|  |  | 
|  | private final Set<DexType> list = Sets.newIdentityHashSet(); | 
|  | private final Set<DexType> roots = Sets.newIdentityHashSet(); | 
|  | private final Set<DexMethod> methodRoots = Sets.newIdentityHashSet(); | 
|  | private final Set<DexType> dependencies = Sets.newIdentityHashSet(); | 
|  | private final boolean tracedMethodRootsCleared; | 
|  |  | 
|  | private Builder(boolean tracedMethodRootsCleared) { | 
|  | this.tracedMethodRootsCleared = tracedMethodRootsCleared; | 
|  | } | 
|  |  | 
|  | public void addList(DexProgramClass clazz) { | 
|  | addList(clazz.getType()); | 
|  | } | 
|  |  | 
|  | public void addList(DexType type) { | 
|  | list.add(type); | 
|  | } | 
|  |  | 
|  | public void addRoot(DexProgramClass clazz) { | 
|  | addRoot(clazz.getType()); | 
|  | } | 
|  |  | 
|  | public void addRoot(DexType type) { | 
|  | assert !dependencies.contains(type); | 
|  | roots.add(type); | 
|  | } | 
|  |  | 
|  | public void addRoot(DexMethod method) { | 
|  | methodRoots.add(method); | 
|  | } | 
|  |  | 
|  | public void addDependency(DexProgramClass clazz) { | 
|  | addDependency(clazz.getType()); | 
|  | } | 
|  |  | 
|  | public void addDependency(DexType type) { | 
|  | assert !roots.contains(type); | 
|  | dependencies.add(type); | 
|  | } | 
|  |  | 
|  | public Consumer<DexType> addDependencyAllowSyntheticRoot(SyntheticItems syntheticItems) { | 
|  | return type -> { | 
|  | assert !roots.contains(type) || syntheticItems.isCommittedSynthetic(type); | 
|  | addDependencyIfNotRoot(type); | 
|  | }; | 
|  | } | 
|  |  | 
|  | public void addDependencyIfNotRoot(DexType type) { | 
|  | if (roots.contains(type)) { | 
|  | return; | 
|  | } | 
|  | addDependency(type); | 
|  | } | 
|  |  | 
|  | public boolean isTracedRoot(DexProgramClass clazz) { | 
|  | return isTracedRoot(clazz.getType()); | 
|  | } | 
|  |  | 
|  | public boolean isTracedRoot(DexType type) { | 
|  | return roots.contains(type); | 
|  | } | 
|  |  | 
|  | public boolean isDependency(DexProgramClass clazz) { | 
|  | return isDependency(clazz.getType()); | 
|  | } | 
|  |  | 
|  | public boolean isDependency(DexType type) { | 
|  | return dependencies.contains(type); | 
|  | } | 
|  |  | 
|  | public boolean contains(DexProgramClass clazz) { | 
|  | return contains(clazz.type); | 
|  | } | 
|  |  | 
|  | public boolean contains(DexType type) { | 
|  | return isTracedRoot(type) || isDependency(type); | 
|  | } | 
|  |  | 
|  | public Set<DexType> getRoots() { | 
|  | return roots; | 
|  | } | 
|  |  | 
|  | public MainDexInfo buildList() { | 
|  | // When building without passing the list, the method roots and dependencies should | 
|  | // be empty since no tracing has been done. | 
|  | assert dependencies.isEmpty(); | 
|  | assert roots.isEmpty(); | 
|  | return new MainDexInfo(list); | 
|  | } | 
|  |  | 
|  | public MainDexInfo build(Set<DexType> classList) { | 
|  | // Class can contain dependencies which we should not regard as roots. | 
|  | assert list.isEmpty(); | 
|  | return new MainDexInfo(classList, roots, methodRoots, dependencies, tracedMethodRootsCleared); | 
|  | } | 
|  |  | 
|  | public MainDexInfo build(MainDexInfo previous) { | 
|  | return build(previous.classList); | 
|  | } | 
|  |  | 
|  | public MainDexInfo build() { | 
|  | return new MainDexInfo(list, roots, methodRoots, dependencies, tracedMethodRootsCleared); | 
|  | } | 
|  | } | 
|  |  | 
|  | public Builder builderFromCopy() { | 
|  | Builder builder = new Builder(tracedMethodRootsCleared); | 
|  | builder.list.addAll(classList); | 
|  | builder.roots.addAll(tracedRoots); | 
|  | builder.methodRoots.addAll(tracedMethodRoots); | 
|  | builder.dependencies.addAll(tracedDependencies); | 
|  | return builder; | 
|  | } | 
|  | } |