| // 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; |
| } |
| } |