| // 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.graph; |
| |
| import com.android.tools.r8.ir.analysis.value.SingleValue; |
| import com.android.tools.r8.ir.code.ConstInstruction; |
| import com.android.tools.r8.ir.code.IRCode; |
| import com.android.tools.r8.ir.code.Instruction; |
| import com.android.tools.r8.ir.code.Position; |
| import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier; |
| import com.android.tools.r8.ir.conversion.ExtraParameter; |
| import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter; |
| import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfoFixer; |
| import com.android.tools.r8.shaking.AppInfoWithLiveness; |
| import com.android.tools.r8.utils.ConsumerUtils; |
| import com.android.tools.r8.utils.IntObjConsumer; |
| import com.android.tools.r8.utils.IteratorUtils; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Ordering; |
| import it.unimi.dsi.fastutil.ints.Int2ObjectMap; |
| import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry; |
| import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap; |
| import it.unimi.dsi.fastutil.ints.Int2ObjectSortedMap; |
| import it.unimi.dsi.fastutil.ints.IntArrayList; |
| import it.unimi.dsi.fastutil.ints.IntBidirectionalIterator; |
| import it.unimi.dsi.fastutil.ints.IntCollection; |
| import it.unimi.dsi.fastutil.ints.IntList; |
| import it.unimi.dsi.fastutil.ints.IntLists; |
| import it.unimi.dsi.fastutil.ints.IntSortedSet; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.function.Consumer; |
| |
| public class RewrittenPrototypeDescription { |
| |
| public abstract static class ArgumentInfo { |
| |
| static final ArgumentInfo NO_INFO = |
| new ArgumentInfo() { |
| |
| @Override |
| public ArgumentInfo combine(ArgumentInfo info) { |
| assert false : "ArgumentInfo NO_INFO should not be combined"; |
| return info; |
| } |
| |
| @Override |
| public ArgumentInfo rewrittenWithLens( |
| AppView<AppInfoWithLiveness> appView, GraphLens graphLens) { |
| return this; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return obj == this; |
| } |
| |
| @Override |
| public int hashCode() { |
| return System.identityHashCode(this); |
| } |
| }; |
| |
| @SuppressWarnings("ConstantConditions") |
| public static ArgumentInfo combine(ArgumentInfo arg1, ArgumentInfo arg2) { |
| if (arg1 == null) { |
| assert arg2 != null; |
| return arg2; |
| } |
| if (arg2 == null) { |
| assert arg1 != null; |
| return arg1; |
| } |
| return arg1.combine(arg2); |
| } |
| |
| public boolean isRemovedArgumentInfo() { |
| return false; |
| } |
| |
| public RemovedArgumentInfo asRemovedArgumentInfo() { |
| return null; |
| } |
| |
| public boolean isRewrittenTypeInfo() { |
| return false; |
| } |
| |
| public RewrittenTypeInfo asRewrittenTypeInfo() { |
| return null; |
| } |
| |
| // ArgumentInfo are combined with `this` first, and the `info` argument second. |
| public abstract ArgumentInfo combine(ArgumentInfo info); |
| |
| public abstract ArgumentInfo rewrittenWithLens( |
| AppView<AppInfoWithLiveness> appView, GraphLens graphLens); |
| |
| @Override |
| public abstract boolean equals(Object obj); |
| |
| @Override |
| public abstract int hashCode(); |
| } |
| |
| public static class RemovedArgumentInfo extends ArgumentInfo { |
| |
| public static class Builder { |
| |
| private SingleValue singleValue; |
| private DexType type; |
| |
| public Builder setSingleValue(SingleValue singleValue) { |
| this.singleValue = singleValue; |
| return this; |
| } |
| |
| public Builder setType(DexType type) { |
| this.type = type; |
| return this; |
| } |
| |
| public RemovedArgumentInfo build() { |
| assert type != null; |
| return new RemovedArgumentInfo(singleValue, type); |
| } |
| } |
| |
| private final SingleValue singleValue; |
| private final DexType type; |
| |
| private RemovedArgumentInfo(SingleValue singleValue, DexType type) { |
| this.singleValue = singleValue; |
| this.type = type; |
| } |
| |
| public static Builder builder() { |
| return new Builder(); |
| } |
| |
| public boolean hasSingleValue() { |
| return singleValue != null; |
| } |
| |
| public SingleValue getSingleValue() { |
| return singleValue; |
| } |
| |
| public DexType getType() { |
| return type; |
| } |
| |
| public boolean isNeverUsed() { |
| return !hasSingleValue(); |
| } |
| |
| @Override |
| public boolean isRemovedArgumentInfo() { |
| return true; |
| } |
| |
| @Override |
| public RemovedArgumentInfo asRemovedArgumentInfo() { |
| return this; |
| } |
| |
| @Override |
| public ArgumentInfo combine(ArgumentInfo info) { |
| assert false : "Once the argument is removed one cannot modify it any further."; |
| return this; |
| } |
| |
| @Override |
| public RemovedArgumentInfo rewrittenWithLens( |
| AppView<AppInfoWithLiveness> appView, GraphLens graphLens) { |
| SingleValue rewrittenSingleValue = |
| hasSingleValue() ? singleValue.rewrittenWithLens(appView, graphLens) : null; |
| DexType rewrittenType = graphLens.lookupType(type); |
| if (rewrittenSingleValue != singleValue || rewrittenType != type) { |
| return new RemovedArgumentInfo(rewrittenSingleValue, rewrittenType); |
| } |
| return this; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null || getClass() != obj.getClass()) { |
| return false; |
| } |
| RemovedArgumentInfo other = (RemovedArgumentInfo) obj; |
| return type == other.type && Objects.equals(singleValue, other.singleValue); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(singleValue, type); |
| } |
| } |
| |
| public static class RewrittenTypeInfo extends ArgumentInfo { |
| |
| private final DexType oldType; |
| private final DexType newType; |
| private final SingleValue singleValue; |
| |
| public static RewrittenTypeInfo toVoid( |
| DexType oldReturnType, DexItemFactory dexItemFactory, SingleValue singleValue) { |
| assert singleValue != null; |
| return new RewrittenTypeInfo(oldReturnType, dexItemFactory.voidType, singleValue); |
| } |
| |
| public RewrittenTypeInfo(DexType oldType, DexType newType) { |
| this(oldType, newType, null); |
| } |
| |
| public RewrittenTypeInfo(DexType oldType, DexType newType, SingleValue singleValue) { |
| this.oldType = oldType; |
| this.newType = newType; |
| this.singleValue = singleValue; |
| assert !newType.isVoidType() || singleValue != null; |
| } |
| |
| public RewrittenTypeInfo combine(RewrittenPrototypeDescription other) { |
| return other.hasRewrittenReturnInfo() ? combine(other.getRewrittenReturnInfo()) : this; |
| } |
| |
| public DexType getNewType() { |
| return newType; |
| } |
| |
| public DexType getOldType() { |
| return oldType; |
| } |
| |
| public SingleValue getSingleValue() { |
| return singleValue; |
| } |
| |
| boolean hasBeenChangedToReturnVoid() { |
| return newType.isVoidType(); |
| } |
| |
| public boolean hasSingleValue() { |
| return singleValue != null; |
| } |
| |
| @Override |
| public boolean isRewrittenTypeInfo() { |
| return true; |
| } |
| |
| @Override |
| public RewrittenTypeInfo asRewrittenTypeInfo() { |
| return this; |
| } |
| |
| @Override |
| public ArgumentInfo combine(ArgumentInfo info) { |
| if (info.isRemovedArgumentInfo()) { |
| return info; |
| } |
| assert info.isRewrittenTypeInfo(); |
| return combine(info.asRewrittenTypeInfo()); |
| } |
| |
| public RewrittenTypeInfo combine(RewrittenTypeInfo other) { |
| assert !getNewType().isVoidType(); |
| assert getNewType() == other.getOldType(); |
| return new RewrittenTypeInfo(getOldType(), other.getNewType(), other.getSingleValue()); |
| } |
| |
| @Override |
| public RewrittenTypeInfo rewrittenWithLens( |
| AppView<AppInfoWithLiveness> appView, GraphLens graphLens) { |
| DexType rewrittenNewType = graphLens.lookupType(newType); |
| SingleValue rewrittenSingleValue = |
| hasSingleValue() ? getSingleValue().rewrittenWithLens(appView, graphLens) : null; |
| if (rewrittenNewType != newType || rewrittenSingleValue != singleValue) { |
| // The old type is intentionally not rewritten. |
| return new RewrittenTypeInfo(oldType, rewrittenNewType, rewrittenSingleValue); |
| } |
| return this; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null || getClass() != obj.getClass()) { |
| return false; |
| } |
| RewrittenTypeInfo other = (RewrittenTypeInfo) obj; |
| return oldType == other.oldType |
| && newType == other.newType |
| && Objects.equals(singleValue, other.singleValue); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(oldType, newType, singleValue); |
| } |
| |
| public boolean verifyIsDueToUnboxing(DexItemFactory dexItemFactory) { |
| assert oldType.toBaseType(dexItemFactory).isClassType(); |
| assert newType.toBaseType(dexItemFactory).isIntType(); |
| return true; |
| } |
| } |
| |
| public static class ArgumentInfoCollection { |
| |
| private static final ArgumentInfoCollection EMPTY = new ArgumentInfoCollection(); |
| |
| private final Int2ObjectSortedMap<ArgumentInfo> argumentInfos; |
| |
| // Specific constructor for empty. |
| private ArgumentInfoCollection() { |
| this.argumentInfos = new Int2ObjectRBTreeMap<>(); |
| } |
| |
| private ArgumentInfoCollection(Int2ObjectSortedMap<ArgumentInfo> argumentInfos) { |
| assert argumentInfos != null : "should use empty."; |
| assert !argumentInfos.isEmpty() : "should use empty."; |
| this.argumentInfos = argumentInfos; |
| } |
| |
| public static ArgumentInfoCollection empty() { |
| return EMPTY; |
| } |
| |
| public void forEach(IntObjConsumer<ArgumentInfo> consumer) { |
| for (Entry<ArgumentInfo> entry : argumentInfos.int2ObjectEntrySet()) { |
| consumer.accept(entry.getIntKey(), entry.getValue()); |
| } |
| } |
| |
| public IntSortedSet getKeys() { |
| return argumentInfos.keySet(); |
| } |
| |
| public IntCollection getRemovedParameterIndices() { |
| int numberOfRemovedArguments = numberOfRemovedArguments(); |
| if (numberOfRemovedArguments == 0) { |
| return IntLists.EMPTY_LIST; |
| } |
| if (numberOfRemovedArguments == argumentInfos.size()) { |
| return getKeys(); |
| } |
| IntList removedParameterIndices = new IntArrayList(numberOfRemovedArguments); |
| Iterator<Entry<ArgumentInfo>> iterator = iterator(); |
| while (iterator.hasNext()) { |
| Entry<ArgumentInfo> entry = iterator.next(); |
| if (entry.getValue().isRemovedArgumentInfo()) { |
| removedParameterIndices.add(entry.getIntKey()); |
| } |
| } |
| return removedParameterIndices; |
| } |
| |
| public boolean isArgumentRemoved(int argumentIndex) { |
| return getArgumentInfo(argumentIndex).isRemovedArgumentInfo(); |
| } |
| |
| public boolean isEmpty() { |
| return this == EMPTY; |
| } |
| |
| public Iterator<Entry<ArgumentInfo>> iterator() { |
| return argumentInfos.int2ObjectEntrySet().iterator(); |
| } |
| |
| public boolean hasRemovedArguments() { |
| for (ArgumentInfo value : argumentInfos.values()) { |
| if (value.isRemovedArgumentInfo()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public int numberOfRemovedArguments() { |
| int removed = 0; |
| for (ArgumentInfo value : argumentInfos.values()) { |
| if (value.isRemovedArgumentInfo()) { |
| removed++; |
| } |
| } |
| return removed; |
| } |
| |
| public boolean hasArgumentInfo(int argumentIndex) { |
| return argumentInfos.containsKey(argumentIndex); |
| } |
| |
| public ArgumentInfo getArgumentInfo(int argumentIndex) { |
| return argumentInfos.getOrDefault(argumentIndex, ArgumentInfo.NO_INFO); |
| } |
| |
| public int size() { |
| return argumentInfos.size(); |
| } |
| |
| public ArgumentInfoCollection rewrittenWithLens( |
| AppView<AppInfoWithLiveness> appView, GraphLens graphLens) { |
| Int2ObjectSortedMap<ArgumentInfo> rewrittenArgumentInfos = new Int2ObjectRBTreeMap<>(); |
| for (Int2ObjectMap.Entry<ArgumentInfo> entry : argumentInfos.int2ObjectEntrySet()) { |
| ArgumentInfo argumentInfo = entry.getValue(); |
| ArgumentInfo rewrittenArgumentInfo = argumentInfo.rewrittenWithLens(appView, graphLens); |
| if (rewrittenArgumentInfo != argumentInfo) { |
| rewrittenArgumentInfos.put(entry.getIntKey(), rewrittenArgumentInfo); |
| } |
| } |
| if (!rewrittenArgumentInfos.isEmpty()) { |
| for (Int2ObjectMap.Entry<ArgumentInfo> entry : argumentInfos.int2ObjectEntrySet()) { |
| int key = entry.getIntKey(); |
| if (!rewrittenArgumentInfos.containsKey(key)) { |
| rewrittenArgumentInfos.put(key, entry.getValue()); |
| } |
| } |
| return new ArgumentInfoCollection(rewrittenArgumentInfos); |
| } |
| return this; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null || getClass() != obj.getClass()) { |
| return false; |
| } |
| ArgumentInfoCollection other = (ArgumentInfoCollection) obj; |
| return argumentInfos.equals(other.argumentInfos); |
| } |
| |
| @Override |
| public int hashCode() { |
| return argumentInfos.hashCode(); |
| } |
| |
| public static Builder builder() { |
| return new Builder(); |
| } |
| |
| public static class Builder { |
| |
| private Int2ObjectSortedMap<ArgumentInfo> argumentInfos; |
| |
| public Builder addArgumentInfo(int argIndex, ArgumentInfo argInfo) { |
| if (argumentInfos == null) { |
| argumentInfos = new Int2ObjectRBTreeMap<>(); |
| } |
| assert !argumentInfos.containsKey(argIndex); |
| argumentInfos.put(argIndex, argInfo); |
| return this; |
| } |
| |
| public ArgumentInfoCollection build() { |
| if (argumentInfos == null || argumentInfos.isEmpty()) { |
| return EMPTY; |
| } |
| return new ArgumentInfoCollection(argumentInfos); |
| } |
| } |
| |
| public DexMethod rewriteMethod(ProgramMethod method, DexItemFactory dexItemFactory) { |
| if (isEmpty()) { |
| return method.getReference(); |
| } |
| DexProto rewrittenProto = rewriteProto(method, dexItemFactory); |
| return method.getReference().withProto(rewrittenProto, dexItemFactory); |
| } |
| |
| public DexProto rewriteProto(ProgramMethod method, DexItemFactory dexItemFactory) { |
| return isEmpty() |
| ? method.getProto() |
| : dexItemFactory.createProto(method.getReturnType(), rewriteParameters(method)); |
| } |
| |
| public DexType[] rewriteParameters(ProgramMethod method) { |
| return rewriteParameters(method.getDefinition()); |
| } |
| |
| public DexType[] rewriteParameters(DexEncodedMethod encodedMethod) { |
| // Currently not allowed to remove the receiver of an instance method. This would involve |
| // changing invoke-direct/invoke-virtual into invoke-static. |
| assert encodedMethod.isStatic() || !getArgumentInfo(0).isRemovedArgumentInfo(); |
| DexType[] params = encodedMethod.getReference().proto.parameters.values; |
| if (isEmpty()) { |
| return params; |
| } |
| DexType[] newParams = new DexType[params.length - numberOfRemovedArguments()]; |
| int offset = encodedMethod.getFirstNonReceiverArgumentIndex(); |
| int newParamIndex = 0; |
| for (int oldParamIndex = 0; oldParamIndex < params.length; oldParamIndex++) { |
| ArgumentInfo argInfo = argumentInfos.get(oldParamIndex + offset); |
| if (argInfo == null) { |
| newParams[newParamIndex++] = params[oldParamIndex]; |
| } else if (argInfo.isRewrittenTypeInfo()) { |
| RewrittenTypeInfo rewrittenTypeInfo = argInfo.asRewrittenTypeInfo(); |
| assert params[oldParamIndex] == rewrittenTypeInfo.oldType; |
| newParams[newParamIndex++] = rewrittenTypeInfo.newType; |
| } |
| } |
| return newParams; |
| } |
| |
| public ArgumentInfoCollection combine(ArgumentInfoCollection info) { |
| if (isEmpty()) { |
| return info; |
| } else { |
| if (info.isEmpty()) { |
| return this; |
| } |
| } |
| |
| Int2ObjectSortedMap<ArgumentInfo> newArgInfos = new Int2ObjectRBTreeMap<>(); |
| newArgInfos.putAll(argumentInfos); |
| IntBidirectionalIterator iterator = argumentInfos.keySet().iterator(); |
| int offset = 0; |
| int nextArgIndex; |
| for (int pendingArgIndex : info.argumentInfos.keySet()) { |
| nextArgIndex = peekNextOrMax(iterator); |
| while (nextArgIndex <= pendingArgIndex + offset) { |
| iterator.nextInt(); |
| ArgumentInfo argumentInfo = argumentInfos.get(nextArgIndex); |
| nextArgIndex = peekNextOrMax(iterator); |
| if (argumentInfo.isRemovedArgumentInfo()) { |
| offset++; |
| } |
| } |
| ArgumentInfo newArgInfo = |
| nextArgIndex == pendingArgIndex + offset |
| ? ArgumentInfo.combine( |
| argumentInfos.get(nextArgIndex), info.argumentInfos.get(pendingArgIndex)) |
| : info.argumentInfos.get(pendingArgIndex); |
| newArgInfos.put(pendingArgIndex + offset, newArgInfo); |
| } |
| assert Ordering.natural().isOrdered(newArgInfos.keySet()); |
| return new ArgumentInfoCollection(newArgInfos); |
| } |
| |
| static int peekNextOrMax(IntBidirectionalIterator iterator) { |
| if (iterator.hasNext()) { |
| int i = iterator.nextInt(); |
| iterator.previousInt(); |
| return i; |
| } |
| return Integer.MAX_VALUE; |
| } |
| |
| public MethodOptimizationInfoFixer createMethodOptimizationInfoFixer() { |
| RewrittenPrototypeDescription prototypeChanges = |
| RewrittenPrototypeDescription.create(Collections.emptyList(), null, this); |
| return prototypeChanges.createMethodOptimizationInfoFixer(); |
| } |
| |
| /** |
| * Returns a function for rewriting the parameter annotations on a method info after prototype |
| * changes were made. |
| */ |
| public Consumer<DexEncodedMethod.Builder> createParameterAnnotationsRemover( |
| DexEncodedMethod method) { |
| if (numberOfRemovedArguments() > 0 && !method.parameterAnnotationsList.isEmpty()) { |
| return builder -> { |
| int firstArgumentIndex = method.getFirstNonReceiverArgumentIndex(); |
| builder.removeParameterAnnotations( |
| oldIndex -> getArgumentInfo(oldIndex + firstArgumentIndex).isRemovedArgumentInfo()); |
| }; |
| } |
| return ConsumerUtils.emptyConsumer(); |
| } |
| |
| public int getNewArgumentIndex(int argumentIndex) { |
| int numberOfArgumentsRemovedBeforeArgument = 0; |
| Iterator<Entry<ArgumentInfo>> iterator = iterator(); |
| while (iterator.hasNext()) { |
| Entry<ArgumentInfo> entry = iterator.next(); |
| int argumentIndexForInfo = entry.getIntKey(); |
| if (argumentIndexForInfo >= argumentIndex) { |
| break; |
| } |
| ArgumentInfo argumentInfo = entry.getValue(); |
| if (argumentInfo.isRemovedArgumentInfo()) { |
| numberOfArgumentsRemovedBeforeArgument++; |
| } |
| } |
| assert IteratorUtils.allRemainingMatchDestructive( |
| iterator, entry -> entry.getIntKey() >= argumentIndex); |
| return argumentIndex - numberOfArgumentsRemovedBeforeArgument; |
| } |
| } |
| |
| private static final RewrittenPrototypeDescription NONE = new RewrittenPrototypeDescription(); |
| |
| private final List<ExtraParameter> extraParameters; |
| private final ArgumentInfoCollection argumentInfoCollection; |
| private final RewrittenTypeInfo rewrittenReturnInfo; |
| |
| private RewrittenPrototypeDescription() { |
| this.extraParameters = Collections.emptyList(); |
| this.rewrittenReturnInfo = null; |
| this.argumentInfoCollection = ArgumentInfoCollection.empty(); |
| } |
| |
| private RewrittenPrototypeDescription( |
| List<ExtraParameter> extraParameters, |
| RewrittenTypeInfo rewrittenReturnInfo, |
| ArgumentInfoCollection argumentsInfo) { |
| assert argumentsInfo != null; |
| this.extraParameters = extraParameters; |
| this.rewrittenReturnInfo = rewrittenReturnInfo; |
| this.argumentInfoCollection = argumentsInfo; |
| assert !isEmpty(); |
| } |
| |
| public static RewrittenPrototypeDescription create( |
| List<ExtraParameter> extraParameters, |
| RewrittenTypeInfo rewrittenReturnInfo, |
| ArgumentInfoCollection argumentsInfo) { |
| return extraParameters.isEmpty() && rewrittenReturnInfo == null && argumentsInfo.isEmpty() |
| ? none() |
| : new RewrittenPrototypeDescription(extraParameters, rewrittenReturnInfo, argumentsInfo); |
| } |
| |
| public static RewrittenPrototypeDescription createForRewrittenTypes( |
| RewrittenTypeInfo returnInfo, ArgumentInfoCollection rewrittenArgumentsInfo) { |
| return create(Collections.emptyList(), returnInfo, rewrittenArgumentsInfo); |
| } |
| |
| public static RewrittenPrototypeDescription none() { |
| return NONE; |
| } |
| |
| public Consumer<DexEncodedMethod.Builder> createParameterAnnotationsRemover( |
| DexEncodedMethod method) { |
| return getArgumentInfoCollection().createParameterAnnotationsRemover(method); |
| } |
| |
| public MethodOptimizationInfoFixer createMethodOptimizationInfoFixer() { |
| return new RewrittenPrototypeDescriptionMethodOptimizationInfoFixer(this); |
| } |
| |
| public RewrittenPrototypeDescription combine(RewrittenPrototypeDescription other) { |
| if (isEmpty()) { |
| return other; |
| } |
| if (other.isEmpty()) { |
| return this; |
| } |
| // We currently don't have any passes that remove extra parameters inserted by previous passes. |
| // If the input prototype changes have removed some of the extra parameters, we would need to |
| // adapt the merging of prototype changes below. |
| List<ExtraParameter> newExtraParameters = |
| ImmutableList.<ExtraParameter>builder() |
| .addAll(getExtraParameters()) |
| .addAll(other.getExtraParameters()) |
| .build(); |
| RewrittenTypeInfo newRewrittenTypeInfo = |
| hasRewrittenReturnInfo() |
| ? getRewrittenReturnInfo().combine(other) |
| : other.getRewrittenReturnInfo(); |
| ArgumentInfoCollection newArgumentInfoCollection = |
| getArgumentInfoCollection().combine(other.getArgumentInfoCollection()); |
| return new RewrittenPrototypeDescription( |
| newExtraParameters, newRewrittenTypeInfo, newArgumentInfoCollection); |
| } |
| |
| public boolean isEmpty() { |
| return extraParameters.isEmpty() |
| && rewrittenReturnInfo == null |
| && argumentInfoCollection.isEmpty(); |
| } |
| |
| public boolean hasExtraParameters() { |
| return !extraParameters.isEmpty(); |
| } |
| |
| public List<ExtraParameter> getExtraParameters() { |
| return extraParameters; |
| } |
| |
| public int numberOfExtraParameters() { |
| return extraParameters.size(); |
| } |
| |
| public boolean hasBeenChangedToReturnVoid() { |
| return rewrittenReturnInfo != null && rewrittenReturnInfo.hasBeenChangedToReturnVoid(); |
| } |
| |
| public ArgumentInfoCollection getArgumentInfoCollection() { |
| return argumentInfoCollection; |
| } |
| |
| public boolean hasRewrittenReturnInfo() { |
| return rewrittenReturnInfo != null; |
| } |
| |
| public boolean requiresRewritingAtCallSite() { |
| return hasRewrittenReturnInfo() |
| || numberOfExtraParameters() > 0 |
| || argumentInfoCollection.numberOfRemovedArguments() > 0; |
| } |
| |
| public RewrittenTypeInfo getRewrittenReturnInfo() { |
| return rewrittenReturnInfo; |
| } |
| |
| /** |
| * Returns the {@link ConstInstruction} that should be used to materialize the result of |
| * invocations to the method represented by this {@link RewrittenPrototypeDescription}. |
| * |
| * <p>This method should only be used for methods that return a constant value and whose return |
| * type has been changed to void. |
| * |
| * <p>Note that the current implementation always returns null at this point. |
| */ |
| public Instruction getConstantReturn( |
| AppView<AppInfoWithLiveness> appView, |
| IRCode code, |
| ProgramMethod method, |
| Position position, |
| TypeAndLocalInfoSupplier info) { |
| assert rewrittenReturnInfo != null; |
| assert rewrittenReturnInfo.hasSingleValue(); |
| assert rewrittenReturnInfo.getSingleValue().isMaterializableInContext(appView, method); |
| Instruction instruction = |
| rewrittenReturnInfo.getSingleValue().createMaterializingInstruction(appView, code, info); |
| instruction.setPosition(position); |
| return instruction; |
| } |
| |
| public DexMethod rewriteMethod(ProgramMethod method, DexItemFactory dexItemFactory) { |
| if (isEmpty()) { |
| return method.getReference(); |
| } |
| DexProto rewrittenProto = rewriteProto(method.getDefinition(), dexItemFactory); |
| return method.getReference().withProto(rewrittenProto, dexItemFactory); |
| } |
| |
| public DexProto rewriteProto(DexEncodedMethod encodedMethod, DexItemFactory dexItemFactory) { |
| if (isEmpty()) { |
| return encodedMethod.getReference().proto; |
| } |
| DexType newReturnType = |
| rewrittenReturnInfo != null |
| ? rewrittenReturnInfo.newType |
| : encodedMethod.getReference().proto.returnType; |
| DexType[] newParameters = argumentInfoCollection.rewriteParameters(encodedMethod); |
| return dexItemFactory.createProto(newReturnType, newParameters); |
| } |
| |
| public RewrittenPrototypeDescription rewrittenWithLens( |
| AppView<AppInfoWithLiveness> appView, GraphLens graphLens) { |
| ArgumentInfoCollection newArgumentInfoCollection = |
| argumentInfoCollection.rewrittenWithLens(appView, graphLens); |
| RewrittenTypeInfo newRewrittenReturnInfo = |
| hasRewrittenReturnInfo() ? rewrittenReturnInfo.rewrittenWithLens(appView, graphLens) : null; |
| if (newArgumentInfoCollection != argumentInfoCollection |
| || newRewrittenReturnInfo != rewrittenReturnInfo) { |
| return new RewrittenPrototypeDescription( |
| extraParameters, newRewrittenReturnInfo, newArgumentInfoCollection); |
| } |
| return this; |
| } |
| |
| public RewrittenPrototypeDescription withRewrittenReturnInfo( |
| RewrittenTypeInfo newRewrittenReturnInfo) { |
| if (Objects.equals(rewrittenReturnInfo, newRewrittenReturnInfo)) { |
| return this; |
| } |
| return new RewrittenPrototypeDescription( |
| extraParameters, newRewrittenReturnInfo, argumentInfoCollection); |
| } |
| |
| public RewrittenPrototypeDescription withExtraUnusedNullParameters( |
| int numberOfExtraUnusedNullParameters) { |
| List<ExtraParameter> parameters = |
| Collections.nCopies(numberOfExtraUnusedNullParameters, new ExtraUnusedNullParameter()); |
| return withExtraParameters(parameters); |
| } |
| |
| public RewrittenPrototypeDescription withExtraParameters(List<ExtraParameter> parameters) { |
| if (parameters.isEmpty()) { |
| return this; |
| } |
| List<ExtraParameter> newExtraParameters = |
| new ArrayList<>(extraParameters.size() + parameters.size()); |
| newExtraParameters.addAll(extraParameters); |
| newExtraParameters.addAll(parameters); |
| return new RewrittenPrototypeDescription( |
| newExtraParameters, rewrittenReturnInfo, argumentInfoCollection); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null || getClass() != obj.getClass()) { |
| return false; |
| } |
| RewrittenPrototypeDescription other = (RewrittenPrototypeDescription) obj; |
| return extraParameters.equals(other.extraParameters) |
| && Objects.equals(rewrittenReturnInfo, other.rewrittenReturnInfo) |
| && argumentInfoCollection.equals(other.argumentInfoCollection); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(extraParameters, rewrittenReturnInfo, argumentInfoCollection); |
| } |
| } |