blob: 4ec6686887f5ffe531ef4fbff970474bac480630 [file] [log] [blame]
// 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 com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
import com.android.tools.r8.FeatureSplit;
import com.android.tools.r8.SyntheticInfoConsumer;
import com.android.tools.r8.SyntheticInfoConsumerData;
import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.experimental.startup.StartupOrder;
import com.android.tools.r8.features.ClassToFeatureSplitMap;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.ClassResolutionResult;
import com.android.tools.r8.graph.ClasspathMethod;
import com.android.tools.r8.graph.ClasspathOrLibraryClass;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
import com.android.tools.r8.graph.MethodCollection;
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.ProgramOrClasspathDefinition;
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMerger;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.synthesis.SyntheticFinalization.Result;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.ConsumerUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringDiagnostic;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.objectweb.asm.ClassWriter;
public class SyntheticItems implements SyntheticDefinitionsProvider {
public boolean isSyntheticClassEligibleForMerging(DexProgramClass clazz) {
SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(clazz.type);
if (definition != null) {
return definition.getKind().isShareable();
}
Iterable<SyntheticReference<?, ?, ?>> references = committed.getItems(clazz.type);
Iterator<SyntheticReference<?, ?, ?>> iterator = references.iterator();
if (iterator.hasNext()) {
boolean sharable = iterator.next().getKind().isShareable();
assert Iterables.all(references, r -> sharable == r.getKind().isShareable());
return sharable;
}
return false;
}
public interface GlobalSyntheticsStrategy {
ContextsForGlobalSynthetics getStrategy();
static GlobalSyntheticsStrategy forNonSynthesizing() {
ContextsForGlobalSyntheticsInSingleOutputMode instance =
new ContextsForGlobalSyntheticsInSingleOutputMode() {
@Override
public void addGlobalContexts(
DexType globalType, Collection<? extends ProgramDefinition> contexts) {
throw new Unreachable("Unexpected attempt to add globals to non-desugaring build.");
}
};
return () -> instance;
}
static GlobalSyntheticsStrategy forSingleOutputMode() {
ContextsForGlobalSynthetics instance = new ContextsForGlobalSyntheticsInSingleOutputMode();
return () -> instance;
}
static GlobalSyntheticsStrategy forPerFileMode() {
// Allocate a new context set as the new pending set.
return ContextsForGlobalSyntheticsInPerFileMode::new;
}
}
interface ContextsForGlobalSynthetics {
boolean isEmpty();
void forEach(BiConsumer<DexType, Set<DexType>> fn);
void addGlobalContexts(DexType globalType, Collection<? extends ProgramDefinition> contexts);
}
private static class ContextsForGlobalSyntheticsInSingleOutputMode
implements ContextsForGlobalSynthetics {
@Override
public boolean isEmpty() {
return true;
}
@Override
public void forEach(BiConsumer<DexType, Set<DexType>> fn) {
// nothing to do.
}
@Override
public void addGlobalContexts(
DexType globalType, Collection<? extends ProgramDefinition> contexts) {
// contexts are ignored in single output modes.
}
}
private static class ContextsForGlobalSyntheticsInPerFileMode
implements ContextsForGlobalSynthetics {
private final ConcurrentHashMap<DexType, Set<DexType>> globalContexts =
new ConcurrentHashMap<>();
@Override
public boolean isEmpty() {
return globalContexts.isEmpty();
}
@Override
public void forEach(BiConsumer<DexType, Set<DexType>> fn) {
globalContexts.forEach(fn);
}
@Override
public void addGlobalContexts(
DexType globalType, Collection<? extends ProgramDefinition> contexts) {
Set<DexType> contextReferences =
globalContexts.computeIfAbsent(globalType, k -> ConcurrentHashMap.newKeySet());
contexts.forEach(definition -> contextReferences.add(definition.getContextType()));
}
}
enum State {
OPEN,
FINALIZED
}
/** Collection of pending items. */
private static class PendingSynthetics {
/** Thread safe collection of synthetic items not yet committed to the application. */
private final ConcurrentHashMap<DexType, SyntheticDefinition<?, ?, ?>> definitions =
new ConcurrentHashMap<>();
boolean isEmpty() {
return definitions.isEmpty();
}
boolean containsType(DexType type) {
return definitions.containsKey(type);
}
boolean containsTypeOfKind(DexType type, SyntheticKind kind) {
SyntheticDefinition<?, ?, ?> definition = definitions.get(type);
return definition != null && definition.getKind() == kind;
}
boolean verifyNotRewritten(NonIdentityGraphLens lens) {
assert definitions.keySet().equals(lens.rewriteTypes(definitions.keySet()));
return true;
}
Collection<DexProgramClass> getAllProgramClasses() {
List<DexProgramClass> allPending = new ArrayList<>(definitions.size());
for (SyntheticDefinition<?, ?, ?> item : definitions.values()) {
if (item.isProgramDefinition()) {
allPending.add(item.asProgramDefinition().getHolder());
}
}
return Collections.unmodifiableList(allPending);
}
}
private final State state;
private final SyntheticNaming naming;
private final CommittedSyntheticsCollection committed;
private final PendingSynthetics pending = new PendingSynthetics();
private final ContextsForGlobalSynthetics globalContexts;
private final GlobalSyntheticsStrategy globalSyntheticsStrategy;
public Set<DexType> collectSyntheticsFromContext(DexType context) {
Set<DexType> result = Sets.newIdentityHashSet();
committed
.getMethods()
.forEach(
(synthetic, methodReferences) -> {
methodReferences.forEach(
methodReference -> {
if (methodReference.getContext().getSynthesizingContextType() == context) {
result.add(synthetic);
}
});
});
committed
.getClasses()
.forEach(
(synthetic, classReferences) -> {
classReferences.forEach(
classReference -> {
if (classReference.getContext().getSynthesizingContextType() == context) {
result.add(synthetic);
}
});
});
return result;
}
public SyntheticNaming getNaming() {
return naming;
}
public GlobalSyntheticsStrategy getGlobalSyntheticsStrategy() {
return globalSyntheticsStrategy;
}
// Only for use from initial AppInfo/AppInfoWithClassHierarchy create functions. */
public static CommittedItems createInitialSyntheticItems(
DexApplication application, GlobalSyntheticsStrategy globalSyntheticsStrategy) {
return new CommittedItems(
State.OPEN,
application,
CommittedSyntheticsCollection.empty(application.dexItemFactory().getSyntheticNaming()),
ImmutableList.of(),
globalSyntheticsStrategy);
}
// Only for conversion to a mutable synthetic items collection.
SyntheticItems(CommittedItems commit) {
this(commit.state, commit.committed, commit.globalSyntheticsStrategy);
}
private SyntheticItems(
State state,
CommittedSyntheticsCollection committed,
GlobalSyntheticsStrategy globalSyntheticsStrategy) {
this.state = state;
this.committed = committed;
this.naming = committed.getNaming();
this.globalContexts = globalSyntheticsStrategy.getStrategy();
this.globalSyntheticsStrategy = globalSyntheticsStrategy;
}
public Map<DexType, Set<DexType>> getFinalGlobalSyntheticContexts(AppView appView) {
assert isFinalized();
DexItemFactory factory = appView.dexItemFactory();
ImmutableMap<DexType, Set<DexType>> globalContexts = committed.getGlobalContexts();
NamingLens namingLens = appView.getNamingLens();
Map<DexType, Set<DexType>> rewritten = new IdentityHashMap<>(globalContexts.size());
globalContexts.forEach(
(global, contexts) -> {
Set<DexType> old =
rewritten.put(
namingLens.lookupType(global, factory),
SetUtils.mapIdentityHashSet(contexts, c -> namingLens.lookupType(c, factory)));
assert old == null;
});
return rewritten;
}
public static void collectSyntheticInputs(AppView<?> appView) {
// Collecting synthetic items must be the very first task after application build.
SyntheticItems synthetics = appView.getSyntheticItems();
assert synthetics.committed.isEmpty();
assert synthetics.pending.isEmpty();
CommittedSyntheticsCollection.Builder builder = synthetics.committed.builder();
// TODO(b/158159959): Consider populating the input synthetics when identified.
for (DexProgramClass clazz : appView.appInfo().classes()) {
SyntheticMarker marker = SyntheticMarker.stripMarkerFromClass(clazz, appView);
if (!appView.options().intermediate && marker.getContext() != null) {
DexClass contextClass =
appView
.appInfo()
.definitionForWithoutExistenceAssert(
marker.getContext().getSynthesizingContextType());
if (contextClass == null || contextClass.isNotProgramClass()) {
appView
.reporter()
.error(
new StringDiagnostic(
"Attempt at compiling intermediate artifact without its context",
clazz.getOrigin()));
}
}
if (marker.isSyntheticMethods()) {
clazz.forEachProgramMethod(
method ->
builder.addMethod(
new SyntheticMethodDefinition(marker.getKind(), marker.getContext(), method)));
} else if (marker.isSyntheticClass()) {
builder.addClass(
new SyntheticProgramClassDefinition(marker.getKind(), marker.getContext(), clazz));
}
}
CommittedSyntheticsCollection committed = builder.collectSyntheticInputs().build();
if (committed.isEmpty()) {
return;
}
CommittedItems commit =
new CommittedItems(
synthetics.state,
appView.appInfo().app(),
committed,
ImmutableList.of(),
synthetics.globalSyntheticsStrategy);
if (appView.appInfo().hasClassHierarchy()) {
appView
.withClassHierarchy()
.setAppInfo(appView.appInfo().withClassHierarchy().rebuildWithClassHierarchy(commit));
} else {
appView
.withoutClassHierarchy()
.setAppInfo(new AppInfo(commit, appView.appInfo().getMainDexInfo()));
}
}
// Predicates and accessors.
@Override
public ClassResolutionResult definitionFor(
DexType type, Function<DexType, ClassResolutionResult> baseDefinitionFor) {
DexClass clazz = null;
SyntheticKind kind = null;
SyntheticDefinition<?, ?, ?> item = pending.definitions.get(type);
if (item != null) {
clazz = item.getHolder();
kind = item.getKind();
assert clazz.isProgramClass() == item.isProgramDefinition();
assert clazz.isClasspathClass() == item.isClasspathDefinition();
}
if (clazz != null) {
assert kind != null;
assert !baseDefinitionFor.apply(type).hasClassResolutionResult()
|| kind.isMayOverridesNonProgramType()
: "Pending synthetic definition also present in the active program: " + type;
return clazz;
}
return baseDefinitionFor.apply(type);
}
public boolean isFinalized() {
return state == State.FINALIZED;
}
public boolean hasPendingSyntheticClasses() {
return !pending.isEmpty();
}
public Collection<DexProgramClass> getPendingSyntheticClasses() {
return pending.getAllProgramClasses();
}
public boolean isCommittedSynthetic(DexType type) {
return committed.containsType(type);
}
public boolean isPendingSynthetic(DexType type) {
return pending.containsType(type);
}
public boolean isSynthetic(DexProgramClass clazz) {
return isSynthetic(clazz.type);
}
public boolean isSynthetic(DexType type) {
return committed.containsType(type) || pending.definitions.containsKey(type);
}
public boolean isEligibleForClassMerging(DexProgramClass clazz, HorizontalClassMerger.Mode mode) {
assert isSyntheticClass(clazz);
return mode.isFinal() || isSyntheticLambda(clazz);
}
private boolean isSyntheticLambda(DexProgramClass clazz) {
if (!isSynthetic(clazz)) {
return false;
}
Iterable<SyntheticReference<?, ?, ?>> references = committed.getItems(clazz.getType());
if (!Iterables.isEmpty(references)) {
assert Iterables.size(references) == 1;
return references.iterator().next().getKind() == naming.LAMBDA;
}
SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(clazz.getType());
if (definition != null) {
return definition.getKind() == naming.LAMBDA;
}
assert false;
return false;
}
public boolean isSubjectToKeepRules(DexProgramClass clazz) {
assert isSyntheticClass(clazz);
return isSyntheticInput(clazz);
}
public boolean isSyntheticClass(DexType type) {
return isSynthetic(type);
}
public boolean isSyntheticClass(DexProgramClass clazz) {
return isSyntheticClass(clazz.type);
}
public boolean isGlobalSyntheticClass(DexType type) {
SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(type);
if (definition != null) {
return definition.getKind().isGlobal();
}
return isGlobalReferences(committed.getClasses().get(type));
}
public boolean isGlobalSyntheticClass(DexProgramClass clazz) {
return isGlobalSyntheticClass(clazz.getType());
}
private static boolean isGlobalReferences(List<SyntheticProgramClassReference> references) {
if (references == null) {
return false;
}
if (references.size() == 1 && references.get(0).getKind().isGlobal()) {
return true;
}
assert verifyNoGlobals(references);
return false;
}
private static boolean verifyNoGlobals(List<SyntheticProgramClassReference> references) {
for (SyntheticProgramClassReference reference : references) {
assert !reference.getKind().isGlobal();
}
return true;
}
public boolean isSyntheticOfKind(DexType type, SyntheticKindSelector kindSelector) {
SyntheticKind kind = kindSelector.select(naming);
return pending.containsTypeOfKind(type, kind) || committed.containsTypeOfKind(type, kind);
}
public Iterable<SyntheticKind> getSyntheticKinds(DexType type) {
Iterable<SyntheticKind> references =
IterableUtils.transform(committed.getItems(type), SyntheticReference::getKind);
SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(type);
if (definition != null) {
references = Iterables.concat(references, IterableUtils.singleton(definition.getKind()));
}
return references;
}
boolean isSyntheticInput(DexProgramClass clazz) {
return committed.containsSyntheticInput(clazz.getType());
}
public FeatureSplit getContextualFeatureSplitOrDefault(DexType type, FeatureSplit defaultValue) {
assert isSyntheticClass(type);
if (isSyntheticOfKind(type, kinds -> kinds.ENUM_UNBOXING_SHARED_UTILITY_CLASS)) {
return FeatureSplit.BASE;
}
List<SynthesizingContext> contexts = getSynthesizingContexts(type);
if (contexts.isEmpty()) {
assert false
: "Expected synthetic to have at least one synthesizing context: " + type.getTypeName();
return defaultValue;
}
assert verifyAllHaveSameFeature(contexts, SynthesizingContext::getFeatureSplit);
return contexts.get(0).getFeatureSplit();
}
private static <T> boolean verifyAllHaveSameFeature(
List<T> items, Function<T, FeatureSplit> getter) {
assert !items.isEmpty();
FeatureSplit featureSplit = getter.apply(items.get(0));
for (int i = 1; i < items.size(); i++) {
assert featureSplit == getter.apply(items.get(i));
}
return true;
}
private void forEachSynthesizingContext(DexType type, Consumer<SynthesizingContext> consumer) {
for (SyntheticReference<?, ?, ?> reference : committed.getItems(type)) {
consumer.accept(reference.getContext());
}
SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(type);
if (definition != null) {
consumer.accept(definition.getContext());
}
}
private List<SynthesizingContext> getSynthesizingContexts(DexType type) {
return ListUtils.newImmutableList(builder -> forEachSynthesizingContext(type, builder));
}
public Collection<DexType> getSynthesizingContextTypes(DexType type) {
ImmutableList.Builder<DexType> builder = ImmutableList.builder();
forEachSynthesizingContext(
type, synthesizingContext -> builder.add(synthesizingContext.getSynthesizingContextType()));
return builder.build();
}
// TODO(b/180091213): Implement this and remove client provided the oracle.
public Set<DexReference> getSynthesizingContextReferences(
DexProgramClass clazz, SynthesizingContextOracle oracle) {
assert isSyntheticClass(clazz);
return oracle.getSynthesizingContexts(clazz);
}
public Collection<Origin> getSynthesizingOrigin(DexType type) {
if (!isSynthetic(type)) {
return Collections.emptyList();
}
ImmutableList.Builder<Origin> builder = ImmutableList.builder();
forEachSynthesizingContext(
type,
context -> {
builder.add(context.getInputContextOrigin());
});
return builder.build();
}
public DexType getSynthesizingInputContext(DexType syntheticType, InternalOptions options) {
if (!isSynthetic(syntheticType)) {
return null;
}
Box<DexType> uniqueInputContext = new Box<>(null);
forEachSynthesizingContext(
syntheticType,
context -> {
assert uniqueInputContext.get() == null;
uniqueInputContext.set(context.getSynthesizingInputContext(options.intermediate));
});
return uniqueInputContext.get();
}
public interface SynthesizingContextOracle {
Set<DexReference> getSynthesizingContexts(DexProgramClass clazz);
}
public boolean isSyntheticMethodThatShouldNotBeDoubleProcessed(ProgramMethod method) {
for (SyntheticMethodReference reference :
committed.getMethods().getOrDefault(method.getHolderType(), Collections.emptyList())) {
if (reference.getKind().equals(naming.STATIC_INTERFACE_CALL)) {
return true;
}
}
SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(method.getHolderType());
if (definition != null) {
return definition.getKind().equals(naming.STATIC_INTERFACE_CALL);
}
return false;
}
// The compiler should not inspect the kind of a synthetic, so this provided only as a assertion
// utility.
public boolean verifySyntheticLambdaProperty(
DexProgramClass clazz,
Predicate<DexProgramClass> ifIsLambda,
Predicate<DexProgramClass> ifNotLambda) {
Iterable<SyntheticReference<?, ?, ?>> references = committed.getItems(clazz.getType());
SyntheticDefinition<?, ?, ?> definition = pending.definitions.get(clazz.getType());
if (definition != null) {
references = Iterables.concat(references, IterableUtils.singleton(definition.toReference()));
}
if (Iterables.any(references, reference -> reference.getKind().equals(naming.LAMBDA))) {
assert ifIsLambda.test(clazz);
} else {
assert ifNotLambda.test(clazz);
}
return true;
}
private SynthesizingContext getSynthesizingContext(
ProgramDefinition context, AppView<?> appView) {
InternalOptions options = appView.options();
if (appView.hasClassHierarchy()) {
AppInfoWithClassHierarchy appInfo = appView.appInfoWithClassHierarchy();
return getSynthesizingContext(
context, appInfo.getClassToFeatureSplitMap(), options, appView.getStartupOrder());
}
return getSynthesizingContext(
context,
ClassToFeatureSplitMap.createEmptyClassToFeatureSplitMap(),
options,
StartupOrder.empty());
}
/** Used to find the synthesizing context for a new synthetic that is about to be created. */
private SynthesizingContext getSynthesizingContext(
ProgramDefinition context,
ClassToFeatureSplitMap featureSplits,
InternalOptions options,
StartupOrder startupOrder) {
DexType contextType = context.getContextType();
SyntheticDefinition<?, ?, ?> existingDefinition = pending.definitions.get(contextType);
if (existingDefinition != null) {
return existingDefinition.getContext();
}
Iterable<SyntheticReference<?, ?, ?>> existingReferences = committed.getItems(contextType);
if (!Iterables.isEmpty(existingReferences)) {
// Use a deterministic synthesizing context from the set of contexts.
return IterableUtils.min(
existingReferences,
(existingReference, other) ->
existingReference.getReference().compareTo(other.getReference()))
.getContext();
}
// This context is not nested in an existing synthetic context so create a new "leaf" context.
FeatureSplit featureSplit = featureSplits.getFeatureSplit(context, options, startupOrder, this);
return SynthesizingContext.fromNonSyntheticInputContext(context, featureSplit);
}
// Addition and creation of synthetic items.
private DexProgramClass internalLookupProgramClass(
DexType type, SyntheticKind kind, AppView<?> appView) {
DexClass clazz = appView.definitionFor(type);
if (clazz == null) {
return null;
}
if (clazz.isProgramClass()) {
return clazz.asProgramClass();
}
if (clazz.isLibraryClass() && kind.isGlobal()) {
return null;
}
errorOnInvalidSyntheticEnsure(clazz, "program", appView);
return null;
}
private DexProgramClass internalEnsureFixedProgramClass(
SyntheticKind kind,
Consumer<SyntheticProgramClassBuilder> classConsumer,
Consumer<DexProgramClass> onCreationConsumer,
SynthesizingContext outerContext,
AppView<?> appView) {
Function<SynthesizingContext, DexType> contextToType =
c -> SyntheticNaming.createFixedType(kind, c, appView.dexItemFactory());
DexType type = contextToType.apply(outerContext);
// Fast path is that the synthetic is already present. If so it must be a program class.
DexProgramClass clazz = internalLookupProgramClass(type, kind, appView);
if (clazz != null) {
return clazz;
}
// Slow path creates the class using the context to make it thread safe.
synchronized (type) {
// Recheck if it is present now the lock is held.
clazz = internalLookupProgramClass(type, kind, appView);
if (clazz != null) {
return clazz;
}
assert !isSyntheticClass(type);
clazz =
internalCreateProgramClass(
kind,
syntheticProgramClassBuilder -> {
syntheticProgramClassBuilder.setUseSortedMethodBacking(true);
classConsumer.accept(syntheticProgramClassBuilder);
},
outerContext,
type,
contextToType,
appView);
onCreationConsumer.accept(clazz);
return clazz;
}
}
private DexProgramClass internalCreateProgramClass(
SyntheticKind kind,
Consumer<SyntheticProgramClassBuilder> fn,
SynthesizingContext outerContext,
DexType type,
Function<SynthesizingContext, DexType> contextToType,
AppView<?> appView) {
registerSyntheticTypeRewriting(outerContext, contextToType, appView, type);
SyntheticProgramClassBuilder classBuilder =
new SyntheticProgramClassBuilder(type, kind, outerContext, appView.dexItemFactory());
fn.accept(classBuilder);
DexProgramClass clazz = classBuilder.build();
addPendingDefinition(new SyntheticProgramClassDefinition(kind, outerContext, clazz));
return clazz;
}
private void registerSyntheticTypeRewriting(
SynthesizingContext outerContext,
Function<SynthesizingContext, DexType> contextToType,
AppView<?> appView,
DexType type) {
DexType rewrittenContextType =
appView.typeRewriter.rewrittenContextType(outerContext.getSynthesizingContextType());
if (rewrittenContextType == null) {
return;
}
SynthesizingContext synthesizingContext = SynthesizingContext.fromType(rewrittenContextType);
DexType rewrittenType = contextToType.apply(synthesizingContext);
appView.typeRewriter.rewriteType(type, rewrittenType);
}
public DexProgramClass createClass(
SyntheticKindSelector kindSelector, UniqueContext context, AppView<?> appView) {
return createClass(kindSelector, context, appView, ConsumerUtils.emptyConsumer());
}
public DexProgramClass createClass(
SyntheticKindSelector kindSelector,
UniqueContext context,
AppView<?> appView,
Consumer<SyntheticProgramClassBuilder> fn) {
SyntheticKind kind = kindSelector.select(naming);
// Obtain the outer synthesizing context in the case the context itself is synthetic.
// This is to ensure a flat input-type -> synthetic-item mapping.
SynthesizingContext outerContext = getSynthesizingContext(context.getClassContext(), appView);
Function<SynthesizingContext, DexType> contextToType =
c -> SyntheticNaming.createInternalType(kind, c, context.getSyntheticSuffix(), appView);
return internalCreateProgramClass(
kind, fn, outerContext, contextToType.apply(outerContext), contextToType, appView);
}
// TODO(b/172194101): Make this take a unique context.
public DexProgramClass createFixedClass(
SyntheticKindSelector kindSelector,
DexProgramClass context,
AppView<?> appView,
Consumer<SyntheticProgramClassBuilder> fn) {
SyntheticKind kind = kindSelector.select(naming);
SynthesizingContext outerContext = internalGetOuterContext(context, appView);
Function<SynthesizingContext, DexType> contextToType =
c -> SyntheticNaming.createFixedType(kind, c, appView.dexItemFactory());
return internalCreateProgramClass(
kind, fn, outerContext, contextToType.apply(outerContext), contextToType, appView);
}
public DexProgramClass getExistingFixedClass(
SyntheticKindSelector kindSelector, DexClass context, AppView<?> appView) {
SyntheticKind kind = kindSelector.select(naming);
assert kind.isFixedSuffixSynthetic();
SynthesizingContext outerContext = internalGetOuterContext(context, appView);
DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory());
DexClass clazz = appView.definitionFor(type);
assert clazz != null : "Missing existing fixed class " + type;
assert isSyntheticClass(type);
assert clazz.isProgramClass();
return clazz.asProgramClass();
}
// Obtain the outer synthesizing context in the case the context itself is synthetic.
// This is to ensure a flat input-type -> synthetic-item mapping.
private SynthesizingContext internalGetOuterContext(DexClass context, AppView<?> appView) {
return context.isProgramClass()
? getSynthesizingContext(context.asProgramClass(), appView)
: SynthesizingContext.fromNonSyntheticInputContext(context.asClasspathOrLibraryClass());
}
@FunctionalInterface
public interface SyntheticKindSelector {
SyntheticKind select(SyntheticNaming naming);
}
/**
* Ensure that a fixed synthetic class exists.
*
* <p>This method is thread safe and will synchronize based on the context of the fixed synthetic.
*/
public DexProgramClass ensureFixedClass(
SyntheticKindSelector kindSelector,
DexClass context,
AppView<?> appView,
Consumer<SyntheticProgramClassBuilder> fn,
Consumer<DexProgramClass> onCreationConsumer) {
SyntheticKind kind = kindSelector.select(naming);
assert kind.isFixedSuffixSynthetic();
SynthesizingContext outerContext = internalGetOuterContext(context, appView);
return internalEnsureFixedProgramClass(kind, fn, onCreationConsumer, outerContext, appView);
}
public ProgramMethod ensureFixedClassMethod(
DexString name,
DexProto proto,
SyntheticKindSelector kindSelector,
ProgramOrClasspathDefinition context,
AppView<?> appView,
Consumer<SyntheticProgramClassBuilder> buildClassCallback,
Consumer<SyntheticMethodBuilder> buildMethodCallback) {
return ensureFixedClassMethod(
name,
proto,
kindSelector,
context,
appView,
buildClassCallback,
buildMethodCallback,
emptyConsumer());
}
public ProgramMethod ensureFixedClassMethod(
DexString name,
DexProto proto,
SyntheticKindSelector kindSelector,
ProgramOrClasspathDefinition context,
AppView<?> appView,
Consumer<SyntheticProgramClassBuilder> buildClassCallback,
Consumer<SyntheticMethodBuilder> buildMethodCallback,
Consumer<ProgramMethod> newMethodCallback) {
SyntheticKind kind = kindSelector.select(naming);
DexProgramClass clazz =
ensureFixedClass(
kindSelector, context.getContextClass(), appView, buildClassCallback, emptyConsumer());
DexMethod methodReference = appView.dexItemFactory().createMethod(clazz.getType(), proto, name);
DexEncodedMethod methodDefinition =
internalEnsureMethod(
methodReference, clazz, kind, appView, buildMethodCallback, newMethodCallback);
return new ProgramMethod(clazz, methodDefinition);
}
private void errorOnInvalidSyntheticEnsure(DexClass dexClass, String kind, AppView<?> appView) {
String classKind =
dexClass.isProgramClass()
? "program"
: dexClass.isClasspathClass() ? "classpath" : "library";
throw appView
.reporter()
.fatalError(
"Cannot ensure "
+ dexClass.type
+ " as a synthetic "
+ kind
+ " class, because it is already a "
+ classKind
+ " class.");
}
private DexClasspathClass internalEnsureFixedClasspathClass(
SyntheticKind kind,
Consumer<SyntheticClasspathClassBuilder> classConsumer,
Consumer<DexClasspathClass> onCreationConsumer,
SynthesizingContext outerContext,
AppView<?> appView) {
Function<SynthesizingContext, DexType> contextToType =
(c) -> SyntheticNaming.createFixedType(kind, c, appView.dexItemFactory());
DexType type = contextToType.apply(outerContext);
synchronized (type) {
DexClass clazz = appView.definitionFor(type);
if (clazz != null) {
if (!clazz.isClasspathClass()) {
errorOnInvalidSyntheticEnsure(clazz, "classpath", appView);
}
return clazz.asClasspathClass();
}
registerSyntheticTypeRewriting(outerContext, contextToType, appView, type);
SyntheticClasspathClassBuilder classBuilder =
new SyntheticClasspathClassBuilder(type, kind, outerContext, appView.dexItemFactory());
classConsumer.accept(classBuilder);
DexClasspathClass definition = classBuilder.build();
addPendingDefinition(new SyntheticClasspathClassDefinition(kind, outerContext, definition));
onCreationConsumer.accept(definition);
return definition;
}
}
public DexClasspathClass ensureFixedClasspathClassFromType(
SyntheticKindSelector kindSelector,
DexType contextType,
AppView<?> appView,
Consumer<SyntheticClasspathClassBuilder> classConsumer,
Consumer<DexClasspathClass> onCreationConsumer) {
SyntheticKind kind = kindSelector.select(naming);
SynthesizingContext outerContext = SynthesizingContext.fromType(contextType);
return internalEnsureFixedClasspathClass(
kind, classConsumer, onCreationConsumer, outerContext, appView);
}
public DexClasspathClass ensureFixedClasspathClass(
SyntheticKindSelector kindSelector,
ClasspathOrLibraryClass context,
AppView<?> appView,
Consumer<SyntheticClasspathClassBuilder> classConsumer,
Consumer<DexClasspathClass> onCreationConsumer) {
// Obtain the outer synthesizing context in the case the context itself is synthetic.
// This is to ensure a flat input-type -> synthetic-item mapping.
SynthesizingContext outerContext = SynthesizingContext.fromNonSyntheticInputContext(context);
return internalEnsureFixedClasspathClass(
kindSelector.select(naming), classConsumer, onCreationConsumer, outerContext, appView);
}
public ClasspathMethod ensureFixedClasspathMethodFromType(
DexString methodName,
DexProto methodProto,
SyntheticKindSelector kindSelector,
DexType contextType,
AppView<?> appView,
Consumer<SyntheticClasspathClassBuilder> classConsumer,
Consumer<DexClasspathClass> onCreationConsumer,
Consumer<SyntheticMethodBuilder> buildMethodCallback) {
DexClasspathClass clazz =
ensureFixedClasspathClassFromType(
kindSelector, contextType, appView, classConsumer, onCreationConsumer);
return internalEnsureFixedClasspathMethod(
methodName, methodProto, kindSelector.select(naming), appView, buildMethodCallback, clazz);
}
public ClasspathMethod ensureFixedClasspathClassMethod(
DexString methodName,
DexProto methodProto,
SyntheticKindSelector kindSelector,
ClasspathOrLibraryClass context,
AppView<?> appView,
Consumer<SyntheticClasspathClassBuilder> buildClassCallback,
Consumer<DexClasspathClass> onClassCreationCallback,
Consumer<SyntheticMethodBuilder> buildMethodCallback) {
DexClasspathClass clazz =
ensureFixedClasspathClass(
kindSelector, context, appView, buildClassCallback, onClassCreationCallback);
return internalEnsureFixedClasspathMethod(
methodName, methodProto, kindSelector.select(naming), appView, buildMethodCallback, clazz);
}
private ClasspathMethod internalEnsureFixedClasspathMethod(
DexString methodName,
DexProto methodProto,
SyntheticKind kind,
AppView<?> appView,
Consumer<SyntheticMethodBuilder> buildMethodCallback,
DexClasspathClass clazz) {
DexMethod methodReference =
appView.dexItemFactory().createMethod(clazz.getType(), methodProto, methodName);
DexEncodedMethod methodDefinition =
internalEnsureMethod(
methodReference,
clazz,
kind,
appView,
methodBuilder -> {
// For class path classes we always disable api level checks because we never trace
// the code and it cannot be inlined.
buildMethodCallback.accept(methodBuilder.disableAndroidApiLevelCheck());
},
emptyConsumer());
return new ClasspathMethod(clazz, methodDefinition);
}
@SuppressWarnings("unchecked")
private <T extends DexClassAndMethod> DexEncodedMethod internalEnsureMethod(
DexMethod methodReference,
DexClass clazz,
SyntheticKind kind,
AppView<?> appView,
Consumer<SyntheticMethodBuilder> buildMethodCallback,
Consumer<T> newMethodCallback) {
MethodCollection methodCollection = clazz.getMethodCollection();
synchronized (methodCollection) {
DexEncodedMethod methodDefinition = methodCollection.getMethod(methodReference);
if (methodDefinition != null) {
return methodDefinition;
}
SyntheticMethodBuilder builder =
new SyntheticMethodBuilder(appView.dexItemFactory(), clazz.getType(), kind);
builder.setName(methodReference.getName());
builder.setProto(methodReference.getProto());
buildMethodCallback.accept(builder);
methodDefinition = builder.build();
methodCollection.addMethod(methodDefinition);
newMethodCallback.accept((T) DexClassAndMethod.create(clazz, methodDefinition));
return methodDefinition;
}
}
public DexProgramClass ensureGlobalClass(
Supplier<MissingGlobalSyntheticsConsumerDiagnostic> diagnosticSupplier,
SyntheticKindSelector kindSelector,
DexType globalType,
Collection<? extends ProgramDefinition> contexts,
AppView<?> appView,
Consumer<SyntheticProgramClassBuilder> fn,
Consumer<DexProgramClass> onCreationConsumer) {
SyntheticKind kind = kindSelector.select(naming);
assert kind.isGlobal();
assert !contexts.isEmpty();
if (appView.options().intermediate && !appView.options().hasGlobalSyntheticsConsumer()) {
appView.reporter().fatalError(diagnosticSupplier.get());
}
// A global type is its own context.
SynthesizingContext outerContext = SynthesizingContext.fromType(globalType);
DexProgramClass globalSynthetic =
internalEnsureFixedProgramClass(kind, fn, onCreationConsumer, outerContext, appView);
addGlobalContexts(globalSynthetic.getType(), contexts);
return globalSynthetic;
}
/** Create a single synthetic method item. */
public ProgramMethod createMethod(
SyntheticKindSelector kindSelector,
UniqueContext context,
AppView<?> appView,
Consumer<SyntheticMethodBuilder> fn) {
return createMethod(
kindSelector, context.getClassContext(), appView, fn, context::getSyntheticSuffix);
}
private ProgramMethod createMethod(
SyntheticKindSelector kindSelector,
ProgramDefinition context,
AppView<?> appView,
Consumer<SyntheticMethodBuilder> fn,
Supplier<String> syntheticIdSupplier) {
assert !isFinalized();
// Obtain the outer synthesizing context in the case the context itself is synthetic.
// This is to ensure a flat input-type -> synthetic-item mapping.
SynthesizingContext outerContext = getSynthesizingContext(context, appView);
SyntheticKind kind = kindSelector.select(naming);
DexType type =
SyntheticNaming.createInternalType(kind, outerContext, syntheticIdSupplier.get(), appView);
SyntheticProgramClassBuilder classBuilder =
new SyntheticProgramClassBuilder(type, kind, outerContext, appView.dexItemFactory());
DexProgramClass clazz =
classBuilder
.addMethod(fn.andThen(m -> m.setName(SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_NAME)))
.build();
ProgramMethod method = new ProgramMethod(clazz, clazz.methods().iterator().next());
addPendingDefinition(new SyntheticMethodDefinition(kind, outerContext, method));
return method;
}
private void addPendingDefinition(SyntheticDefinition<?, ?, ?> definition) {
pending.definitions.put(definition.getHolder().getType(), definition);
}
private void addGlobalContexts(
DexType globalType, Collection<? extends ProgramDefinition> contexts) {
globalContexts.addGlobalContexts(globalType, contexts);
}
// Commit of the synthetic items to a new fully populated application.
public CommittedItems commit(DexApplication application) {
return commitPrunedItems(PrunedItems.empty(application));
}
public CommittedItems commitPrunedItems(PrunedItems prunedItems) {
return commit(prunedItems, pending, globalContexts, committed, state, globalSyntheticsStrategy);
}
public CommittedItems commitRewrittenWithLens(
DexApplication application, NonIdentityGraphLens lens) {
assert pending.verifyNotRewritten(lens);
return commit(
PrunedItems.empty(application),
pending,
globalContexts,
committed.rewriteWithLens(lens),
state,
globalSyntheticsStrategy);
}
private static CommittedItems commit(
PrunedItems prunedItems,
PendingSynthetics pending,
ContextsForGlobalSynthetics globalContexts,
CommittedSyntheticsCollection committed,
State state,
GlobalSyntheticsStrategy globalSyntheticsStrategy) {
DexApplication application = prunedItems.getPrunedApp();
Set<DexType> removedClasses = prunedItems.getNoLongerSyntheticItems();
CommittedSyntheticsCollection.Builder builder = committed.builder();
// Compute the synthetic additions and add them to the application.
ImmutableList<DexType> committedProgramTypes;
DexApplication amendedApplication;
if (pending.definitions.isEmpty()) {
committedProgramTypes = ImmutableList.of();
amendedApplication = application;
} else {
DexApplication.Builder<?> appBuilder = application.builder();
ImmutableList.Builder<DexType> committedProgramTypesBuilder = ImmutableList.builder();
for (SyntheticDefinition<?, ?, ?> definition : pending.definitions.values()) {
if (!removedClasses.contains(definition.getHolder().getType())) {
if (definition.isProgramDefinition()) {
committedProgramTypesBuilder.add(definition.getHolder().getType());
if (definition.getKind().isMayOverridesNonProgramType()) {
appBuilder.addProgramClassPotentiallyOverridingNonProgramClass(
definition.asProgramDefinition().getHolder());
} else {
appBuilder.addProgramClass(definition.asProgramDefinition().getHolder());
}
} else if (appBuilder.isDirect()) {
assert definition.isClasspathDefinition();
appBuilder.asDirect().addClasspathClass(definition.asClasspathDefinition().getHolder());
}
builder.addItem(definition);
}
}
builder.addGlobalContexts(globalContexts);
committedProgramTypes = committedProgramTypesBuilder.build();
amendedApplication = appBuilder.build();
}
return new CommittedItems(
state,
amendedApplication,
builder.build().pruneItems(prunedItems),
committedProgramTypes,
globalSyntheticsStrategy);
}
public void writeAttributeIfIntermediateSyntheticClass(
ClassWriter writer, DexProgramClass clazz, AppView<?> appView) {
if (!appView.options().intermediate || !appView.options().isGeneratingClassFiles()) {
return;
}
Iterator<SyntheticReference<?, ?, ?>> it = committed.getItems(clazz.getType()).iterator();
if (it.hasNext()) {
SyntheticKind kind = it.next().getKind();
// When compiling intermediates there should not be any mergings as they may invalidate the
// single kind of a synthetic which is required for marking synthetics. This check could be
// relaxed to ensure that all kinds are equivalent if merging is possible.
assert !it.hasNext();
SyntheticMarker.writeMarkerAttribute(writer, kind, appView.getSyntheticItems());
}
}
// Finalization of synthetic items.
Result computeFinalSynthetics(AppView<?> appView, Timing timing) {
assert !hasPendingSyntheticClasses();
return new SyntheticFinalization(appView.options(), this, committed)
.computeFinalSynthetics(appView, timing);
}
public void reportSyntheticsInformation(SyntheticInfoConsumer consumer) {
assert isFinalized();
Map<DexType, DexType> seen = new IdentityHashMap<>();
committed.forEachItem(
ref -> {
DexType holder = ref.getHolder();
DexType context = ref.getContext().getSynthesizingContextType();
DexType old = seen.put(holder, context);
assert old == null || old == context;
if (old == null) {
consumer.acceptSyntheticInfo(new SyntheticInfoConsumerDataImpl(holder, context));
}
});
}
private static class SyntheticInfoConsumerDataImpl implements SyntheticInfoConsumerData {
private final DexType holder;
private final DexType context;
public SyntheticInfoConsumerDataImpl(DexType holder, DexType context) {
this.holder = holder;
this.context = context;
}
@Override
public ClassReference getSyntheticClass() {
return Reference.classFromDescriptor(holder.toDescriptorString());
}
@Override
public ClassReference getSynthesizingContextClass() {
return Reference.classFromDescriptor(context.toDescriptorString());
}
}
}