| // 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 com.android.tools.r8.errors.InternalCompilerError; |
| import com.android.tools.r8.graph.AppView; |
| import com.android.tools.r8.graph.DexApplication; |
| import com.android.tools.r8.graph.DexClass; |
| import com.android.tools.r8.graph.DexItemFactory; |
| import com.android.tools.r8.graph.DexProgramClass; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens; |
| import com.android.tools.r8.graph.ProgramMethod; |
| import com.android.tools.r8.synthesis.SyntheticFinalization.Result; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.ImmutableSet.Builder; |
| import com.google.common.collect.Sets; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.function.Consumer; |
| import java.util.function.Function; |
| |
| public class SyntheticItems implements SyntheticDefinitionsProvider { |
| |
| static final int INVALID_ID_AFTER_SYNTHETIC_FINALIZATION = -1; |
| |
| /** |
| * The internal synthetic class separator is only used for representing synthetic items during |
| * compilation. In particular, this separator must never be used to write synthetic classes to the |
| * final compilation result. |
| */ |
| public static final String INTERNAL_SYNTHETIC_CLASS_SEPARATOR = "-$$InternalSynthetic"; |
| |
| /** |
| * The external synthetic class separator is used when writing classes. It may appear in types |
| * during compilation as the output of a compilation may be the input to another. |
| */ |
| public static final String EXTERNAL_SYNTHETIC_CLASS_SEPARATOR = "-$$ExternalSynthetic"; |
| |
| /** Method prefix when generating synthetic methods in a class. */ |
| static final String INTERNAL_SYNTHETIC_METHOD_PREFIX = "m"; |
| |
| public static boolean verifyNotInternalSynthetic(DexType type) { |
| assert !type.toDescriptorString().contains(SyntheticItems.INTERNAL_SYNTHETIC_CLASS_SEPARATOR); |
| return true; |
| } |
| |
| /** Globally incremented id for the next internal synthetic class. */ |
| private int nextSyntheticId; |
| |
| /** |
| * Thread safe collection of synthesized classes that are not yet committed to the application. |
| * TODO(b/158159959): Remove legacy support. |
| */ |
| private final Map<DexType, DexProgramClass> legacyPendingClasses = new ConcurrentHashMap<>(); |
| |
| /** |
| * Immutable set of synthetic types in the application (eg, committed). TODO(b/158159959): Remove |
| * legacy support. |
| */ |
| private final ImmutableSet<DexType> legacySyntheticTypes; |
| |
| /** Thread safe collection of synthetic items not yet committed to the application. */ |
| private final ConcurrentHashMap<DexType, SyntheticDefinition> pendingDefinitions = |
| new ConcurrentHashMap<>(); |
| |
| /** Mapping from synthetic type to its synthetic description. */ |
| private final ImmutableMap<DexType, SyntheticReference> nonLecacySyntheticItems; |
| |
| // Only for use from initial AppInfo/AppInfoWithClassHierarchy create functions. */ |
| public static SyntheticItems createInitialSyntheticItems() { |
| return new SyntheticItems(0, ImmutableSet.of(), ImmutableMap.of()); |
| } |
| |
| // Only for conversion to a mutable synthetic items collection. |
| SyntheticItems(CommittedItems commit) { |
| this(commit.nextSyntheticId, commit.legacySyntheticTypes, commit.syntheticItems); |
| } |
| |
| private SyntheticItems( |
| int nextSyntheticId, |
| ImmutableSet<DexType> legacySyntheticTypes, |
| ImmutableMap<DexType, SyntheticReference> nonLecacySyntheticItems) { |
| this.nextSyntheticId = nextSyntheticId; |
| this.legacySyntheticTypes = legacySyntheticTypes; |
| this.nonLecacySyntheticItems = nonLecacySyntheticItems; |
| assert nonLecacySyntheticItems.keySet().stream() |
| .noneMatch( |
| t -> t.toDescriptorString().endsWith(getSyntheticDescriptorSuffix(nextSyntheticId))); |
| assert Sets.intersection(nonLecacySyntheticItems.keySet(), legacySyntheticTypes).isEmpty(); |
| } |
| |
| // Internal synthetic id creation helpers. |
| |
| private synchronized int getNextSyntheticId() { |
| if (nextSyntheticId == INVALID_ID_AFTER_SYNTHETIC_FINALIZATION) { |
| throw new InternalCompilerError( |
| "Unexpected attempt to synthesize classes after synthetic finalization."); |
| } |
| return nextSyntheticId++; |
| } |
| |
| private static DexType hygienicType( |
| DexItemFactory factory, int syntheticId, SynthesizingContext context) { |
| String contextDesc = context.type.toDescriptorString(); |
| String prefix = contextDesc.substring(0, contextDesc.length() - 1); |
| String syntheticDesc = prefix + getSyntheticDescriptorSuffix(syntheticId); |
| return factory.createType(syntheticDesc); |
| } |
| |
| private static String getSyntheticDescriptorSuffix(int syntheticId) { |
| return INTERNAL_SYNTHETIC_CLASS_SEPARATOR + syntheticId + ";"; |
| } |
| |
| // Predicates and accessors. |
| |
| @Override |
| public DexClass definitionFor(DexType type, Function<DexType, DexClass> baseDefinitionFor) { |
| DexProgramClass pending = legacyPendingClasses.get(type); |
| if (pending == null) { |
| SyntheticDefinition item = pendingDefinitions.get(type); |
| if (item != null) { |
| pending = item.getHolder(); |
| } |
| } |
| if (pending != null) { |
| assert baseDefinitionFor.apply(type) == null |
| : "Pending synthetic definition also present in the active program: " + type; |
| return pending; |
| } |
| return baseDefinitionFor.apply(type); |
| } |
| |
| public boolean hasPendingSyntheticClasses() { |
| return !legacyPendingClasses.isEmpty() || !pendingDefinitions.isEmpty(); |
| } |
| |
| public Collection<DexProgramClass> getPendingSyntheticClasses() { |
| List<DexProgramClass> pending = |
| new ArrayList<>(pendingDefinitions.size() + legacyPendingClasses.size()); |
| for (SyntheticDefinition item : pendingDefinitions.values()) { |
| pending.add(item.getHolder()); |
| } |
| pending.addAll(legacyPendingClasses.values()); |
| return Collections.unmodifiableList(pending); |
| } |
| |
| private boolean isCommittedSynthetic(DexType type) { |
| return nonLecacySyntheticItems.containsKey(type) || legacySyntheticTypes.contains(type); |
| } |
| |
| public boolean isPendingSynthetic(DexType type) { |
| return pendingDefinitions.containsKey(type) || legacyPendingClasses.containsKey(type); |
| } |
| |
| public boolean isSyntheticClass(DexType type) { |
| return isCommittedSynthetic(type) |
| || isPendingSynthetic(type) |
| // TODO(b/158159959): Remove usage of name-based identification. |
| || type.isD8R8SynthesizedClassType(); |
| } |
| |
| public boolean isSyntheticClass(DexProgramClass clazz) { |
| return isSyntheticClass(clazz.type); |
| } |
| |
| public Collection<DexProgramClass> getLegacyPendingClasses() { |
| return Collections.unmodifiableCollection(legacyPendingClasses.values()); |
| } |
| |
| private SynthesizingContext getSynthesizingContext(DexProgramClass context) { |
| SyntheticDefinition pendingItemContext = pendingDefinitions.get(context.type); |
| if (pendingItemContext != null) { |
| return pendingItemContext.getContext(); |
| } |
| SyntheticReference committedItemContext = nonLecacySyntheticItems.get(context.type); |
| return committedItemContext != null |
| ? committedItemContext.getContext() |
| : new SynthesizingContext(context.type, context.origin); |
| } |
| |
| // Addition and creation of synthetic items. |
| |
| // TODO(b/158159959): Remove the usage of this direct class addition (and name-based id). |
| public void addLegacySyntheticClass(DexProgramClass clazz) { |
| assert clazz.type.isD8R8SynthesizedClassType(); |
| assert !isCommittedSynthetic(clazz.type); |
| DexProgramClass previous = legacyPendingClasses.put(clazz.type, clazz); |
| assert previous == null || previous == clazz; |
| } |
| |
| /** Create a single synthetic method item. */ |
| public ProgramMethod createMethod( |
| DexProgramClass context, DexItemFactory factory, Consumer<SyntheticMethodBuilder> fn) { |
| // Obtain the outer synthesizing context in the case the context itself is synthetic. |
| // The is to ensure a flat input-type -> synthetic-item mapping. |
| SynthesizingContext outerContext = getSynthesizingContext(context); |
| DexType type = hygienicType(factory, getNextSyntheticId(), outerContext); |
| DexProgramClass clazz = |
| new SyntheticClassBuilder(type, outerContext, factory).addMethod(fn).build(); |
| ProgramMethod method = new ProgramMethod(clazz, clazz.methods().iterator().next()); |
| addPendingDefinition(new SyntheticMethodDefinition(outerContext, method)); |
| return method; |
| } |
| |
| private void addPendingDefinition(SyntheticDefinition definition) { |
| pendingDefinitions.put(definition.getHolder().getType(), definition); |
| } |
| |
| // Commit of the synthetic items to a new fully populated application. |
| |
| public CommittedItems commit(DexApplication application) { |
| return commitPrunedClasses(application, Collections.emptySet()); |
| } |
| |
| public CommittedItems commitPrunedClasses( |
| DexApplication application, Set<DexType> removedClasses) { |
| return commit( |
| application, |
| removedClasses, |
| legacyPendingClasses, |
| legacySyntheticTypes, |
| pendingDefinitions, |
| nonLecacySyntheticItems, |
| nextSyntheticId); |
| } |
| |
| public CommittedItems commitRewrittenWithLens( |
| DexApplication application, NonIdentityGraphLens lens) { |
| // Rewrite the previously committed synthetic types. |
| ImmutableSet<DexType> rewrittenLegacyTypes = lens.rewriteTypes(this.legacySyntheticTypes); |
| ImmutableMap.Builder<DexType, SyntheticReference> rewrittenItems = ImmutableMap.builder(); |
| for (SyntheticReference reference : nonLecacySyntheticItems.values()) { |
| SyntheticReference rewritten = reference.rewrite(lens); |
| rewrittenItems.put(rewritten.getHolder(), rewritten); |
| } |
| // No pending item should need rewriting. |
| assert legacyPendingClasses.keySet().equals(lens.rewriteTypes(legacyPendingClasses.keySet())); |
| assert pendingDefinitions.keySet().equals(lens.rewriteTypes(pendingDefinitions.keySet())); |
| return commit( |
| application, |
| Collections.emptySet(), |
| legacyPendingClasses, |
| rewrittenLegacyTypes, |
| pendingDefinitions, |
| rewrittenItems.build(), |
| nextSyntheticId); |
| } |
| |
| private static CommittedItems commit( |
| DexApplication application, |
| Set<DexType> removedClasses, |
| Map<DexType, DexProgramClass> legacyPendingClasses, |
| ImmutableSet<DexType> legacySyntheticTypes, |
| ConcurrentHashMap<DexType, SyntheticDefinition> pendingDefinitions, |
| ImmutableMap<DexType, SyntheticReference> syntheticItems, |
| int nextSyntheticId) { |
| // Legacy synthetics must already have been committed. |
| assert verifyClassesAreInApp(application, legacyPendingClasses.values()); |
| // Add the set of legacy definitions to the synthetic types. |
| ImmutableSet<DexType> mergedLegacyTypes = legacySyntheticTypes; |
| if (!legacyPendingClasses.isEmpty() || !removedClasses.isEmpty()) { |
| ImmutableSet.Builder<DexType> legacyBuilder = ImmutableSet.builder(); |
| filteredAdd(legacySyntheticTypes, removedClasses, legacyBuilder); |
| filteredAdd(legacyPendingClasses.keySet(), removedClasses, legacyBuilder); |
| mergedLegacyTypes = legacyBuilder.build(); |
| } |
| // The set of synthetic items is the union of the previous types plus the pending additions. |
| ImmutableMap<DexType, SyntheticReference> mergedItems; |
| ImmutableList<DexType> additions; |
| DexApplication amendedApplication; |
| if (pendingDefinitions.isEmpty()) { |
| mergedItems = filteredCopy(syntheticItems, removedClasses); |
| additions = ImmutableList.of(); |
| amendedApplication = application; |
| } else { |
| DexApplication.Builder<?> appBuilder = application.builder(); |
| ImmutableMap.Builder<DexType, SyntheticReference> itemsBuilder = ImmutableMap.builder(); |
| ImmutableList.Builder<DexType> additionsBuilder = ImmutableList.builder(); |
| for (SyntheticDefinition definition : pendingDefinitions.values()) { |
| if (removedClasses.contains(definition.getHolder().getType())) { |
| continue; |
| } |
| SyntheticReference reference = definition.toReference(); |
| itemsBuilder.put(reference.getHolder(), reference); |
| additionsBuilder.add(definition.getHolder().getType()); |
| appBuilder.addProgramClass(definition.getHolder()); |
| } |
| filteredAdd(syntheticItems, removedClasses, itemsBuilder); |
| mergedItems = itemsBuilder.build(); |
| additions = additionsBuilder.build(); |
| amendedApplication = appBuilder.build(); |
| } |
| return new CommittedItems( |
| nextSyntheticId, amendedApplication, mergedLegacyTypes, mergedItems, additions); |
| } |
| |
| private static void filteredAdd( |
| Set<DexType> input, Set<DexType> excludeSet, Builder<DexType> result) { |
| if (excludeSet.isEmpty()) { |
| result.addAll(input); |
| } else { |
| for (DexType type : input) { |
| if (!excludeSet.contains(type)) { |
| result.add(type); |
| } |
| } |
| } |
| } |
| |
| private static ImmutableMap<DexType, SyntheticReference> filteredCopy( |
| ImmutableMap<DexType, SyntheticReference> syntheticItems, Set<DexType> removedClasses) { |
| if (removedClasses.isEmpty()) { |
| return syntheticItems; |
| } |
| ImmutableMap.Builder<DexType, SyntheticReference> builder = ImmutableMap.builder(); |
| filteredAdd(syntheticItems, removedClasses, builder); |
| return builder.build(); |
| } |
| |
| private static void filteredAdd( |
| ImmutableMap<DexType, SyntheticReference> syntheticItems, |
| Set<DexType> removedClasses, |
| ImmutableMap.Builder<DexType, SyntheticReference> builder) { |
| if (removedClasses.isEmpty()) { |
| builder.putAll(syntheticItems); |
| } else { |
| syntheticItems.forEach( |
| (t, r) -> { |
| if (!removedClasses.contains(t)) { |
| builder.put(t, r); |
| } |
| }); |
| } |
| } |
| |
| private static boolean verifyClassesAreInApp( |
| DexApplication app, Collection<DexProgramClass> classes) { |
| for (DexProgramClass clazz : classes) { |
| assert app.programDefinitionFor(clazz.type) != null : "Missing synthetic: " + clazz.type; |
| } |
| return true; |
| } |
| |
| // Finalization of synthetic items. |
| |
| public Result computeFinalSynthetics(AppView<?> appView) { |
| assert !hasPendingSyntheticClasses(); |
| return new SyntheticFinalization( |
| appView.options(), legacySyntheticTypes, nonLecacySyntheticItems) |
| .computeFinalSynthetics(appView); |
| } |
| } |