blob: f44ec67c42aab2a3bb07ddbda296d591e7571728 [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.ir.conversion;
import static com.android.tools.r8.graph.UseRegistry.MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
import static com.android.tools.r8.ir.code.Invoke.Type.VIRTUAL;
import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS;
import static com.android.tools.r8.ir.code.Opcodes.CONST_METHOD_HANDLE;
import static com.android.tools.r8.ir.code.Opcodes.INIT_CLASS;
import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_OF;
import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_CUSTOM;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_MULTI_NEW_ARRAY;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_NEW_ARRAY;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_POLYMORPHIC;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
import static com.android.tools.r8.ir.code.Opcodes.MOVE_EXCEPTION;
import static com.android.tools.r8.ir.code.Opcodes.NEW_ARRAY_EMPTY;
import static com.android.tools.r8.ir.code.Opcodes.NEW_INSTANCE;
import static com.android.tools.r8.ir.code.Opcodes.RETURN;
import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
import static com.android.tools.r8.utils.ObjectUtils.getBooleanOrElse;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AccessControl;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexMethodHandle;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLens;
import com.android.tools.r8.graph.GraphLens.FieldLookupResult;
import com.android.tools.r8.graph.GraphLens.MethodLookupResult;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.RewrittenPrototypeDescription;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfo;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.analysis.value.SingleNumberValue;
import com.android.tools.r8.ir.code.Assume;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.CatchHandlers;
import com.android.tools.r8.ir.code.CheckCast;
import com.android.tools.r8.ir.code.ConstClass;
import com.android.tools.r8.ir.code.ConstInstruction;
import com.android.tools.r8.ir.code.ConstMethodHandle;
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InitClass;
import com.android.tools.r8.ir.code.InstanceGet;
import com.android.tools.r8.ir.code.InstanceOf;
import com.android.tools.r8.ir.code.InstancePut;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeCustom;
import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeMultiNewArray;
import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.MoveException;
import com.android.tools.r8.ir.code.NewArrayEmpty;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Phi;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.SafeCheckCast;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.TypeAndLocalInfoSupplier;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.optimize.enums.EnumUnboxer;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.optimize.MemberRebindingAnalysis;
import com.android.tools.r8.utils.InternalOptions;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
public class LensCodeRewriter {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final EnumUnboxer enumUnboxer;
private final LensCodeRewriterUtils helper;
private final InternalOptions options;
LensCodeRewriter(AppView<? extends AppInfoWithClassHierarchy> appView, EnumUnboxer enumUnboxer) {
this.appView = appView;
this.enumUnboxer = enumUnboxer;
this.helper = new LensCodeRewriterUtils(appView);
this.options = appView.options();
}
private Value makeOutValue(Instruction insn, IRCode code) {
if (insn.outValue() != null) {
TypeElement oldType = insn.getOutType();
TypeElement newType = oldType.rewrittenWithLens(appView, appView.graphLens());
return code.createValue(newType, insn.getLocalInfo());
}
return null;
}
private Value makeOutValue(FieldInstruction insn, IRCode code, DexField rewrittenField) {
if (insn.hasOutValue()) {
Nullability nullability = insn.getOutType().nullability();
TypeElement newType = TypeElement.fromDexType(rewrittenField.getType(), nullability, appView);
return code.createValue(newType, insn.getLocalInfo());
}
return null;
}
/** Replace type appearances, invoke targets and field accesses with actual definitions. */
public void rewrite(IRCode code, ProgramMethod method) {
Set<Phi> affectedPhis =
enumUnboxer != null ? enumUnboxer.rewriteCode(code) : Sets.newIdentityHashSet();
GraphLens graphLens = appView.graphLens();
DexItemFactory factory = appView.dexItemFactory();
// Rewriting types that affects phi can cause us to compute TOP for cyclic phi's. To solve this
// we track all phi's that needs to be re-computed.
ListIterator<BasicBlock> blocks = code.listIterator();
boolean mayHaveUnreachableBlocks = false;
while (blocks.hasNext()) {
BasicBlock block = blocks.next();
if (block.hasCatchHandlers() && options.enableVerticalClassMerging) {
boolean anyGuardsRenamed = block.renameGuardsInCatchHandlers(graphLens);
if (anyGuardsRenamed) {
mayHaveUnreachableBlocks |= unlinkDeadCatchHandlers(block);
}
}
InstructionListIterator iterator = block.listIterator(code);
while (iterator.hasNext()) {
Instruction current = iterator.next();
switch (current.opcode()) {
case INVOKE_CUSTOM:
{
InvokeCustom invokeCustom = current.asInvokeCustom();
DexCallSite callSite = invokeCustom.getCallSite();
DexCallSite newCallSite = helper.rewriteCallSite(callSite, method);
if (newCallSite != callSite) {
Value newOutValue = makeOutValue(invokeCustom, code);
InvokeCustom newInvokeCustom =
new InvokeCustom(newCallSite, newOutValue, invokeCustom.inValues());
iterator.replaceCurrentInstruction(newInvokeCustom);
if (newOutValue != null && newOutValue.getType() != invokeCustom.getOutType()) {
affectedPhis.addAll(newOutValue.uniquePhiUsers());
}
}
}
break;
case CONST_METHOD_HANDLE:
{
DexMethodHandle handle = current.asConstMethodHandle().getValue();
DexMethodHandle newHandle =
helper.rewriteDexMethodHandle(handle, NOT_ARGUMENT_TO_LAMBDA_METAFACTORY, method);
if (newHandle != handle) {
Value newOutValue = makeOutValue(current, code);
iterator.replaceCurrentInstruction(new ConstMethodHandle(newOutValue, newHandle));
if (newOutValue != null && newOutValue.getType() != current.getOutType()) {
affectedPhis.addAll(newOutValue.uniquePhiUsers());
}
}
}
break;
case INIT_CLASS:
{
InitClass initClass = current.asInitClass();
new InstructionReplacer(code, current, iterator, affectedPhis)
.replaceInstructionIfTypeChanged(
initClass.getClassValue(), (t, v) -> new InitClass(v, t));
}
break;
case INVOKE_DIRECT:
case INVOKE_INTERFACE:
case INVOKE_POLYMORPHIC:
case INVOKE_STATIC:
case INVOKE_SUPER:
case INVOKE_VIRTUAL:
{
InvokeMethod invoke = current.asInvokeMethod();
DexMethod invokedMethod = invoke.getInvokedMethod();
DexType invokedHolder = invokedMethod.holder;
if (invokedHolder.isArrayType()) {
DexType baseType = invokedHolder.toBaseType(factory);
new InstructionReplacer(code, current, iterator, affectedPhis)
.replaceInstructionIfTypeChanged(
baseType,
(t, v) -> {
DexType mappedHolder = invokedHolder.replaceBaseType(t, factory);
// Just reuse proto and name, as no methods on array types cant be renamed
// nor change signature.
DexMethod actualTarget =
factory.createMethod(
mappedHolder, invokedMethod.proto, invokedMethod.name);
return Invoke.create(VIRTUAL, actualTarget, null, v, invoke.inValues());
});
continue;
}
if (!invokedHolder.isClassType()) {
assert false;
continue;
}
if (invoke.isInvokeDirect()) {
checkInvokeDirect(method.getReference(), invoke.asInvokeDirect());
}
MethodLookupResult lensLookup =
graphLens.lookupMethod(invokedMethod, method.getReference(), invoke.getType());
DexMethod actualTarget = lensLookup.getReference();
Invoke.Type actualInvokeType = lensLookup.getType();
RewrittenPrototypeDescription prototypeChanges = lensLookup.getPrototypeChanges();
if (prototypeChanges.requiresRewritingAtCallSite()
|| invoke.getType() != actualInvokeType
|| actualTarget != invokedMethod) {
List<Value> newInValues;
ArgumentInfoCollection argumentInfoCollection =
prototypeChanges.getArgumentInfoCollection();
if (argumentInfoCollection.isEmpty()) {
newInValues = invoke.inValues();
} else {
if (argumentInfoCollection.hasRemovedArguments()) {
if (Log.ENABLED) {
Log.info(
getClass(),
"Invoked method "
+ invokedMethod.toSourceString()
+ " with "
+ argumentInfoCollection.numberOfRemovedArguments()
+ " arguments removed");
}
}
newInValues = new ArrayList<>(actualTarget.proto.parameters.size());
for (int i = 0; i < invoke.inValues().size(); i++) {
ArgumentInfo argumentInfo = argumentInfoCollection.getArgumentInfo(i);
if (argumentInfo.isRewrittenTypeInfo()) {
RewrittenTypeInfo argInfo = argumentInfo.asRewrittenTypeInfo();
Value rewrittenValue =
rewriteValueIfDefault(
code,
iterator,
argInfo.getOldType(),
argInfo.getNewType(),
invoke.inValues().get(i));
newInValues.add(rewrittenValue);
} else if (!argumentInfo.isRemovedArgumentInfo()) {
newInValues.add(invoke.inValues().get(i));
}
}
}
ConstInstruction constantReturnMaterializingInstruction = null;
if (prototypeChanges.hasBeenChangedToReturnVoid(appView.dexItemFactory())
&& invoke.outValue() != null) {
constantReturnMaterializingInstruction =
prototypeChanges.getConstantReturn(code, invoke.getPosition());
if (invoke.outValue().hasLocalInfo()) {
constantReturnMaterializingInstruction
.outValue()
.setLocalInfo(invoke.outValue().getLocalInfo());
}
invoke.outValue().replaceUsers(constantReturnMaterializingInstruction.outValue());
if (invoke.getOutType() != constantReturnMaterializingInstruction.getOutType()) {
affectedPhis.addAll(
constantReturnMaterializingInstruction.outValue().uniquePhiUsers());
}
}
Value newOutValue =
prototypeChanges.hasBeenChangedToReturnVoid(appView.dexItemFactory())
? null
: makeOutValue(invoke, code);
Map<SingleNumberValue, Map<DexType, Value>> parameterMap = new IdentityHashMap<>();
int parameterIndex = newInValues.size() - (actualInvokeType == STATIC ? 0 : 1);
for (ExtraParameter parameter : prototypeChanges.getExtraParameters()) {
DexType type = actualTarget.proto.getParameter(parameterIndex++);
SingleNumberValue numberValue = parameter.getValue(appView);
// Try to find an existing constant instruction, otherwise generate a new one.
Value value =
parameterMap
.computeIfAbsent(numberValue, ignore -> new IdentityHashMap<>())
.computeIfAbsent(
type,
ignore -> {
iterator.previous();
Instruction instruction =
numberValue.createMaterializingInstruction(
appView,
code,
TypeAndLocalInfoSupplier.create(
parameter.getTypeElement(appView, type), null));
assert !instruction.instructionTypeCanThrow();
instruction.setPosition(
options.debug ? invoke.getPosition() : Position.none());
iterator.add(instruction);
iterator.next();
return instruction.outValue();
});
newInValues.add(value);
// TODO(b/164901008): Fix when the number of arguments overflows.
if (newInValues.size() > 255) {
throw new CompilationError(
"The addition of extra unused null parameters in R8 led to the overflow of"
+ " the number of arguments of the method "
+ actualTarget);
}
}
assert newInValues.size()
== actualTarget.proto.parameters.size() + (actualInvokeType == STATIC ? 0 : 1);
// TODO(b/157111832): This bit should be part of the graph lens lookup result.
boolean isInterface =
getBooleanOrElse(
appView.definitionFor(actualTarget.holder), DexClass::isInterface, false);
Invoke newInvoke =
Invoke.create(
actualInvokeType,
actualTarget,
null,
newOutValue,
newInValues,
isInterface);
iterator.replaceCurrentInstruction(newInvoke);
if (newOutValue != null && newOutValue.getType() != current.getOutType()) {
affectedPhis.addAll(newOutValue.uniquePhiUsers());
}
if (constantReturnMaterializingInstruction != null) {
if (block.hasCatchHandlers()) {
// Split the block to ensure no instructions after throwing instructions.
iterator
.split(code, blocks)
.listIterator(code)
.add(constantReturnMaterializingInstruction);
} else {
iterator.add(constantReturnMaterializingInstruction);
}
}
DexType actualReturnType = actualTarget.proto.returnType;
DexType expectedReturnType = graphLens.lookupType(invokedMethod.proto.returnType);
if (newInvoke.outValue() != null && actualReturnType != expectedReturnType) {
throw new Unreachable(
"Unexpected need to insert a cast. Possibly related to resolving"
+ " b/79143143.\n"
+ invokedMethod
+ " type changed from "
+ expectedReturnType
+ " to "
+ actualReturnType);
}
}
}
break;
case INSTANCE_GET:
{
InstanceGet instanceGet = current.asInstanceGet();
DexField field = instanceGet.getField();
FieldLookupResult lookup = graphLens.lookupFieldResult(field);
DexField rewrittenField = rewriteFieldReference(lookup, method);
DexMethod replacementMethod =
graphLens.lookupGetFieldForMethod(rewrittenField, method.getReference());
Value newOutValue = null;
if (replacementMethod != null) {
newOutValue = makeOutValue(instanceGet, code, rewrittenField);
iterator.replaceCurrentInstruction(
new InvokeStatic(replacementMethod, newOutValue, instanceGet.inValues()));
} else if (rewrittenField != field) {
newOutValue = makeOutValue(instanceGet, code, rewrittenField);
iterator.replaceCurrentInstruction(
new InstanceGet(newOutValue, instanceGet.object(), rewrittenField));
}
if (newOutValue != null) {
if (lookup.hasCastType() && newOutValue.hasNonDebugUsers()) {
TypeElement castType =
TypeElement.fromDexType(
lookup.getCastType(), newOutValue.getType().nullability(), appView);
Value castOutValue = code.createValue(castType);
newOutValue.replaceUsers(castOutValue);
CheckCast checkCast =
SafeCheckCast.builder()
.setCastType(lookup.getCastType())
.setObject(newOutValue)
.setOutValue(castOutValue)
.setPosition(instanceGet)
.build();
iterator.addThrowingInstructionToPossiblyThrowingBlock(
code, blocks, checkCast, options);
affectedPhis.addAll(checkCast.outValue().uniquePhiUsers());
} else if (newOutValue.getType() != instanceGet.getOutType()) {
affectedPhis.addAll(newOutValue.uniquePhiUsers());
}
}
}
break;
case INSTANCE_PUT:
{
InstancePut instancePut = current.asInstancePut();
DexField field = instancePut.getField();
FieldLookupResult lookup = graphLens.lookupFieldResult(field);
DexField rewrittenField = rewriteFieldReference(lookup, method);
DexMethod replacementMethod =
graphLens.lookupPutFieldForMethod(rewrittenField, method.getReference());
if (replacementMethod != null) {
iterator.replaceCurrentInstruction(
new InvokeStatic(replacementMethod, null, instancePut.inValues()));
} else if (rewrittenField != field) {
Value rewrittenValue =
rewriteValueIfDefault(
code, iterator, field.type, rewrittenField.type, instancePut.value());
InstancePut newInstancePut =
InstancePut.createPotentiallyInvalid(
rewrittenField, instancePut.object(), rewrittenValue);
iterator.replaceCurrentInstruction(newInstancePut);
}
}
break;
case STATIC_GET:
{
StaticGet staticGet = current.asStaticGet();
DexField field = staticGet.getField();
FieldLookupResult lookup = graphLens.lookupFieldResult(field);
DexField rewrittenField = rewriteFieldReference(lookup, method);
DexMethod replacementMethod =
graphLens.lookupGetFieldForMethod(rewrittenField, method.getReference());
Value newOutValue = null;
if (replacementMethod != null) {
newOutValue = makeOutValue(staticGet, code, rewrittenField);
iterator.replaceCurrentInstruction(
new InvokeStatic(replacementMethod, newOutValue, staticGet.inValues()));
} else if (rewrittenField != field) {
newOutValue = makeOutValue(staticGet, code, rewrittenField);
iterator.replaceCurrentInstruction(new StaticGet(newOutValue, rewrittenField));
}
if (newOutValue != null) {
if (lookup.hasCastType() && newOutValue.hasNonDebugUsers()) {
TypeElement castType =
TypeElement.fromDexType(
lookup.getCastType(), newOutValue.getType().nullability(), appView);
Value castOutValue = code.createValue(castType);
newOutValue.replaceUsers(castOutValue);
CheckCast checkCast =
SafeCheckCast.builder()
.setCastType(lookup.getCastType())
.setObject(newOutValue)
.setOutValue(castOutValue)
.setPosition(staticGet)
.build();
iterator.addThrowingInstructionToPossiblyThrowingBlock(
code, blocks, checkCast, options);
affectedPhis.addAll(checkCast.outValue().uniquePhiUsers());
} else if (newOutValue.getType() != staticGet.getOutType()) {
affectedPhis.addAll(newOutValue.uniquePhiUsers());
}
}
}
break;
case STATIC_PUT:
{
StaticPut staticPut = current.asStaticPut();
DexField field = staticPut.getField();
FieldLookupResult lookup = graphLens.lookupFieldResult(field);
DexField actualField = rewriteFieldReference(lookup, method);
DexMethod replacementMethod =
graphLens.lookupPutFieldForMethod(actualField, method.getReference());
if (replacementMethod != null) {
iterator.replaceCurrentInstruction(
new InvokeStatic(
replacementMethod, staticPut.outValue(), staticPut.inValues()));
} else if (actualField != field) {
Value rewrittenValue =
rewriteValueIfDefault(
code, iterator, field.type, actualField.type, staticPut.value());
iterator.replaceCurrentInstruction(new StaticPut(rewrittenValue, actualField));
}
}
break;
case CHECK_CAST:
{
CheckCast checkCast = current.asCheckCast();
new InstructionReplacer(code, current, iterator, affectedPhis)
.replaceInstructionIfTypeChanged(
checkCast.getType(), (t, v) -> new CheckCast(v, checkCast.object(), t));
}
break;
case CONST_CLASS:
{
ConstClass constClass = current.asConstClass();
new InstructionReplacer(code, current, iterator, affectedPhis)
.replaceInstructionIfTypeChanged(
constClass.getValue(), (t, v) -> new ConstClass(v, t));
}
break;
case INSTANCE_OF:
{
InstanceOf instanceOf = current.asInstanceOf();
new InstructionReplacer(code, current, iterator, affectedPhis)
.replaceInstructionIfTypeChanged(
instanceOf.type(), (t, v) -> new InstanceOf(v, instanceOf.value(), t));
}
break;
case INVOKE_MULTI_NEW_ARRAY:
{
InvokeMultiNewArray multiNewArray = current.asInvokeMultiNewArray();
new InstructionReplacer(code, current, iterator, affectedPhis)
.replaceInstructionIfTypeChanged(
multiNewArray.getArrayType(),
(t, v) -> new InvokeMultiNewArray(t, v, multiNewArray.inValues()));
}
break;
case INVOKE_NEW_ARRAY:
{
InvokeNewArray newArray = current.asInvokeNewArray();
new InstructionReplacer(code, current, iterator, affectedPhis)
.replaceInstructionIfTypeChanged(
newArray.getArrayType(),
(t, v) -> new InvokeNewArray(t, v, newArray.inValues()));
}
break;
case MOVE_EXCEPTION:
{
MoveException moveException = current.asMoveException();
new InstructionReplacer(code, current, iterator, affectedPhis)
.replaceInstructionIfTypeChanged(
moveException.getExceptionType(), (t, v) -> new MoveException(v, t, options));
}
break;
case NEW_ARRAY_EMPTY:
{
NewArrayEmpty newArrayEmpty = current.asNewArrayEmpty();
new InstructionReplacer(code, current, iterator, affectedPhis)
.replaceInstructionIfTypeChanged(
newArrayEmpty.type, (t, v) -> new NewArrayEmpty(v, newArrayEmpty.size(), t));
}
break;
case NEW_INSTANCE:
{
DexType type = current.asNewInstance().clazz;
new InstructionReplacer(code, current, iterator, affectedPhis)
.replaceInstructionIfTypeChanged(type, NewInstance::new);
}
break;
case RETURN:
{
Return ret = current.asReturn();
if (ret.isReturnVoid()) {
break;
}
DexType returnType = code.method().getReference().proto.returnType;
Value retValue = ret.returnValue();
DexType initialType =
retValue.getType().isPrimitiveType()
? retValue.getType().asPrimitiveType().toDexType(factory)
: factory.objectType; // Place holder, any reference type will do.
Value rewrittenValue =
rewriteValueIfDefault(code, iterator, initialType, returnType, retValue);
if (retValue != rewrittenValue) {
Return newReturn = new Return(rewrittenValue);
iterator.replaceCurrentInstruction(newReturn);
}
}
break;
case ASSUME:
{
// TODO(b/174543992): It's not clear we should rewrite the assumes here. The code
// present fixes the problem for enum unboxing, but not for lambda merging.
// The LensCodeRewriter is run before most assume instructions are inserted, however,
// the call site optimization may propagate assumptions at IR building time, and such
// assumes are already present.
// R8 clears the assumes if the type is rewritten to a primitive type.
Assume assume = current.asAssume();
if (assume.hasOutValue()) {
TypeElement type = assume.getOutType();
TypeElement substituted = type.rewrittenWithLens(appView, graphLens);
if (substituted != type) {
assert type.isArrayType() || type.isClassType();
if (substituted.isPrimitiveType()) {
assert type.isClassType();
assert appView.unboxedEnums().isUnboxedEnum(type.asClassType().getClassType());
// Any assumption of a class type being converted to a primitive type is
// invalid. Dynamic type is irrelevant and non null is incorrect.
assume.outValue().replaceUsers(assume.src());
iterator.removeOrReplaceByDebugLocalRead();
} else if (substituted.isPrimitiveArrayType()) {
assert type.isArrayType();
// Non-null assumptions on a class array type being converted to a primitive
// array type remains, but dynamic type becomes irrelevant.
assume.unsetDynamicTypeAssumption();
if (assume.hasNonNullAssumption()) {
current.outValue().setType(substituted);
affectedPhis.addAll(current.outValue().uniquePhiUsers());
} else {
iterator.removeOrReplaceByDebugLocalRead();
}
} else {
assert !substituted.isPrimitiveType();
assert !substituted.isPrimitiveArrayType();
current.outValue().setType(substituted);
affectedPhis.addAll(current.outValue().uniquePhiUsers());
}
}
}
}
break;
default:
if (current.hasOutValue()) {
// For all other instructions, substitute any changed type.
TypeElement type = current.getOutType();
TypeElement substituted = type.rewrittenWithLens(appView, graphLens);
if (substituted != type) {
current.outValue().setType(substituted);
affectedPhis.addAll(current.outValue().uniquePhiUsers());
}
}
break;
}
}
}
if (mayHaveUnreachableBlocks) {
code.removeUnreachableBlocks();
}
if (!affectedPhis.isEmpty()) {
new DestructivePhiTypeUpdater(appView).recomputeAndPropagateTypes(code, affectedPhis);
}
assert code.isConsistentSSABeforeTypesAreCorrect();
assert code.hasNoVerticallyMergedClasses(appView);
}
private DexField rewriteFieldReference(FieldLookupResult lookup, ProgramMethod context) {
if (lookup.hasReboundReference()) {
DexClass holder = appView.definitionFor(lookup.getReboundReference().getHolderType());
DexEncodedField definition = lookup.getReboundReference().lookupOnClass(holder);
if (definition != null) {
DexClassAndField field = DexClassAndField.create(holder, definition);
if (AccessControl.isMemberAccessible(field, holder, context, appView).isTrue()) {
return MemberRebindingAnalysis.validMemberRebindingTargetFor(
appView, field, lookup.getReference());
}
}
}
return lookup.getReference();
}
// If the initialValue is a default value and its type is rewritten from a reference type to a
// primitive type, then the default value type lattice needs to be changed.
private Value rewriteValueIfDefault(
IRCode code,
InstructionListIterator iterator,
DexType oldType,
DexType newType,
Value initialValue) {
if (initialValue.isConstNumber()
&& initialValue.definition.asConstNumber().isZero()
&& defaultValueHasChanged(oldType, newType)) {
iterator.previous();
Value rewrittenDefaultValue =
iterator.insertConstNumberInstruction(
code, options, 0, defaultValueLatticeElement(newType));
iterator.next();
return rewrittenDefaultValue;
}
return initialValue;
}
private boolean defaultValueHasChanged(DexType oldType, DexType newType) {
if (newType.isPrimitiveType()) {
if (oldType.isPrimitiveType()) {
return ValueType.fromDexType(newType) != ValueType.fromDexType(oldType);
}
return true;
} else if (oldType.isPrimitiveType()) {
return true;
}
// All reference types uses null as default value.
assert newType.isReferenceType();
assert oldType.isReferenceType();
return false;
}
private TypeElement defaultValueLatticeElement(DexType type) {
if (type.isPrimitiveType()) {
return TypeElement.fromDexType(type, null, appView);
}
return TypeElement.getNull();
}
// If the given invoke is on the form "invoke-direct A.<init>, v0, ..." and the definition of
// value v0 is "new-instance v0, B", where B is a subtype of A (see the Art800 and B116282409
// tests), then fail with a compilation error if A has previously been merged into B.
//
// The motivation for this is that the vertical class merger cannot easily recognize the above
// code pattern, since it runs prior to IR construction. Therefore, we currently allow merging
// A and B although this will lead to invalid code, because this code pattern does generally
// not occur in practice (it leads to a verification error on the JVM, but not on Art).
private void checkInvokeDirect(DexMethod method, InvokeDirect invoke) {
VerticallyMergedClasses verticallyMergedClasses = appView.verticallyMergedClasses();
if (verticallyMergedClasses == null) {
// No need to check the invocation.
return;
}
DexMethod invokedMethod = invoke.getInvokedMethod();
if (invokedMethod.name != appView.dexItemFactory().constructorMethodName) {
// Not a constructor call.
return;
}
if (invoke.arguments().isEmpty()) {
// The new instance should always be passed to the constructor call, but continue gracefully.
return;
}
Value receiver = invoke.arguments().get(0);
if (!receiver.isPhi() && receiver.definition.isNewInstance()) {
NewInstance newInstance = receiver.definition.asNewInstance();
if (newInstance.clazz != invokedMethod.holder
&& verticallyMergedClasses.hasBeenMergedIntoSubtype(invokedMethod.holder)) {
// Generated code will not work. Fail with a compilation error.
throw appView
.options()
.reporter
.fatalError(
String.format(
"Unable to rewrite `invoke-direct %s.<init>(new %s, ...)` in method `%s` after "
+ "type `%s` was merged into `%s`. Please add the following rule to your "
+ "Proguard configuration file: `-keep,allowobfuscation class %s`.",
invokedMethod.holder.toSourceString(),
newInstance.clazz,
method.toSourceString(),
invokedMethod.holder,
verticallyMergedClasses.getTargetFor(invokedMethod.holder),
invokedMethod.holder.toSourceString()));
}
}
}
/**
* Due to class merging, it is possible that two exception classes have been merged into one. This
* function removes catch handlers where the guards ended up being the same as a previous one.
*
* @return true if any dead catch handlers were removed.
*/
private boolean unlinkDeadCatchHandlers(BasicBlock block) {
assert block.hasCatchHandlers();
CatchHandlers<BasicBlock> catchHandlers = block.getCatchHandlers();
List<DexType> guards = catchHandlers.getGuards();
List<BasicBlock> targets = catchHandlers.getAllTargets();
Set<DexType> previouslySeenGuards = new HashSet<>();
List<BasicBlock> deadCatchHandlers = new ArrayList<>();
for (int i = 0; i < guards.size(); i++) {
// The type may have changed due to class merging.
DexType guard = appView.graphLens().lookupType(guards.get(i));
boolean guardSeenBefore = !previouslySeenGuards.add(guard);
if (guardSeenBefore) {
deadCatchHandlers.add(targets.get(i));
}
}
// Remove the guards that are guaranteed to be dead.
for (BasicBlock deadCatchHandler : deadCatchHandlers) {
deadCatchHandler.unlinkCatchHandler();
}
assert block.consistentCatchHandlers();
return !deadCatchHandlers.isEmpty();
}
class InstructionReplacer {
private final IRCode code;
private final Instruction current;
private final InstructionListIterator iterator;
private final Set<Phi> affectedPhis;
InstructionReplacer(
IRCode code, Instruction current, InstructionListIterator iterator, Set<Phi> affectedPhis) {
this.code = code;
this.current = current;
this.iterator = iterator;
this.affectedPhis = affectedPhis;
}
void replaceInstructionIfTypeChanged(
DexType type, BiFunction<DexType, Value, Instruction> constructor) {
DexType newType = appView.graphLens().lookupType(type);
if (newType != type) {
Value newOutValue = makeOutValue(current, code);
Instruction newInstruction = constructor.apply(newType, newOutValue);
iterator.replaceCurrentInstruction(newInstruction);
if (newOutValue != null) {
if (newOutValue.getType() != current.getOutType()) {
affectedPhis.addAll(newOutValue.uniquePhiUsers());
} else {
assert current.hasInvariantOutType();
assert current.isConstClass()
|| current.isInitClass()
|| current.isInstanceOf()
|| (current.isInvokeVirtual()
&& current.asInvokeVirtual().getInvokedMethod().holder.isArrayType());
}
}
}
}
}
}