blob: 2aad1c9503c9ae47e9112075f647d20a8094d00f [file] [log] [blame]
// Copyright (c) 2017, 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.graph.lens;
import static com.android.tools.r8.utils.collections.ThrowingSet.isThrowingSet;
import static com.google.common.base.Predicates.alwaysFalse;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexDefinitionSupplier;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
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.ProgramMethod;
import com.android.tools.r8.graph.proto.RewrittenPrototypeDescription;
import com.android.tools.r8.horizontalclassmerging.HorizontalClassMergerGraphLens;
import com.android.tools.r8.ir.code.InvokeType;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
import com.android.tools.r8.ir.optimize.CustomLensCodeRewriter;
import com.android.tools.r8.ir.optimize.enums.EnumUnboxingLens;
import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
import com.android.tools.r8.optimize.MemberRebindingLens;
import com.android.tools.r8.utils.CollectionUtils;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
import com.android.tools.r8.utils.collections.MutableBidirectionalManyToOneRepresentativeMap;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.android.tools.r8.verticalclassmerging.VerticalClassMergerGraphLens;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Predicate;
/**
* A GraphLens implements a virtual view on top of the graph, used to delay global rewrites until
* later IR processing stages.
*
* <p>Valid remappings are limited to the following operations:
*
* <ul>
* <li>Mapping a classes type to one of the super/subtypes.
* <li>Renaming private methods/fields.
* <li>Moving methods/fields to a super/subclass.
* <li>Replacing method/field references by the same method/field on a super/subtype
* <li>Moved methods might require changed invocation type at the call site
* </ul>
*
* Note that the latter two have to take visibility into account.
*/
public abstract class GraphLens {
public abstract static class Builder {
protected final MutableBidirectionalManyToOneRepresentativeMap<DexMethod, DexMethod> methodMap =
BidirectionalManyToOneRepresentativeHashMap.newIdentityHashMap();
protected Builder() {}
@SuppressWarnings("ReferenceEquality")
public void move(DexMethod from, DexMethod to) {
if (from == to) {
return;
}
methodMap.put(from, to);
}
public abstract GraphLens build(AppView<?> appView);
}
/**
* Intentionally package private. All graph lenses except for {@link IdentityGraphLens} should
* inherit from {@link NonIdentityGraphLens}.
*/
GraphLens() {}
public boolean isSyntheticFinalizationGraphLens() {
return false;
}
@Deprecated
public final DexType getOriginalType(DexType type) {
GraphLens appliedLens = getIdentityLens();
return getOriginalType(type, appliedLens);
}
public final DexType getOriginalType(DexType type, GraphLens appliedLens) {
return getOriginalReference(type, appliedLens, NonIdentityGraphLens::getPreviousClassType);
}
public abstract Iterable<DexType> getOriginalTypes(DexType type);
@Deprecated
public final DexField getOriginalFieldSignature(DexField field) {
GraphLens appliedLens = getIdentityLens();
return getOriginalFieldSignature(field, appliedLens);
}
public final DexField getOriginalFieldSignature(DexField field, GraphLens appliedLens) {
return getOriginalReference(
field, appliedLens, NonIdentityGraphLens::getPreviousFieldSignature);
}
@Deprecated
public final DexMethod getOriginalMethodSignature(DexMethod method) {
GraphLens appliedLens = getIdentityLens();
return getOriginalMethodSignature(method, appliedLens);
}
public final DexMethod getOriginalMethodSignature(DexMethod method, GraphLens appliedLens) {
return getOriginalReference(
method, appliedLens, NonIdentityGraphLens::getPreviousMethodSignature);
}
public final DexMethod getOriginalMethodSignatureForMapping(DexMethod method) {
GraphLens appliedLens = getIdentityLens();
return getOriginalReference(
method, appliedLens, NonIdentityGraphLens::getPreviousMethodSignatureForMapping);
}
private <T extends DexReference> T getOriginalReference(
T reference, GraphLens appliedLens, BiFunction<NonIdentityGraphLens, T, T> previousFn) {
GraphLens current = this;
T original = reference;
while (current.isNonIdentityLens() && current != appliedLens) {
NonIdentityGraphLens nonIdentityLens = current.asNonIdentityLens();
original = previousFn.apply(nonIdentityLens, original);
current = nonIdentityLens.getPrevious();
}
return original;
}
public final DexReference getRenamedReference(
DexReference originalReference, GraphLens codeLens) {
return originalReference.apply(
clazz -> lookupType(clazz, codeLens),
field -> getRenamedFieldSignature(field, codeLens),
method -> getRenamedMethodSignature(method, codeLens));
}
@Deprecated
public final DexField getRenamedFieldSignature(DexField originalField) {
GraphLens appliedLens = getIdentityLens();
return getRenamedFieldSignature(originalField, appliedLens);
}
public final DexField getRenamedFieldSignature(DexField originalField, GraphLens appliedLens) {
return getRenamedReference(
originalField, appliedLens, NonIdentityGraphLens::getNextFieldSignature);
}
public final DexMember<?, ?> getRenamedMemberSignature(
DexMember<?, ?> originalMember, GraphLens codeLens) {
return originalMember.isDexField()
? getRenamedFieldSignature(originalMember.asDexField(), codeLens)
: getRenamedMethodSignature(originalMember.asDexMethod(), codeLens);
}
@Deprecated
public final DexMethod getRenamedMethodSignature(DexMethod originalMethod) {
GraphLens appliedLens = getIdentityLens();
return getRenamedMethodSignature(originalMethod, appliedLens);
}
public final DexMethod getRenamedMethodSignature(DexMethod method, GraphLens appliedLens) {
return getRenamedReference(method, appliedLens, NonIdentityGraphLens::getNextMethodSignature);
}
private <T extends DexReference> T getRenamedReference(
T reference, GraphLens appliedLens, BiFunction<NonIdentityGraphLens, T, T> nextFn) {
return getRenamedReference(reference, appliedLens, nextFn, alwaysFalse());
}
private <T extends DexReference> T getRenamedReference(
T reference,
GraphLens appliedLens,
BiFunction<NonIdentityGraphLens, T, T> nextFn,
Predicate<T> stoppingCriterion) {
GraphLens current = this;
Deque<NonIdentityGraphLens> lenses = new ArrayDeque<>();
while (current.isNonIdentityLens() && current != appliedLens) {
NonIdentityGraphLens nonIdentityLens = current.asNonIdentityLens();
lenses.addLast(nonIdentityLens);
current = nonIdentityLens.getPrevious();
}
while (!lenses.isEmpty()) {
NonIdentityGraphLens lens = lenses.removeLast();
reference = nextFn.apply(lens, reference);
if (stoppingCriterion.test(reference)) {
break;
}
}
return reference;
}
@SuppressWarnings("ReferenceEquality")
// Predicate indicating if a rewritten reference is a simple renaming, meaning the move from one
// reference to another is simply either just a renaming or/also renaming of the references. In
// other words, the content of the definition, including the definition of all of its members is
// the same modulo the renaming.
public <T extends DexReference> boolean isSimpleRenaming(T from, T to) {
assert from != to;
return false;
}
public abstract String lookupPackageName(String pkg);
public final DexType lookupClassType(DexType type, GraphLens appliedLens) {
return getRenamedReference(
type, appliedLens, NonIdentityGraphLens::getNextClassType, DexType::isPrimitiveType);
}
@Deprecated
public DexType lookupType(DexType type) {
GraphLens appliedLens = getIdentityLens();
return lookupType(type, appliedLens);
}
public abstract DexType lookupType(DexType type, GraphLens appliedLens);
@Deprecated
public final MethodLookupResult lookupInvokeDirect(DexMethod method, ProgramMethod context) {
return lookupMethod(method, context.getReference(), InvokeType.DIRECT);
}
public final MethodLookupResult lookupInvokeDirect(
DexMethod method, ProgramMethod context, GraphLens codeLens) {
return lookupMethod(method, context.getReference(), InvokeType.DIRECT, codeLens);
}
@Deprecated
public final MethodLookupResult lookupInvokeInterface(DexMethod method, ProgramMethod context) {
return lookupMethod(method, context.getReference(), InvokeType.INTERFACE);
}
public final MethodLookupResult lookupInvokeInterface(
DexMethod method, ProgramMethod context, GraphLens codeLens) {
return lookupMethod(method, context.getReference(), InvokeType.INTERFACE, codeLens);
}
@Deprecated
public final MethodLookupResult lookupInvokeStatic(DexMethod method, ProgramMethod context) {
return lookupMethod(method, context.getReference(), InvokeType.STATIC);
}
public final MethodLookupResult lookupInvokeStatic(
DexMethod method, ProgramMethod context, GraphLens codeLens) {
return lookupMethod(method, context.getReference(), InvokeType.STATIC, codeLens);
}
@Deprecated
public final MethodLookupResult lookupInvokeSuper(DexMethod method, ProgramMethod context) {
return lookupMethod(method, context.getReference(), InvokeType.SUPER);
}
public final MethodLookupResult lookupInvokeSuper(
DexMethod method, ProgramMethod context, GraphLens codeLens) {
return lookupMethod(method, context.getReference(), InvokeType.SUPER, codeLens);
}
@Deprecated
public final MethodLookupResult lookupInvokeVirtual(DexMethod method, ProgramMethod context) {
return lookupMethod(method, context.getReference(), InvokeType.VIRTUAL);
}
public final MethodLookupResult lookupInvokeVirtual(
DexMethod method, ProgramMethod context, GraphLens codeLens) {
return lookupMethod(method, context.getReference(), InvokeType.VIRTUAL, codeLens);
}
@Deprecated
@SuppressWarnings("InlineMeSuggester")
public final MethodLookupResult lookupMethod(
DexMethod method, DexMethod context, InvokeType type) {
return lookupMethod(method, context, type, null);
}
/**
* Lookup a rebound or non-rebound method reference using the current graph lens.
*
* @param codeLens Specifies the graph lens which has already been applied to the code object. The
* lookup procedure will not recurse beyond this graph lens to ensure that each mapping is
* applied at most once.
* <p>Note: since the compiler currently inserts {@link ClearCodeRewritingGraphLens} it is
* generally valid to pass null for the {@param codeLens}. The removal of {@link
* ClearCodeRewritingGraphLens} is tracked by b/202368283. After this is removed, the compiler
* should generally use the result of calling {@link AppView#codeLens()}.
*/
public abstract MethodLookupResult lookupMethod(
DexMethod method, DexMethod context, InvokeType type, GraphLens codeLens);
protected abstract MethodLookupResult internalLookupMethod(
DexMethod reference,
DexMethod context,
InvokeType type,
GraphLens codeLens,
LookupMethodContinuation continuation);
public interface LookupMethodContinuation {
MethodLookupResult lookupMethod(MethodLookupResult previous);
}
public final RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
DexMethod method) {
return lookupPrototypeChangesForMethodDefinition(method, null);
}
public abstract RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
DexMethod method, GraphLens codeLens);
@Deprecated
@SuppressWarnings("InlineMeSuggester")
public final DexField lookupField(DexField field) {
return lookupField(field, null);
}
/** Lookup a rebound or non-rebound field reference using the current graph lens. */
public DexField lookupField(DexField field, GraphLens codeLens) {
// Lookup the field using the graph lens and return the (non-rebound) reference from the lookup
// result.
return lookupFieldResult(field, codeLens).getReference();
}
/** Lookup a rebound or non-rebound field reference using the current graph lens. */
public final FieldLookupResult lookupFieldResult(DexField field) {
// Lookup the field using the graph lens and return the lookup result.
return lookupFieldResult(field, null);
}
/** Lookup a rebound or non-rebound field reference using the current graph lens. */
public final FieldLookupResult lookupFieldResult(DexField field, GraphLens codeLens) {
// Lookup the field using the graph lens and return the lookup result.
return internalLookupField(field, codeLens, x -> x);
}
protected abstract FieldLookupResult internalLookupField(
DexField reference, GraphLens codeLens, LookupFieldContinuation continuation);
interface LookupFieldContinuation {
FieldLookupResult lookupField(FieldLookupResult previous);
}
// The method lookupMethod() maps a pair INVOKE=(method signature, invoke type) to a new pair
// INVOKE'=(method signature', invoke type'). This mapping can be context sensitive, meaning that
// the result INVOKE' depends on where the invocation INVOKE is in the program. This is, for
// example, used by the vertical class merger to translate invoke-super instructions that hit
// a method in the direct super class to invoke-direct instructions after class merging.
//
// This method can be used to determine if a graph lens is context sensitive. If a graph lens
// is context insensitive, it is safe to invoke lookupMethod() without a context (or to pass null
// as context). Trying to invoke a context sensitive graph lens without a context will lead to
// an assertion error.
public abstract boolean isContextFreeForMethods(GraphLens codeLens);
public boolean verifyIsContextFreeForMethod(DexMethod method, GraphLens codeLens) {
assert isContextFreeForMethods(codeLens);
return true;
}
public static GraphLens getIdentityLens() {
return IdentityGraphLens.getInstance();
}
public boolean hasCodeRewritings() {
return true;
}
public boolean hasCustomLensCodeRewriter() {
return false;
}
public CustomLensCodeRewriter getCustomLensCodeRewriter() {
assert hasCustomLensCodeRewriter();
return null;
}
public boolean isAccessModifierLens() {
return false;
}
public boolean isAppliedLens() {
return false;
}
public boolean isArgumentPropagatorGraphLens() {
return false;
}
public final boolean isClassMergerLens() {
return isHorizontalClassMergerGraphLens() || isVerticalClassMergerLens();
}
public boolean isClearCodeRewritingLens() {
return false;
}
public boolean isEnumUnboxerLens() {
return false;
}
public EnumUnboxingLens asEnumUnboxerLens() {
return null;
}
public boolean isNumberUnboxerLens() {
return false;
}
public boolean isHorizontalClassMergerGraphLens() {
return false;
}
public HorizontalClassMergerGraphLens asHorizontalClassMergerGraphLens() {
return null;
}
public abstract boolean isIdentityLens();
public abstract boolean isIdentityLensForFields(GraphLens codeLens);
public boolean isMemberRebindingLens() {
return false;
}
public MemberRebindingLens asMemberRebindingLens() {
return null;
}
public boolean isMemberRebindingIdentityLens() {
return false;
}
public MemberRebindingIdentityLens asMemberRebindingIdentityLens() {
return null;
}
public abstract boolean isNonIdentityLens();
public NonIdentityGraphLens asNonIdentityLens() {
return null;
}
public boolean isProtoNormalizerLens() {
return false;
}
public boolean isRepackagingLens() {
return false;
}
public boolean isVerticalClassMergerLens() {
return false;
}
public VerticalClassMergerGraphLens asVerticalClassMergerLens() {
return null;
}
@SuppressWarnings("ReferenceEquality")
public boolean assertFieldsNotModified(Iterable<DexEncodedField> fields) {
for (DexEncodedField field : fields) {
DexField reference = field.getReference();
assert getRenamedFieldSignature(reference) == reference;
}
return true;
}
public Map<DexCallSite, ProgramMethodSet> rewriteCallSites(
Map<DexCallSite, ProgramMethodSet> callSites,
DexDefinitionSupplier definitions,
Timing timing) {
timing.begin("Rewrite call sites");
Map<DexCallSite, ProgramMethodSet> result = new IdentityHashMap<>();
LensCodeRewriterUtils rewriter = new LensCodeRewriterUtils(definitions, this, null);
callSites.forEach(
(callSite, contexts) -> {
for (ProgramMethod context : contexts.rewrittenWithLens(definitions, this)) {
DexCallSite rewrittenCallSite = rewriter.rewriteCallSite(callSite, context);
result
.computeIfAbsent(rewrittenCallSite, ignore -> ProgramMethodSet.create())
.add(context);
}
});
timing.end();
return result;
}
@SuppressWarnings("ReferenceEquality")
public Set<DexField> rewriteFields(Set<DexField> fields, Timing timing) {
timing.begin("Rewrite fields");
GraphLens appliedLens = getIdentityLens();
Set<DexField> rewrittenFields;
if (isIdentityLensForFields(appliedLens)) {
assert verifyIsIdentityLensForFields(fields, appliedLens);
rewrittenFields = fields;
} else {
rewrittenFields = null;
for (DexField field : fields) {
DexField rewrittenField = getRenamedFieldSignature(field, appliedLens);
// If rewrittenFields is non-null we have previously seen a change and need to record the
// field no matter what.
if (rewrittenFields != null) {
rewrittenFields.add(rewrittenField);
continue;
}
// If the field has not been rewritten then we can reuse the input set.
if (rewrittenField == field) {
continue;
}
// Otherwise add the rewritten field and all previous fields to a new set.
rewrittenFields = SetUtils.newIdentityHashSet(fields.size());
CollectionUtils.forEachUntilExclusive(fields, rewrittenFields::add, field);
rewrittenFields.add(rewrittenField);
}
if (rewrittenFields == null) {
rewrittenFields = fields;
} else {
rewrittenFields =
SetUtils.trimCapacityOfIdentityHashSetIfSizeLessThan(rewrittenFields, fields.size());
}
}
timing.end();
return rewrittenFields;
}
@SuppressWarnings("ReferenceEquality")
private boolean verifyIsIdentityLensForFields(
Collection<DexField> fields, GraphLens appliedLens) {
for (DexField field : fields) {
assert lookupField(field, appliedLens) == field;
}
return true;
}
@SuppressWarnings("unchecked")
public <T extends DexReference> T rewriteReference(T reference) {
return rewriteReference(reference, null);
}
@SuppressWarnings("unchecked")
public <T extends DexReference> T rewriteReference(T reference, GraphLens codeLens) {
return (T)
reference.apply(
type -> lookupType(type, codeLens),
field -> getRenamedFieldSignature(field, codeLens),
method -> getRenamedMethodSignature(method, codeLens));
}
public <T extends DexReference> Set<T> rewriteReferences(Set<T> references) {
if (isThrowingSet(references)) {
return references;
}
Set<T> result = SetUtils.newIdentityHashSet(references.size());
for (T reference : references) {
result.add(rewriteReference(reference));
}
return result;
}
public <R extends DexReference, T> Map<R, T> rewriteReferenceKeys(
Map<R, T> map, BiFunction<R, List<T>, T> merge) {
Map<R, T> result = new IdentityHashMap<>();
Map<R, List<T>> needsMerge = new IdentityHashMap<>();
map.forEach(
(reference, value) -> {
R rewrittenReference = rewriteReference(reference);
List<T> unmergedValues = needsMerge.get(rewrittenReference);
if (unmergedValues != null) {
unmergedValues.add(value);
} else {
T existingValue = result.put(rewrittenReference, value);
if (existingValue != null) {
// Remove this for now and let the merge function decide when all colliding values are
// known.
needsMerge.put(rewrittenReference, ListUtils.newArrayList(existingValue, value));
result.remove(rewrittenReference);
}
}
});
needsMerge.forEach(
(rewrittenReference, unmergedValues) -> {
T mergedValue = merge.apply(rewrittenReference, unmergedValues);
if (mergedValue != null) {
result.put(rewrittenReference, mergedValue);
}
});
return result;
}
public <T extends DexReference> Object2BooleanMap<T> rewriteReferenceKeys(
Object2BooleanMap<T> map) {
Object2BooleanMap<T> result = new Object2BooleanArrayMap<>();
for (Object2BooleanMap.Entry<T> entry : map.object2BooleanEntrySet()) {
result.put(rewriteReference(entry.getKey()), entry.getBooleanValue());
}
return result;
}
public <T> ImmutableMap<DexField, T> rewriteFieldKeys(Map<DexField, T> map) {
ImmutableMap.Builder<DexField, T> builder = ImmutableMap.builder();
map.forEach((field, value) -> builder.put(getRenamedFieldSignature(field), value));
return builder.build();
}
public ImmutableSet<DexType> rewriteTypes(Set<DexType> types) {
ImmutableSet.Builder<DexType> builder = new ImmutableSet.Builder<>();
for (DexType type : types) {
builder.add(lookupType(type));
}
return builder.build();
}
public <T> Map<DexType, T> rewriteTypeKeys(Map<DexType, T> map, BiFunction<T, T, T> merge) {
Map<DexType, T> newMap = new IdentityHashMap<>();
map.forEach(
(type, value) -> {
DexType rewrittenType = lookupType(type);
T previousValue = newMap.get(rewrittenType);
newMap.put(
rewrittenType, previousValue != null ? merge.apply(value, previousValue) : value);
});
return newMap;
}
public boolean verifyMappingToOriginalProgram(
AppView<?> appView, DexApplication originalApplication) {
Iterable<DexProgramClass> classes = appView.appInfo().classesWithDeterministicOrder();
// Collect all original fields and methods for efficient querying.
Set<DexField> originalFields = Sets.newIdentityHashSet();
Set<DexMethod> originalMethods = Sets.newIdentityHashSet();
for (DexProgramClass clazz : originalApplication.classes()) {
for (DexEncodedField field : clazz.fields()) {
originalFields.add(field.getReference());
}
for (DexEncodedMethod method : clazz.methods()) {
originalMethods.add(method.getReference());
}
}
// Check that all fields and methods in the generated program can be mapped back to one of the
// original fields or methods.
for (DexProgramClass clazz : classes) {
if (appView.appInfo().getSyntheticItems().isSyntheticClass(clazz)) {
continue;
}
for (DexEncodedField field : clazz.fields()) {
if (field.isD8R8Synthesized()) {
// Fields synthesized by D8/R8 may not be mapped.
continue;
}
DexField originalField = getOriginalFieldSignature(field.getReference());
assert originalFields.contains(originalField)
: "Unable to map field `"
+ field.getReference().toSourceString()
+ "` back to original program";
}
for (DexEncodedMethod method : clazz.methods()) {
// Methods synthesized by D8/R8 are not mapped, but all non-synthesized must be originals.
assert method.isD8R8Synthesized() || originalMethods.contains(method.getReference());
}
}
return true;
}
}