|  | // 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.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 = | 
|  | CheckCast.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 = | 
|  | CheckCast.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.getType().isNullType() && defaultValueHasChanged(oldType, newType)) { | 
|  | assert newType.isIntType(); | 
|  | 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()); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } |