| // 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.synthesis; | |
| import static java.util.Collections.emptyList; | |
| import com.android.tools.r8.graph.DexApplication; | |
| import com.android.tools.r8.graph.DexType; | |
| import com.android.tools.r8.graph.PrunedItems; | |
| import com.android.tools.r8.graph.lens.NonIdentityGraphLens; | |
| import com.android.tools.r8.synthesis.SyntheticItems.ContextsForGlobalSynthetics; | |
| import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind; | |
| import com.android.tools.r8.utils.internal.IterableUtils; | |
| import com.android.tools.r8.utils.internal.SetUtils; | |
| import com.android.tools.r8.utils.timing.Timing; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.common.collect.ImmutableSet; | |
| import com.google.common.collect.Iterables; | |
| import java.util.ArrayList; | |
| import java.util.Collection; | |
| import java.util.HashSet; | |
| import java.util.IdentityHashMap; | |
| import java.util.List; | |
| import java.util.Map; | |
| import java.util.Set; | |
| import java.util.function.BiConsumer; | |
| import java.util.function.Consumer; | |
| /** | |
| * Immutable collection of committed items. | |
| * | |
| * <p>This structure is to make it easier to pass the items from SyntheticItems to CommittedItems | |
| * and back while also providing a builder for updating the committed synthetics. | |
| */ | |
| public class CommittedSyntheticsCollection { | |
| static class Builder { | |
| private final CommittedSyntheticsCollection parent; | |
| private Map<DexType, List<SyntheticProgramClassReference>> classes = null; | |
| private Map<DexType, List<SyntheticMethodReference>> methods = null; | |
| private ImmutableSet.Builder<DexType> newSyntheticInputs = null; | |
| private Map<DexType, Set<DexType>> globalContexts = null; | |
| public Builder(CommittedSyntheticsCollection parent) { | |
| this.parent = parent; | |
| } | |
| public Builder addItem(SyntheticDefinition<?, ?, ?> definition) { | |
| if (definition.isProgramDefinition()) { | |
| definition.asProgramDefinition().apply(this::addMethod, this::addClass); | |
| } | |
| return this; | |
| } | |
| public Builder addClass(SyntheticProgramClassDefinition definition) { | |
| return addClass(definition.toReference()); | |
| } | |
| public Builder addClass(SyntheticProgramClassReference reference) { | |
| if (classes == null) { | |
| classes = new IdentityHashMap<>(); | |
| } | |
| classes.computeIfAbsent(reference.getHolder(), ignore -> new ArrayList<>()).add(reference); | |
| return this; | |
| } | |
| public Builder addMethod(SyntheticMethodDefinition definition) { | |
| return addMethod(definition.toReference()); | |
| } | |
| public Builder addMethod(SyntheticMethodReference reference) { | |
| if (methods == null) { | |
| methods = new IdentityHashMap<>(); | |
| } | |
| methods.computeIfAbsent(reference.getHolder(), ignore -> new ArrayList<>()).add(reference); | |
| return this; | |
| } | |
| public Builder addSyntheticInput(DexType syntheticInput) { | |
| ensureNewSyntheticInputs().add(syntheticInput); | |
| return this; | |
| } | |
| Builder collectSyntheticInputs() { | |
| if (classes != null) { | |
| ensureNewSyntheticInputs().addAll(classes.keySet()); | |
| } | |
| if (methods != null) { | |
| ensureNewSyntheticInputs().addAll(methods.keySet()); | |
| } | |
| return this; | |
| } | |
| private ImmutableSet.Builder<DexType> ensureNewSyntheticInputs() { | |
| if (newSyntheticInputs == null) { | |
| newSyntheticInputs = ImmutableSet.builder(); | |
| } | |
| return newSyntheticInputs; | |
| } | |
| public Builder addGlobalContexts(ContextsForGlobalSynthetics additionalGlobalContexts) { | |
| if (!additionalGlobalContexts.isEmpty()) { | |
| if (globalContexts == null) { | |
| globalContexts = new IdentityHashMap<>(); | |
| } | |
| additionalGlobalContexts.forEach( | |
| (globalType, contexts) -> | |
| globalContexts | |
| .computeIfAbsent(globalType, k -> SetUtils.newIdentityHashSet()) | |
| .addAll(contexts)); | |
| } | |
| return this; | |
| } | |
| public CommittedSyntheticsCollection build() { | |
| if (classes == null && methods == null && globalContexts == null) { | |
| // Adding synthetic inputs implies that an actual synthetic is added too. | |
| assert newSyntheticInputs == null; | |
| return parent; | |
| } | |
| ImmutableMap<DexType, List<SyntheticProgramClassReference>> allClasses = | |
| mergeMapOfLists(classes, parent.classes); | |
| ImmutableMap<DexType, List<SyntheticMethodReference>> allMethods = | |
| mergeMapOfLists(methods, parent.methods); | |
| ImmutableSet<DexType> allSyntheticInputs = | |
| newSyntheticInputs == null ? parent.syntheticInputs : newSyntheticInputs.build(); | |
| ImmutableMap<DexType, Set<DexType>> allGlobalContexts = | |
| globalContexts == null | |
| ? parent.globalContexts | |
| : mergeMapOfSets(globalContexts, parent.globalContexts); | |
| return new CommittedSyntheticsCollection( | |
| allMethods, allClasses, allGlobalContexts, allSyntheticInputs); | |
| } | |
| } | |
| private static <T> ImmutableMap<DexType, List<T>> mergeMapOfLists( | |
| Map<DexType, List<T>> newSynthetics, ImmutableMap<DexType, List<T>> oldSynthetics) { | |
| if (newSynthetics == null) { | |
| return oldSynthetics; | |
| } | |
| oldSynthetics.forEach( | |
| (type, elements) -> | |
| newSynthetics.computeIfAbsent(type, ignore -> new ArrayList<>()).addAll(elements)); | |
| return ImmutableMap.copyOf(newSynthetics); | |
| } | |
| private static <T> ImmutableMap<DexType, Set<T>> mergeMapOfSets( | |
| Map<DexType, Set<T>> newSynthetics, ImmutableMap<DexType, Set<T>> oldSynthetics) { | |
| if (newSynthetics == null) { | |
| return oldSynthetics; | |
| } | |
| oldSynthetics.forEach( | |
| (type, elements) -> | |
| newSynthetics.computeIfAbsent(type, ignore -> new HashSet<>()).addAll(elements)); | |
| return ImmutableMap.copyOf(newSynthetics); | |
| } | |
| /** Mapping from synthetic type to its synthetic method item description. */ | |
| private final ImmutableMap<DexType, List<SyntheticMethodReference>> methods; | |
| /** Mapping from synthetic type to its synthetic class item description. */ | |
| private final ImmutableMap<DexType, List<SyntheticProgramClassReference>> classes; | |
| /** Mapping from global synthetic type to its synthesizing contexts. */ | |
| private final ImmutableMap<DexType, Set<DexType>> globalContexts; | |
| /** Set of synthetic types that were present in the input. */ | |
| public final ImmutableSet<DexType> syntheticInputs; | |
| public CommittedSyntheticsCollection( | |
| ImmutableMap<DexType, List<SyntheticMethodReference>> methods, | |
| ImmutableMap<DexType, List<SyntheticProgramClassReference>> classes, | |
| ImmutableMap<DexType, Set<DexType>> globalContexts, | |
| ImmutableSet<DexType> syntheticInputs) { | |
| this.methods = methods; | |
| this.classes = classes; | |
| this.globalContexts = globalContexts; | |
| this.syntheticInputs = syntheticInputs; | |
| assert verifySyntheticInputsSubsetOfSynthetics(); | |
| } | |
| public CommittedSyntheticsCollection merge( | |
| ImmutableMap<DexType, List<SyntheticMethodReference>> methods, | |
| ImmutableMap<DexType, List<SyntheticProgramClassReference>> classes, | |
| ImmutableMap<DexType, Set<DexType>> globalContexts, | |
| ImmutableSet<DexType> syntheticInputs) { | |
| if (isEmpty()) { | |
| return new CommittedSyntheticsCollection(methods, classes, globalContexts, syntheticInputs); | |
| } | |
| return new CommittedSyntheticsCollection( | |
| ImmutableMap.<DexType, List<SyntheticMethodReference>>builderWithExpectedSize( | |
| this.methods.size() + methods.size()) | |
| .putAll(this.methods) | |
| .putAll(methods) | |
| .build(), | |
| ImmutableMap.<DexType, List<SyntheticProgramClassReference>>builderWithExpectedSize( | |
| this.classes.size() + classes.size()) | |
| .putAll(this.classes) | |
| .putAll(classes) | |
| .build(), | |
| ImmutableMap.<DexType, Set<DexType>>builderWithExpectedSize( | |
| this.globalContexts.size() + globalContexts.size()) | |
| .putAll(this.globalContexts) | |
| .putAll(globalContexts) | |
| .build(), | |
| ImmutableSet.<DexType>builderWithExpectedSize( | |
| this.syntheticInputs.size() + syntheticInputs.size()) | |
| .addAll(this.syntheticInputs) | |
| .addAll(syntheticInputs) | |
| .build()); | |
| } | |
| private boolean verifySyntheticInputsSubsetOfSynthetics() { | |
| Set<DexType> synthetics = | |
| ImmutableSet.<DexType>builder().addAll(methods.keySet()).addAll(classes.keySet()).build(); | |
| syntheticInputs.forEach( | |
| syntheticInput -> { | |
| assert synthetics.contains(syntheticInput) | |
| : "Expected " + syntheticInput.toSourceString() + " to be a synthetic"; | |
| }); | |
| return true; | |
| } | |
| public static CommittedSyntheticsCollection empty() { | |
| return new CommittedSyntheticsCollection( | |
| ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of(), ImmutableSet.of()); | |
| } | |
| Builder builder() { | |
| return new Builder(this); | |
| } | |
| boolean isEmpty() { | |
| boolean empty = methods.isEmpty() && classes.isEmpty(); | |
| assert !empty || syntheticInputs.isEmpty(); | |
| return empty; | |
| } | |
| public boolean containsMethod(DexType type) { | |
| return methods.containsKey(type); | |
| } | |
| public boolean containsType(DexType type) { | |
| return containsMethod(type) || classes.containsKey(type); | |
| } | |
| @SuppressWarnings("ReferenceEquality") | |
| boolean containsTypeOfKind(DexType type, SyntheticKind kind) { | |
| List<SyntheticProgramClassReference> synthetics = classes.get(type); | |
| if (synthetics == null) { | |
| List<SyntheticMethodReference> syntheticMethodReferences = methods.get(type); | |
| if (syntheticMethodReferences == null) { | |
| return false; | |
| } | |
| for (SyntheticMethodReference syntheticMethodReference : syntheticMethodReferences) { | |
| if (syntheticMethodReference.getKind() == kind) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| for (SyntheticProgramClassReference synthetic : synthetics) { | |
| if (synthetic.getKind() == kind) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| public boolean containsSyntheticInput(DexType type) { | |
| return syntheticInputs.contains(type); | |
| } | |
| public void forEachClass(BiConsumer<DexType, List<SyntheticProgramClassReference>> consumer) { | |
| classes.forEach(consumer); | |
| } | |
| public void forEachClassFlattened(BiConsumer<DexType, SyntheticProgramClassReference> consumer) { | |
| classes.forEach( | |
| (type, references) -> { | |
| for (SyntheticProgramClassReference reference : references) { | |
| consumer.accept(type, reference); | |
| } | |
| }); | |
| } | |
| public void forEachMethod(BiConsumer<DexType, List<SyntheticMethodReference>> consumer) { | |
| methods.forEach(consumer); | |
| } | |
| public void forEachMethodFlattened(BiConsumer<DexType, SyntheticMethodReference> consumer) { | |
| methods.forEach( | |
| (type, references) -> { | |
| for (SyntheticMethodReference reference : references) { | |
| consumer.accept(type, reference); | |
| } | |
| }); | |
| } | |
| public Set<DexType> getContextsForGlobal(DexType globalSynthetic) { | |
| return globalContexts.get(globalSynthetic); | |
| } | |
| public ImmutableMap<DexType, Set<DexType>> getGlobalContexts() { | |
| return globalContexts; | |
| } | |
| public ImmutableMap<DexType, List<SyntheticMethodReference>> getMethods() { | |
| return methods; | |
| } | |
| public ImmutableMap<DexType, List<SyntheticProgramClassReference>> getClasses() { | |
| return classes; | |
| } | |
| public Iterable<SyntheticReference<?, ?, ?>> getItems(DexType type) { | |
| return Iterables.concat( | |
| classes.getOrDefault(type, emptyList()), methods.getOrDefault(type, emptyList())); | |
| } | |
| public void forEachSyntheticInput(Consumer<DexType> fn) { | |
| syntheticInputs.forEach(fn); | |
| } | |
| public void forEachItem(Consumer<SyntheticReference<?, ?, ?>> fn) { | |
| methods.values().forEach(r -> r.forEach(fn)); | |
| classes.values().forEach(r -> r.forEach(fn)); | |
| } | |
| CommittedSyntheticsCollection pruneItems(PrunedItems prunedItems) { | |
| Set<DexType> removed = prunedItems.getRemovedClasses(); | |
| if (removed.isEmpty()) { | |
| return this; | |
| } | |
| Builder builder = CommittedSyntheticsCollection.empty().builder(); | |
| boolean changed = false; | |
| for (SyntheticMethodReference reference : IterableUtils.flatten(methods.values())) { | |
| if (removed.contains(reference.getHolder())) { | |
| changed = true; | |
| } else { | |
| builder.addMethod(reference); | |
| } | |
| } | |
| for (SyntheticProgramClassReference reference : IterableUtils.flatten(classes.values())) { | |
| if (removed.contains(reference.getHolder())) { | |
| changed = true; | |
| } else { | |
| builder.addClass(reference); | |
| } | |
| } | |
| for (DexType syntheticInput : syntheticInputs) { | |
| if (removed.contains(syntheticInput)) { | |
| changed = true; | |
| } else { | |
| builder.addSyntheticInput(syntheticInput); | |
| } | |
| } | |
| // Global synthetic contexts are only collected for per-file modes which only prune synthetic | |
| // items, not inputs. | |
| return changed ? builder.build() : this; | |
| } | |
| CommittedSyntheticsCollection rewriteWithLens(NonIdentityGraphLens lens, Timing timing) { | |
| ImmutableSet.Builder<DexType> syntheticInputsBuilder = ImmutableSet.builder(); | |
| return new CommittedSyntheticsCollection( | |
| rewriteItems(methods, lens, syntheticInputsBuilder), | |
| rewriteItems(classes, lens, syntheticInputsBuilder), | |
| globalContexts, | |
| syntheticInputsBuilder.build()); | |
| } | |
| private <R extends Rewritable<R>> ImmutableMap<DexType, List<R>> rewriteItems( | |
| Map<DexType, List<R>> items, | |
| NonIdentityGraphLens lens, | |
| ImmutableSet.Builder<DexType> syntheticInputsBuilder) { | |
| Map<DexType, List<R>> rewrittenItems = new IdentityHashMap<>(); | |
| for (R reference : IterableUtils.flatten(items.values())) { | |
| R rewritten = reference.rewrite(lens); | |
| if (rewritten != null) { | |
| rewrittenItems | |
| .computeIfAbsent(rewritten.getHolder(), ignore -> new ArrayList<>()) | |
| .add(rewritten); | |
| if (syntheticInputs.contains(reference.getHolder())) { | |
| syntheticInputsBuilder.add(rewritten.getHolder()); | |
| } | |
| } | |
| } | |
| return ImmutableMap.copyOf(rewrittenItems); | |
| } | |
| boolean verifyTypesAreInApp(DexApplication application) { | |
| assert verifyTypesAreInApp(application, methods.keySet()); | |
| assert verifyTypesAreInApp(application, classes.keySet()); | |
| assert verifyTypesAreInApp(application, syntheticInputs); | |
| return true; | |
| } | |
| private static boolean verifyTypesAreInApp(DexApplication app, Collection<DexType> types) { | |
| for (DexType type : types) { | |
| assert app.programDefinitionFor(type) != null : "Missing synthetic: " + type; | |
| } | |
| return true; | |
| } | |
| } |