|  | // Copyright (c) 2016, 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 com.android.tools.r8.dex.Constants; | 
|  | import com.android.tools.r8.errors.CompilationError; | 
|  | import com.android.tools.r8.errors.Unreachable; | 
|  | import com.android.tools.r8.graph.DebugLocalInfo; | 
|  | import com.android.tools.r8.graph.DexCallSite; | 
|  | 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.DexProto; | 
|  | import com.android.tools.r8.graph.DexType; | 
|  | import com.android.tools.r8.graph.JarApplicationReader; | 
|  | import com.android.tools.r8.ir.analysis.type.TypeLatticeElement; | 
|  | import com.android.tools.r8.ir.code.CatchHandlers; | 
|  | import com.android.tools.r8.ir.code.Cmp.Bias; | 
|  | import com.android.tools.r8.ir.code.If; | 
|  | import com.android.tools.r8.ir.code.Invoke; | 
|  | import com.android.tools.r8.ir.code.MemberType; | 
|  | import com.android.tools.r8.ir.code.Monitor; | 
|  | import com.android.tools.r8.ir.code.NumericType; | 
|  | import com.android.tools.r8.ir.code.Position; | 
|  | import com.android.tools.r8.ir.code.ValueType; | 
|  | import com.android.tools.r8.ir.code.ValueTypeConstraint; | 
|  | import com.android.tools.r8.ir.conversion.IRBuilder.BlockInfo; | 
|  | import com.android.tools.r8.ir.conversion.JarState.Local; | 
|  | import com.android.tools.r8.ir.conversion.JarState.LocalChangeAtOffset; | 
|  | import com.android.tools.r8.ir.conversion.JarState.Slot; | 
|  | import com.android.tools.r8.logging.Log; | 
|  | import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; | 
|  | import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; | 
|  | import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap; | 
|  | import it.unimi.dsi.fastutil.ints.IntOpenHashSet; | 
|  | import it.unimi.dsi.fastutil.ints.IntSet; | 
|  | import java.io.PrintWriter; | 
|  | import java.io.StringWriter; | 
|  | import java.util.ArrayList; | 
|  | import java.util.Arrays; | 
|  | import java.util.HashSet; | 
|  | import java.util.Iterator; | 
|  | import java.util.LinkedList; | 
|  | import java.util.List; | 
|  | import java.util.Queue; | 
|  | import java.util.Set; | 
|  | import java.util.function.BiConsumer; | 
|  | import org.objectweb.asm.Handle; | 
|  | import org.objectweb.asm.Opcodes; | 
|  | import org.objectweb.asm.Type; | 
|  | import org.objectweb.asm.tree.AbstractInsnNode; | 
|  | import org.objectweb.asm.tree.FieldInsnNode; | 
|  | import org.objectweb.asm.tree.IincInsnNode; | 
|  | import org.objectweb.asm.tree.InsnNode; | 
|  | import org.objectweb.asm.tree.IntInsnNode; | 
|  | import org.objectweb.asm.tree.InvokeDynamicInsnNode; | 
|  | import org.objectweb.asm.tree.JumpInsnNode; | 
|  | import org.objectweb.asm.tree.LabelNode; | 
|  | import org.objectweb.asm.tree.LdcInsnNode; | 
|  | import org.objectweb.asm.tree.LineNumberNode; | 
|  | import org.objectweb.asm.tree.LocalVariableNode; | 
|  | import org.objectweb.asm.tree.LookupSwitchInsnNode; | 
|  | import org.objectweb.asm.tree.MethodInsnNode; | 
|  | import org.objectweb.asm.tree.MethodNode; | 
|  | import org.objectweb.asm.tree.MultiANewArrayInsnNode; | 
|  | import org.objectweb.asm.tree.TableSwitchInsnNode; | 
|  | import org.objectweb.asm.tree.TryCatchBlockNode; | 
|  | import org.objectweb.asm.tree.TypeInsnNode; | 
|  | import org.objectweb.asm.tree.VarInsnNode; | 
|  | import org.objectweb.asm.util.Textifier; | 
|  | import org.objectweb.asm.util.TraceMethodVisitor; | 
|  |  | 
|  | public class JarSourceCode implements SourceCode { | 
|  |  | 
|  | // Try-catch block wrapper containing resolved offsets. | 
|  | private static class TryCatchBlock { | 
|  |  | 
|  | private final int handler; | 
|  | private final int start; | 
|  | private final int end; | 
|  |  | 
|  | private final String type; | 
|  |  | 
|  | public TryCatchBlock(TryCatchBlockNode node, JarSourceCode code) { | 
|  | this(code.getOffset(node.handler), | 
|  | code.getOffset(node.start), | 
|  | code.getOffset(node.end), | 
|  | node.type); | 
|  | } | 
|  |  | 
|  | private TryCatchBlock(int handler, int start, int end, String type) { | 
|  | assert start < end; | 
|  | this.handler = handler; | 
|  | this.start = start; | 
|  | this.end = end; | 
|  | this.type = type; | 
|  | } | 
|  |  | 
|  | int getStart() { | 
|  | return start; | 
|  | } | 
|  |  | 
|  | int getEnd() { | 
|  | return end; | 
|  | } | 
|  |  | 
|  | int getHandler() { | 
|  | return handler; | 
|  | } | 
|  |  | 
|  | String getType() { | 
|  | return type; | 
|  | } | 
|  | } | 
|  |  | 
|  | private static class JarStateWorklistItem { | 
|  | BlockInfo blockInfo; | 
|  | int instructionIndex; | 
|  |  | 
|  | public JarStateWorklistItem(BlockInfo blockInfo, int instructionIndex) { | 
|  | this.blockInfo = blockInfo; | 
|  | this.instructionIndex = instructionIndex; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Various descriptors. | 
|  | private static final String INT_ARRAY_DESC = "[I"; | 
|  | private static final String REFLECT_ARRAY_DESC = "Ljava/lang/reflect/Array;"; | 
|  | private static final String REFLECT_ARRAY_NEW_INSTANCE_NAME = "newInstance"; | 
|  | private static final String REFLECT_ARRAY_NEW_INSTANCE_DESC = | 
|  | "(Ljava/lang/Class;[I)Ljava/lang/Object;"; | 
|  | private static final String POLYMORPHIC_DEFAULT_SIGNATURE_DESC = | 
|  | "([Ljava/lang/Object;)Ljava/lang/Object;"; | 
|  | private static final String POLYMORPHIC_VARHANDLE_SET_SIGNATURE_DESC = | 
|  | "([Ljava/lang/Object;)V"; | 
|  | private static final String POLYMORPHIC_VARHANDLE_COMPARE_AND_SET_SIGNATURE_DESC = | 
|  | "([Ljava/lang/Object;)Z"; | 
|  |  | 
|  | // Various internal names. | 
|  | public static final String INTERNAL_NAME_METHOD_HANDLE = "java/lang/invoke/MethodHandle"; | 
|  | public static final String INTERNAL_NAME_VAR_HANDLE = "java/lang/invoke/VarHandle"; | 
|  |  | 
|  | // Language types. | 
|  | static final Type CLASS_TYPE = Type.getObjectType("java/lang/Class"); | 
|  | static final Type STRING_TYPE = Type.getObjectType("java/lang/String"); | 
|  | static final Type INT_ARRAY_TYPE = Type.getObjectType(INT_ARRAY_DESC); | 
|  | static final Type THROWABLE_TYPE = Type.getObjectType("java/lang/Throwable"); | 
|  | static final Type METHOD_HANDLE_TYPE = Type.getObjectType(INTERNAL_NAME_METHOD_HANDLE); | 
|  | static final Type METHOD_TYPE_TYPE = Type.getObjectType("java/lang/invoke/MethodType"); | 
|  |  | 
|  | private static final int[] NO_TARGETS = {}; | 
|  |  | 
|  | private final JarApplicationReader application; | 
|  | private final MethodNode node; | 
|  | private final DexType clazz; | 
|  | private final List<Type> parameterTypes; | 
|  | private final LabelNode initialLabel; | 
|  |  | 
|  | private TraceMethodVisitor printVisitor = null; | 
|  |  | 
|  | private final JarState state; | 
|  | private AbstractInsnNode currentInstruction = null; | 
|  |  | 
|  | // Special try-catch block for synchronized methods. | 
|  | // This block is given a negative instruction index as it is not part of the instruction stream. | 
|  | // The start range of 0 ensures that a new block will start at the first real instruction and | 
|  | // thus that the monitor-entry prelude (part of the argument block which must not have a try-catch | 
|  | // successor) is not joined with the first instruction block (which likely will have a try-catch | 
|  | // successor). | 
|  | private static final int EXCEPTIONAL_SYNC_EXIT_OFFSET = -2; | 
|  | private static final TryCatchBlock EXCEPTIONAL_SYNC_EXIT = | 
|  | new TryCatchBlock(EXCEPTIONAL_SYNC_EXIT_OFFSET, 0, Integer.MAX_VALUE, null); | 
|  |  | 
|  | // Instruction that enters the monitor. Null if the method is not synchronized. | 
|  | private Monitor monitorEnter = null; | 
|  |  | 
|  | // State to signal that the code currently being emitted is part of synchronization prelude/exits. | 
|  | private boolean generatingMethodSynchronization = false; | 
|  |  | 
|  | // Current position associated with the current instruction during building. | 
|  | private Position currentPosition; | 
|  |  | 
|  | // Canonicalized positions to lower memory usage. | 
|  | private final Int2ReferenceMap<Position> canonicalPositions = new Int2ReferenceOpenHashMap<>(); | 
|  |  | 
|  | // Synthetic position with line = 0. | 
|  | private Position preamblePosition = null; | 
|  |  | 
|  | // Cooked position to indicate positions in synthesized code (ie, for synchronization). | 
|  | private Position syntheticPosition = null; | 
|  |  | 
|  | private final DexMethod originalMethod; | 
|  | private final Position callerPosition; | 
|  |  | 
|  | private boolean hasExitingInstruction = false; | 
|  |  | 
|  | private boolean debug; | 
|  |  | 
|  | public JarSourceCode( | 
|  | DexType clazz, | 
|  | MethodNode node, | 
|  | JarApplicationReader application, | 
|  | DexMethod originalMethod, | 
|  | Position callerPosition) { | 
|  | assert node != null; | 
|  | assert node.desc != null; | 
|  | this.node = node; | 
|  | this.application = application; | 
|  | this.originalMethod = originalMethod; | 
|  | this.clazz = clazz; | 
|  | this.callerPosition = callerPosition; | 
|  | parameterTypes = Arrays.asList(application.getArgumentTypes(node.desc)); | 
|  | state = new JarState(node.maxLocals, node.localVariables, this, application); | 
|  | AbstractInsnNode first = node.instructions.getFirst(); | 
|  | initialLabel = first instanceof LabelNode ? (LabelNode) first : null; | 
|  | } | 
|  |  | 
|  | private boolean isStatic() { | 
|  | return (node.access & Opcodes.ACC_STATIC) > 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Determine if we should emit monitor enter/exit instructions at method entry/exit. | 
|  | * | 
|  | * @return true if we are generating Dex and method is marked synchronized, otherwise false. | 
|  | */ | 
|  | private boolean generateMethodSynchronization() { | 
|  | // When generating class files, don't treat the method specially because it is synchronized. | 
|  | // At runtime, the JVM will automatically perform the correct monitor enter/exit instructions. | 
|  | return !application.options.isGeneratingClassFiles() | 
|  | && (node.access & Opcodes.ACC_SYNCHRONIZED) > 0; | 
|  | } | 
|  |  | 
|  | private int formalParameterCount() { | 
|  | return parameterTypes.size(); | 
|  | } | 
|  |  | 
|  | private int actualArgumentCount() { | 
|  | return isStatic() ? formalParameterCount() : formalParameterCount() + 1; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int instructionCount() { | 
|  | return node.instructions.size(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int instructionIndex(int instructionOffset) { | 
|  | return instructionOffset; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int instructionOffset(int instructionIndex) { | 
|  | return instructionIndex; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean verifyRegister(int register) { | 
|  | // The register set is dynamically managed by the state so we assume all values valid here. | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void setUp() { | 
|  | if (Log.ENABLED) { | 
|  | Log.debug(JarSourceCode.class, "Computing blocks for:\n" + toString()); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void clear() { | 
|  |  | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void buildPrelude(IRBuilder builder) { | 
|  | debug = builder.isDebugMode(); | 
|  | currentPosition = getPreamblePosition(); | 
|  |  | 
|  | state.beginTransactionSynthetic(); | 
|  |  | 
|  | // Record types for arguments. | 
|  | Int2ReferenceMap<Type> argumentLocals = recordArgumentTypes(builder); | 
|  | IntSet initializedLocals = new IntOpenHashSet(node.localVariables.size()); | 
|  | initializedLocals.addAll(argumentLocals.keySet()); | 
|  | // Initialize all non-argument locals to ensure safe insertion of debug-local instructions. | 
|  | for (Object o : node.localVariables) { | 
|  | LocalVariableNode local = (LocalVariableNode) o; | 
|  | Type localType; | 
|  | switch (application.getAsmType(local.desc).getSort()) { | 
|  | case Type.OBJECT: | 
|  | case Type.ARRAY: { | 
|  | localType = JarState.NULL_TYPE; | 
|  | break; | 
|  | } | 
|  | case Type.LONG: { | 
|  | localType = Type.LONG_TYPE; | 
|  | break; | 
|  | } | 
|  | case Type.DOUBLE: { | 
|  | localType = Type.DOUBLE_TYPE; | 
|  | break; | 
|  | } | 
|  | case Type.BOOLEAN: | 
|  | case Type.CHAR: | 
|  | case Type.BYTE: | 
|  | case Type.SHORT: | 
|  | case Type.INT: { | 
|  | localType = Type.INT_TYPE; | 
|  | break; | 
|  | } | 
|  | case Type.FLOAT: { | 
|  | localType = Type.FLOAT_TYPE; | 
|  | break; | 
|  | } | 
|  | case Type.VOID: | 
|  | case Type.METHOD: | 
|  | default: | 
|  | throw new Unreachable("Invalid local variable type: " ); | 
|  | } | 
|  | int localRegister = state.getLocalRegister(local.index, localType); | 
|  | if (!initializedLocals.contains(localRegister)) { | 
|  | int writeRegister = state.writeLocal(local.index, localType); | 
|  | assert writeRegister == localRegister; | 
|  | initializedLocals.add(localRegister); | 
|  | } | 
|  | } | 
|  |  | 
|  | state.endTransaction(); | 
|  | state.beginTransaction(0, true); | 
|  |  | 
|  | // Build the actual argument instructions now that type and debug information is known | 
|  | // for arguments. | 
|  | buildArgumentInstructions(builder); | 
|  |  | 
|  | // Add debug information for all locals at the initial label. | 
|  | for (Local local : state.getLocalsToOpen()) { | 
|  | if (!argumentLocals.containsKey(local.slot.register)) { | 
|  | builder.addDebugLocalStart(local.slot.register, local.info); | 
|  | } | 
|  | } | 
|  | state.endTransaction(); | 
|  |  | 
|  | if (generateMethodSynchronization()) { | 
|  | generatingMethodSynchronization = true; | 
|  | Type clazzType = application.getAsmType(clazz.toDescriptorString()); | 
|  | int monitorRegister; | 
|  | if (isStatic()) { | 
|  | // Load the class using a temporary on the stack. | 
|  | monitorRegister = state.push(clazzType); | 
|  | state.pop(); | 
|  | builder.addConstClass(monitorRegister, clazz); | 
|  | } else { | 
|  | assert actualArgumentCount() > 0; | 
|  | // The object is stored in the first local. | 
|  | monitorRegister = state.readLocal(0, clazzType).register; | 
|  | } | 
|  | // Build the monitor enter and save it for when generating exits later. | 
|  | monitorEnter = builder.addMonitor(Monitor.Type.ENTER, monitorRegister); | 
|  | generatingMethodSynchronization = false; | 
|  | } | 
|  | computeBlockEntryJarStates(builder); | 
|  | state.setBuilding(); | 
|  | } | 
|  |  | 
|  | private void buildArgumentInstructions(IRBuilder builder) { | 
|  | int argumentRegister = 0; | 
|  | if (!isStatic()) { | 
|  | Type thisType = application.getAsmType(clazz.descriptor.toString()); | 
|  | Slot slot = state.readLocal(argumentRegister++, thisType); | 
|  | builder.addThisArgument(slot.register); | 
|  | } | 
|  | for (Type type : parameterTypes) { | 
|  | TypeLatticeElement typeLattice = builder.getTypeLattice(application.getType(type), true); | 
|  | Slot slot = state.readLocal(argumentRegister, type); | 
|  | if (type == Type.BOOLEAN_TYPE) { | 
|  | builder.addBooleanNonThisArgument(slot.register); | 
|  | } else { | 
|  | builder.addNonThisArgument(slot.register, typeLattice); | 
|  | } | 
|  | argumentRegister += typeLattice.requiredRegisters(); | 
|  | } | 
|  | builder.flushArgumentInstructions(); | 
|  | } | 
|  |  | 
|  | private Int2ReferenceMap<Type> recordArgumentTypes(IRBuilder builder) { | 
|  | Int2ReferenceMap<Type> initializedLocals = | 
|  | new Int2ReferenceOpenHashMap<>(node.localVariables.size()); | 
|  | int argumentRegister = 0; | 
|  | if (!isStatic()) { | 
|  | Type thisType = application.getAsmType(clazz.descriptor.toString()); | 
|  | int register = state.writeLocal(argumentRegister++, thisType); | 
|  | initializedLocals.put(register, thisType); | 
|  | } | 
|  | for (Type type : parameterTypes) { | 
|  | int register = state.writeLocal(argumentRegister, type); | 
|  | argumentRegister += isWide(type) ? 2 : 1; | 
|  | initializedLocals.put(register, type); | 
|  | } | 
|  | return initializedLocals; | 
|  | } | 
|  |  | 
|  | private boolean isWide(Type type) { | 
|  | return type.getSort() == Type.DOUBLE || type.getSort() == Type.LONG; | 
|  | } | 
|  |  | 
|  | private void computeBlockEntryJarStates(IRBuilder builder) { | 
|  | hasExitingInstruction = false; | 
|  | Int2ReferenceSortedMap<BlockInfo> CFG = builder.getCFG(); | 
|  | Queue<JarStateWorklistItem> worklist = new LinkedList<>(); | 
|  | BlockInfo entry = CFG.get(IRBuilder.INITIAL_BLOCK_OFFSET); | 
|  | if (CFG.get(0) != null) { | 
|  | entry = CFG.get(0); | 
|  | } | 
|  | worklist.add(new JarStateWorklistItem(entry, 0)); | 
|  | state.recordStateForTarget(0); | 
|  | for (JarStateWorklistItem item = worklist.poll(); item != null; item = worklist.poll()) { | 
|  | state.restoreState(item.instructionIndex); | 
|  | state.beginTransactionAtBlockStart(item.instructionIndex); | 
|  | state.endTransaction(); | 
|  | // Iterate each of the instructions in the block to compute the outgoing JarState. | 
|  | int instCount = instructionCount(); | 
|  | int blockEnd = item.instructionIndex + 1; | 
|  | while (blockEnd < instCount && !CFG.containsKey(blockEnd)) { | 
|  | blockEnd += 1; | 
|  | } | 
|  | for (int i = item.instructionIndex; i < blockEnd; ++i) { | 
|  | boolean hasNextInstruction = i + 1 != blockEnd; | 
|  | state.beginTransaction(i + 1, hasNextInstruction); | 
|  | AbstractInsnNode insn = getInstruction(i); | 
|  | updateState(insn); | 
|  | state.endTransaction(); | 
|  | if (!hasNextInstruction) { | 
|  | hasExitingInstruction |= | 
|  | isReturn(insn) || (isThrow(insn) && item.blockInfo.exceptionalSuccessors.isEmpty()); | 
|  | } | 
|  | } | 
|  | // At the end of the current block, propagate the state to all successors and add the ones | 
|  | // that changed to the worklist. | 
|  | item.blockInfo.normalSuccessors.iterator().forEachRemaining((Integer offset) -> { | 
|  | if (state.recordStateForTarget(offset)) { | 
|  | if (offset >= 0) { | 
|  | worklist.add(new JarStateWorklistItem(CFG.get(offset.intValue()), offset)); | 
|  | } | 
|  | } | 
|  | }); | 
|  | item.blockInfo.exceptionalSuccessors.iterator().forEachRemaining((Integer offset) -> { | 
|  | if (state.recordStateForExceptionalTarget(offset)) { | 
|  | if (offset >= 0) { | 
|  | worklist.add(new JarStateWorklistItem(CFG.get(offset.intValue()), offset)); | 
|  | } | 
|  | } | 
|  | }); | 
|  | } | 
|  | state.restoreState(0); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void buildPostlude(IRBuilder builder) { | 
|  | if (generateMethodSynchronization()) { | 
|  | generatingMethodSynchronization = true; | 
|  | buildMonitorExit(builder); | 
|  | generatingMethodSynchronization = false; | 
|  | } | 
|  | } | 
|  |  | 
|  | private void buildExceptionalPostlude(IRBuilder builder) { | 
|  | assert generateMethodSynchronization(); | 
|  | generatingMethodSynchronization = true; | 
|  | currentPosition = getExceptionalExitPosition(); | 
|  | buildMonitorExit(builder); | 
|  | builder.addThrow(getMoveExceptionRegister()); | 
|  | generatingMethodSynchronization = false; | 
|  | } | 
|  |  | 
|  | private void buildMonitorExit(IRBuilder builder) { | 
|  | assert generatingMethodSynchronization; | 
|  | builder.add(new Monitor(Monitor.Type.EXIT, monitorEnter.inValues().get(0))); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void buildBlockTransfer( | 
|  | IRBuilder builder, int predecessorOffset, int successorOffset, boolean isExceptional) { | 
|  | assert currentInstruction == null || predecessorOffset == getOffset(currentInstruction); | 
|  | currentInstruction = null; | 
|  | if (predecessorOffset == IRBuilder.INITIAL_BLOCK_OFFSET | 
|  | || successorOffset == EXCEPTIONAL_SYNC_EXIT_OFFSET) { | 
|  | return; | 
|  | } | 
|  | // The transfer has not yet taken place, so the current position is that of the predecessor, | 
|  | // except for exceptional edges where the transfer has already taken place. | 
|  | currentPosition = | 
|  | getCanonicalDebugPositionAtOffset(isExceptional ? successorOffset : predecessorOffset); | 
|  |  | 
|  | LocalChangeAtOffset localChange = state.getLocalChange(predecessorOffset, successorOffset); | 
|  | if (!isExceptional) { | 
|  | for (Local toClose : localChange.getLocalsToClose()) { | 
|  | builder.addDebugLocalEnd(toClose.slot.register, toClose.info); | 
|  | } | 
|  | } | 
|  | for (Local toOpen : localChange.getLocalsToOpen()) { | 
|  | builder.addDebugLocalStart(toOpen.slot.register, toOpen.info); | 
|  | } | 
|  |  | 
|  | // If there are no explicit exits from the method (ie, this method is a loop without an explict | 
|  | // return or an unhandled throw) then we cannot guarantee that a local live in a successor will | 
|  | // ensure it is marked as such (via an explict 'end' marker) and thus be live in predecessors. | 
|  | // In this case we insert an 'end' point on all explicit goto instructions ensuring that any | 
|  | // back-edge will explicitly keep locals live at that point. | 
|  | if (!hasExitingInstruction && getInstruction(predecessorOffset).getOpcode() == Opcodes.GOTO) { | 
|  | assert !isExceptional; | 
|  | for (Local local : localChange.getLocalsToPreserve()) { | 
|  | builder.addDebugLocalEnd(local.slot.register, local.info); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void buildInstruction( | 
|  | IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) { | 
|  | if (instructionIndex == EXCEPTIONAL_SYNC_EXIT_OFFSET) { | 
|  | buildExceptionalPostlude(builder); | 
|  | return; | 
|  | } | 
|  | AbstractInsnNode insn = getInstruction(instructionIndex); | 
|  | currentInstruction = insn; | 
|  | assert verifyExceptionEdgesAreRecorded(insn); | 
|  |  | 
|  | // If a new block is starting here, we restore the computed JarState. | 
|  | // current position needs to be compute only for the first instruction of a block, thereafter | 
|  | // current position will be updated by LineNumberNode into this block. | 
|  | if (firstBlockInstruction || instructionIndex == 0) { | 
|  | state.restoreState(instructionIndex); | 
|  | currentPosition = getCanonicalDebugPositionAtOffset(instructionIndex); | 
|  | } | 
|  |  | 
|  | String preInstructionState = ""; | 
|  | if (Log.ENABLED) { | 
|  | preInstructionState = state.toString(); | 
|  | } | 
|  |  | 
|  | boolean hasNextInstruction = | 
|  | instructionIndex + 1 != instructionCount() | 
|  | && !builder.getCFG().containsKey(instructionIndex + 1); | 
|  | state.beginTransaction(instructionIndex + 1, hasNextInstruction); | 
|  | if (hasNextInstruction) { | 
|  | // Explicitly end all locals ending at this point. | 
|  | for (Local local : state.getLocalsToClose()) { | 
|  | builder.addDebugLocalEnd(local.slot.register, local.info); | 
|  | } | 
|  | } | 
|  | build(insn, builder); | 
|  | // If the block continues past this instruction then local state should be updated. | 
|  | if (hasNextInstruction) { | 
|  | // Ensure starts of locals starting at this point. | 
|  | for (Local local : state.getLocalsToOpen()) { | 
|  | builder.addDebugLocalStart(local.slot.register, local.info); | 
|  | } | 
|  | } | 
|  | state.endTransaction(); | 
|  |  | 
|  | if (Log.ENABLED && !(insn instanceof LineNumberNode)) { | 
|  | int offset = getOffset(insn); | 
|  | if (insn instanceof LabelNode) { | 
|  | Log.debug(getClass(), "\n%4d: %s", | 
|  | offset, instructionToString(insn).replace('\n', ' ')); | 
|  | } else { | 
|  | Log.debug(getClass(), "\n%4d: %s          pre:  %s\n          post: %s", | 
|  | offset, instructionToString(insn), preInstructionState, state); | 
|  | } | 
|  | } | 
|  |  | 
|  | currentInstruction = null; | 
|  | } | 
|  |  | 
|  | private boolean verifyExceptionEdgesAreRecorded(AbstractInsnNode insn) { | 
|  | if (canThrow(insn)) { | 
|  | for (TryCatchBlock tryCatchBlock : getTryHandlers(insn)) { | 
|  | assert tryCatchBlock.getHandler() == EXCEPTIONAL_SYNC_EXIT_OFFSET | 
|  | || state.hasState(tryCatchBlock.getHandler()); | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void resolveAndBuildSwitch(int value, int fallthroughOffset, | 
|  | int payloadOffset, IRBuilder builder) { | 
|  | throw new Unreachable(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void resolveAndBuildNewArrayFilledData(int arrayRef, int payloadOffset, | 
|  | IRBuilder builder) { | 
|  | throw new Unreachable(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public DebugLocalInfo getIncomingLocalAtBlock(int register, int blockOffset) { | 
|  | return blockOffset == EXCEPTIONAL_SYNC_EXIT_OFFSET | 
|  | ? null | 
|  | : state.getIncomingLocalAtBlock(register, blockOffset); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public DebugLocalInfo getIncomingLocal(int register) { | 
|  | return generatingMethodSynchronization ? null : state.getIncomingLocalInfoForRegister(register); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public DebugLocalInfo getOutgoingLocal(int register) { | 
|  | return generatingMethodSynchronization ? null : state.getOutgoingLocalInfoForRegister(register); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public CatchHandlers<Integer> getCurrentCatchHandlers() { | 
|  | if (generatingMethodSynchronization) { | 
|  | return null; | 
|  | } | 
|  | List<TryCatchBlock> tryCatchBlocks = getTryHandlers(currentInstruction); | 
|  | if (tryCatchBlocks.isEmpty()) { | 
|  | return null; | 
|  | } | 
|  | // TODO(zerny): Compute this more efficiently. | 
|  | return new CatchHandlers<>( | 
|  | getTryHandlerGuards(tryCatchBlocks), | 
|  | getTryHandlerOffsets(tryCatchBlocks)); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int getMoveExceptionRegister(int instructionIndex) { | 
|  | return getMoveExceptionRegister(); | 
|  | } | 
|  |  | 
|  | // In classfiles the register is always on top of stack. | 
|  | private int getMoveExceptionRegister() { | 
|  | return state.startOfStack; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean verifyCurrentInstructionCanThrow() { | 
|  | return generatingMethodSynchronization || canThrow(currentInstruction); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public boolean verifyLocalInScope(DebugLocalInfo local) { | 
|  | for (Local open : state.getLocals()) { | 
|  | if (open.info != null && open.info.name == local.name) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | private AbstractInsnNode getInstruction(int index) { | 
|  | return node.instructions.get(index); | 
|  | } | 
|  |  | 
|  | private static boolean isReturn(AbstractInsnNode insn) { | 
|  | return Opcodes.IRETURN <= insn.getOpcode() && insn.getOpcode() <= Opcodes.RETURN; | 
|  | } | 
|  |  | 
|  | private static boolean isSwitch(AbstractInsnNode insn) { | 
|  | return Opcodes.TABLESWITCH == insn.getOpcode() || insn.getOpcode() == Opcodes.LOOKUPSWITCH; | 
|  | } | 
|  |  | 
|  | private static boolean isThrow(AbstractInsnNode insn) { | 
|  | return Opcodes.ATHROW == insn.getOpcode(); | 
|  | } | 
|  |  | 
|  | private static boolean isControlFlowInstruction(AbstractInsnNode insn) { | 
|  | return isReturn(insn) || isThrow(insn) || isSwitch(insn) || (insn instanceof JumpInsnNode) | 
|  | || insn.getOpcode() == Opcodes.RET; | 
|  | } | 
|  |  | 
|  | private boolean canThrow(AbstractInsnNode insn) { | 
|  | switch (insn.getOpcode()) { | 
|  | case Opcodes.AALOAD: | 
|  | case Opcodes.AASTORE: | 
|  | case Opcodes.ANEWARRAY: | 
|  | // ARETURN does not throw in its dex image. | 
|  | case Opcodes.ARRAYLENGTH: | 
|  | case Opcodes.ATHROW: | 
|  | case Opcodes.BALOAD: | 
|  | case Opcodes.BASTORE: | 
|  | case Opcodes.CALOAD: | 
|  | case Opcodes.CASTORE: | 
|  | case Opcodes.CHECKCAST: | 
|  | case Opcodes.DALOAD: | 
|  | case Opcodes.DASTORE: | 
|  | // DRETURN does not throw in its dex image. | 
|  | case Opcodes.FALOAD: | 
|  | case Opcodes.FASTORE: | 
|  | // FRETURN does not throw in its dex image. | 
|  | case Opcodes.GETFIELD: | 
|  | case Opcodes.GETSTATIC: | 
|  | case Opcodes.IALOAD: | 
|  | case Opcodes.IASTORE: | 
|  | case Opcodes.IDIV: | 
|  | case Opcodes.INSTANCEOF: | 
|  | case Opcodes.INVOKEDYNAMIC: | 
|  | case Opcodes.INVOKEINTERFACE: | 
|  | case Opcodes.INVOKESPECIAL: | 
|  | case Opcodes.INVOKESTATIC: | 
|  | case Opcodes.INVOKEVIRTUAL: | 
|  | case Opcodes.IREM: | 
|  | // IRETURN does not throw in its dex image. | 
|  | case Opcodes.LALOAD: | 
|  | case Opcodes.LASTORE: | 
|  | case Opcodes.LDIV: | 
|  | case Opcodes.LREM: | 
|  | // LRETURN does not throw in its dex image. | 
|  | case Opcodes.MONITORENTER: | 
|  | case Opcodes.MONITOREXIT: | 
|  | case Opcodes.MULTIANEWARRAY: | 
|  | case Opcodes.NEW: | 
|  | case Opcodes.NEWARRAY: | 
|  | case Opcodes.PUTFIELD: | 
|  | case Opcodes.PUTSTATIC: | 
|  | // RETURN does not throw in its dex image. | 
|  | case Opcodes.SALOAD: | 
|  | case Opcodes.SASTORE: | 
|  | return true; | 
|  | case Opcodes.LDC: { | 
|  | // const-class and const-string* may throw in dex. | 
|  | LdcInsnNode ldc = (LdcInsnNode) insn; | 
|  | return (ldc.cst instanceof String && !application.options.isGeneratingClassFiles()) | 
|  | || ldc.cst instanceof Type | 
|  | || ldc.cst instanceof Handle; | 
|  | } | 
|  | default: | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public int traceInstruction(int index, IRBuilder builder) { | 
|  | AbstractInsnNode insn = getInstruction(index); | 
|  | // Exit early on no-op instructions. | 
|  | if (insn instanceof LabelNode || insn instanceof LineNumberNode) { | 
|  | return -1; | 
|  | } | 
|  | // If this instruction exits, close this block. | 
|  | if (isReturn(insn)) { | 
|  | return index; | 
|  | } | 
|  | // For each target ensure a basic block and close this block. | 
|  | int[] targets = getTargets(insn); | 
|  | if (targets != NO_TARGETS) { | 
|  | assert !canThrow(insn); | 
|  | for (int target : targets) { | 
|  | builder.ensureNormalSuccessorBlock(index, target); | 
|  | } | 
|  | return index; | 
|  | } | 
|  | if (canThrow(insn)) { | 
|  | List<TryCatchBlock> tryCatchBlocks = getTryHandlers(insn); | 
|  | if (!tryCatchBlocks.isEmpty()) { | 
|  | Set<Integer> seenHandlerOffsets = new HashSet<>(); | 
|  | for (TryCatchBlock tryCatchBlock : tryCatchBlocks) { | 
|  | // Ensure the block starts at the start of the try-range (don't enqueue, not a target). | 
|  | builder.ensureBlockWithoutEnqueuing(tryCatchBlock.getStart()); | 
|  | // Add edge to exceptional successor (only one edge for each unique successor). | 
|  | int handler = tryCatchBlock.getHandler(); | 
|  | if (!seenHandlerOffsets.contains(handler)) { | 
|  | seenHandlerOffsets.add(handler); | 
|  | builder.ensureExceptionalSuccessorBlock(index, handler); | 
|  | } | 
|  | } | 
|  | // Edge to normal successor if any (fallthrough). | 
|  | if (!isThrow(insn)) { | 
|  | builder.ensureNormalSuccessorBlock(index, getOffset(insn.getNext())); | 
|  | } | 
|  | return index; | 
|  | } | 
|  | // If the throwable instruction is "throw" it closes the block. | 
|  | return isThrow(insn) ? index : -1; | 
|  | } | 
|  | // This instruction does not close the block. | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | private List<TryCatchBlock> getPotentialTryHandlers(AbstractInsnNode insn) { | 
|  | int offset = getOffset(insn); | 
|  | return getPotentialTryHandlers(offset); | 
|  | } | 
|  |  | 
|  | private boolean tryBlockRelevant(TryCatchBlockNode tryHandler, int offset) { | 
|  | int start = getOffset(tryHandler.start); | 
|  | int end = getOffset(tryHandler.end); | 
|  | return start <= offset && offset < end; | 
|  | } | 
|  |  | 
|  | private List<TryCatchBlock> getPotentialTryHandlers(int offset) { | 
|  | List<TryCatchBlock> handlers = new ArrayList<>(); | 
|  | for (int i = 0; i < node.tryCatchBlocks.size(); i++) { | 
|  | TryCatchBlockNode tryBlock = (TryCatchBlockNode) node.tryCatchBlocks.get(i); | 
|  | if (tryBlockRelevant(tryBlock, offset)) { | 
|  | handlers.add(new TryCatchBlock(tryBlock, this)); | 
|  | } | 
|  | } | 
|  | return handlers; | 
|  | } | 
|  |  | 
|  | private List<TryCatchBlock> getTryHandlers(AbstractInsnNode insn) { | 
|  | List<TryCatchBlock> handlers = new ArrayList<>(); | 
|  | Set<String> seen = new HashSet<>(); | 
|  | // The try-catch blocks are ordered by precedence. | 
|  | for (TryCatchBlock tryCatchBlock : getPotentialTryHandlers(insn)) { | 
|  | if (tryCatchBlock.getType() == null) { | 
|  | handlers.add(tryCatchBlock); | 
|  | return handlers; | 
|  | } | 
|  | if (!seen.contains(tryCatchBlock.getType())) { | 
|  | seen.add(tryCatchBlock.getType()); | 
|  | handlers.add(tryCatchBlock); | 
|  | } | 
|  | } | 
|  | if (generateMethodSynchronization()) { | 
|  | // Add synchronized exceptional exit for synchronized-method instructions without a default. | 
|  | assert handlers.isEmpty() || handlers.get(handlers.size() - 1).getType() != null; | 
|  | handlers.add(EXCEPTIONAL_SYNC_EXIT); | 
|  | } | 
|  | return handlers; | 
|  | } | 
|  |  | 
|  | private List<Integer> getTryHandlerOffsets(List<TryCatchBlock> tryCatchBlocks) { | 
|  | List<Integer> offsets = new ArrayList<>(); | 
|  | for (TryCatchBlock tryCatchBlock : tryCatchBlocks) { | 
|  | offsets.add(tryCatchBlock.getHandler()); | 
|  | } | 
|  | return offsets; | 
|  | } | 
|  |  | 
|  | private List<DexType> getTryHandlerGuards(List<TryCatchBlock> tryCatchBlocks) { | 
|  | List<DexType> guards = new ArrayList<>(); | 
|  | for (TryCatchBlock tryCatchBlock : tryCatchBlocks) { | 
|  | guards.add(tryCatchBlock.getType() == null | 
|  | ? DexItemFactory.catchAllType | 
|  | : application.getTypeFromName(tryCatchBlock.getType())); | 
|  |  | 
|  | } | 
|  | return guards; | 
|  | } | 
|  |  | 
|  | int getOffset(AbstractInsnNode insn) { | 
|  | return node.instructions.indexOf(insn); | 
|  | } | 
|  |  | 
|  | private int[] getTargets(AbstractInsnNode insn) { | 
|  | switch (insn.getType()) { | 
|  | case AbstractInsnNode.TABLESWITCH_INSN: { | 
|  | TableSwitchInsnNode switchInsn = (TableSwitchInsnNode) insn; | 
|  | return getSwitchTargets(switchInsn.dflt, switchInsn.labels); | 
|  | } | 
|  | case AbstractInsnNode.LOOKUPSWITCH_INSN: { | 
|  | LookupSwitchInsnNode switchInsn = (LookupSwitchInsnNode) insn; | 
|  | return getSwitchTargets(switchInsn.dflt, switchInsn.labels); | 
|  | } | 
|  | case AbstractInsnNode.JUMP_INSN: { | 
|  | return getJumpTargets((JumpInsnNode) insn); | 
|  | } | 
|  | case AbstractInsnNode.VAR_INSN: { | 
|  | return getVarTargets((VarInsnNode) insn); | 
|  | } | 
|  | default: | 
|  | return NO_TARGETS; | 
|  | } | 
|  | } | 
|  |  | 
|  | private int[] getSwitchTargets(LabelNode dflt, List labels) { | 
|  | int[] targets = new int[1 + labels.size()]; | 
|  | targets[0] = getOffset(dflt); | 
|  | for (int i = 1; i < targets.length; i++) { | 
|  | targets[i] = getOffset((LabelNode) labels.get(i - 1)); | 
|  | } | 
|  | return targets; | 
|  | } | 
|  |  | 
|  | private int[] getJumpTargets(JumpInsnNode jump) { | 
|  | switch (jump.getOpcode()) { | 
|  | case Opcodes.IFEQ: | 
|  | case Opcodes.IFNE: | 
|  | case Opcodes.IFLT: | 
|  | case Opcodes.IFGE: | 
|  | case Opcodes.IFGT: | 
|  | case Opcodes.IFLE: | 
|  | case Opcodes.IF_ICMPEQ: | 
|  | case Opcodes.IF_ICMPNE: | 
|  | case Opcodes.IF_ICMPLT: | 
|  | case Opcodes.IF_ICMPGE: | 
|  | case Opcodes.IF_ICMPGT: | 
|  | case Opcodes.IF_ICMPLE: | 
|  | case Opcodes.IF_ACMPEQ: | 
|  | case Opcodes.IF_ACMPNE: | 
|  | case Opcodes.IFNULL: | 
|  | case Opcodes.IFNONNULL: | 
|  | return new int[]{getOffset(jump.label), getOffset(jump.getNext())}; | 
|  | case Opcodes.GOTO: | 
|  | return new int[]{getOffset(jump.label)}; | 
|  | case Opcodes.JSR: { | 
|  | throw new Unreachable("JSR should be handled by the ASM jsr inliner"); | 
|  | } | 
|  | default: | 
|  | throw new Unreachable("Unexpected opcode in jump instruction: " + jump); | 
|  | } | 
|  | } | 
|  |  | 
|  | private int[] getVarTargets(VarInsnNode insn) { | 
|  | if (insn.getOpcode() == Opcodes.RET) { | 
|  | throw new Unreachable("RET should be handled by the ASM jsr inliner"); | 
|  | } | 
|  | return NO_TARGETS; | 
|  | } | 
|  |  | 
|  | // Type conversion helpers. | 
|  |  | 
|  | private static ValueType valueType(Type type) { | 
|  | switch (type.getSort()) { | 
|  | case Type.ARRAY: | 
|  | case Type.OBJECT: | 
|  | return ValueType.OBJECT; | 
|  | case Type.BOOLEAN: | 
|  | case Type.BYTE: | 
|  | case Type.SHORT: | 
|  | case Type.CHAR: | 
|  | case Type.INT: | 
|  | return ValueType.INT; | 
|  | case Type.FLOAT: | 
|  | return ValueType.FLOAT; | 
|  | case Type.LONG: | 
|  | return ValueType.LONG; | 
|  | case Type.DOUBLE: | 
|  | return ValueType.DOUBLE; | 
|  | case Type.VOID: | 
|  | // Illegal. Throws in fallthrough. | 
|  | default: | 
|  | throw new Unreachable("Invalid type in valueType: " + type); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static MemberType memberType(Type type) { | 
|  | switch (type.getSort()) { | 
|  | case Type.ARRAY: | 
|  | case Type.OBJECT: | 
|  | return MemberType.OBJECT; | 
|  | case Type.BOOLEAN: | 
|  | return MemberType.BOOLEAN; | 
|  | case Type.BYTE: | 
|  | return MemberType.BYTE; | 
|  | case Type.SHORT: | 
|  | return MemberType.SHORT; | 
|  | case Type.CHAR: | 
|  | return MemberType.CHAR; | 
|  | case Type.INT: | 
|  | return MemberType.INT; | 
|  | case Type.FLOAT: | 
|  | return MemberType.FLOAT; | 
|  | case Type.LONG: | 
|  | return MemberType.LONG; | 
|  | case Type.DOUBLE: | 
|  | return MemberType.DOUBLE; | 
|  | case Type.VOID: | 
|  | // Illegal. Throws in fallthrough. | 
|  | default: | 
|  | throw new Unreachable("Invalid type in memberType: " + type); | 
|  | } | 
|  | } | 
|  |  | 
|  | private MemberType memberType(String fieldDesc) { | 
|  | return memberType(application.getAsmType(fieldDesc)); | 
|  | } | 
|  |  | 
|  | private static NumericType numericType(Type type) { | 
|  | switch (type.getSort()) { | 
|  | case Type.BYTE: | 
|  | return NumericType.BYTE; | 
|  | case Type.CHAR: | 
|  | return NumericType.CHAR; | 
|  | case Type.SHORT: | 
|  | return NumericType.SHORT; | 
|  | case Type.INT: | 
|  | return NumericType.INT; | 
|  | case Type.LONG: | 
|  | return NumericType.LONG; | 
|  | case Type.FLOAT: | 
|  | return NumericType.FLOAT; | 
|  | case Type.DOUBLE: | 
|  | return NumericType.DOUBLE; | 
|  | default: | 
|  | throw new Unreachable("Invalid type in numericType: " + type); | 
|  | } | 
|  | } | 
|  |  | 
|  | private Invoke.Type invokeType(MethodInsnNode method) { | 
|  | switch (method.getOpcode()) { | 
|  | case Opcodes.INVOKEVIRTUAL: | 
|  | if (isCallToPolymorphicSignatureMethod(method.owner, method.name)) { | 
|  | return Invoke.Type.POLYMORPHIC; | 
|  | } | 
|  | return Invoke.Type.VIRTUAL; | 
|  | case Opcodes.INVOKESTATIC: | 
|  | return Invoke.Type.STATIC; | 
|  | case Opcodes.INVOKEINTERFACE: | 
|  | return Invoke.Type.INTERFACE; | 
|  | case Opcodes.INVOKESPECIAL: { | 
|  | // This is actually incorrect unless the input was verified. The spec says that it has | 
|  | // invoke super semantics, if the type is a supertype of the current class. If it is the | 
|  | // same or a subtype, it has invoke direct semantics. The latter case is illegal, so we | 
|  | // map it to a super call here. In R8, we abort at a later stage (see. | 
|  | // See also <a href= | 
|  | // "https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokespecial" | 
|  | // </a> for invokespecial dispatch and <a href="https://docs.oracle.com/javase/specs/jvms/" | 
|  | // "se7/html/jvms-4.html#jvms-4.10.1.9.invokespecial"</a> for verification requirements. In | 
|  | // particular, the requirement | 
|  | //   isAssignable(class(CurrentClassName, L), class(MethodClassName, L)). | 
|  | DexType owner = application.getTypeFromName(method.owner); | 
|  | if (owner == clazz || method.name.equals(Constants.INSTANCE_INITIALIZER_NAME)) { | 
|  | return Invoke.Type.DIRECT; | 
|  | } else { | 
|  | return Invoke.Type.SUPER; | 
|  | } | 
|  | } | 
|  | default: | 
|  | throw new Unreachable("Unexpected MethodInsnNode opcode: " + method.getOpcode()); | 
|  | } | 
|  | } | 
|  |  | 
|  | private Type makeArrayType(Type elementType) { | 
|  | return application.getAsmObjectType("[" + elementType.getDescriptor()); | 
|  | } | 
|  |  | 
|  | private static String arrayTypeDesc(int arrayTypeCode) { | 
|  | switch (arrayTypeCode) { | 
|  | case Opcodes.T_BOOLEAN: | 
|  | return "[Z"; | 
|  | case Opcodes.T_CHAR: | 
|  | return "[C"; | 
|  | case Opcodes.T_FLOAT: | 
|  | return "[F"; | 
|  | case Opcodes.T_DOUBLE: | 
|  | return "[D"; | 
|  | case Opcodes.T_BYTE: | 
|  | return "[B"; | 
|  | case Opcodes.T_SHORT: | 
|  | return "[S"; | 
|  | case Opcodes.T_INT: | 
|  | return "[I"; | 
|  | case Opcodes.T_LONG: | 
|  | return "[J"; | 
|  | default: | 
|  | throw new Unreachable("Unexpected array-type code " + arrayTypeCode); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static Type getArrayElementTypeForOpcode(int opcode) { | 
|  | switch (opcode) { | 
|  | case Opcodes.IALOAD: | 
|  | case Opcodes.IASTORE: | 
|  | return Type.INT_TYPE; | 
|  | case Opcodes.FALOAD: | 
|  | case Opcodes.FASTORE: | 
|  | return Type.FLOAT_TYPE; | 
|  | case Opcodes.LALOAD: | 
|  | case Opcodes.LASTORE: | 
|  | return Type.LONG_TYPE; | 
|  | case Opcodes.DALOAD: | 
|  | case Opcodes.DASTORE: | 
|  | return Type.DOUBLE_TYPE; | 
|  | case Opcodes.AALOAD: | 
|  | case Opcodes.AASTORE: | 
|  | return JarState.NULL_TYPE; // We might not know the type. | 
|  | case Opcodes.BALOAD: | 
|  | case Opcodes.BASTORE: | 
|  | return Type.BYTE_TYPE; // We don't distinguish byte and boolean. | 
|  | case Opcodes.CALOAD: | 
|  | case Opcodes.CASTORE: | 
|  | return Type.CHAR_TYPE; | 
|  | case Opcodes.SALOAD: | 
|  | case Opcodes.SASTORE: | 
|  | return Type.SHORT_TYPE; | 
|  | default: | 
|  | throw new Unreachable("Unexpected array opcode " + opcode); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static boolean isCompatibleArrayElementType(int opcode, Type type) { | 
|  | switch (opcode) { | 
|  | case Opcodes.IALOAD: | 
|  | case Opcodes.IASTORE: | 
|  | return Slot.isCompatible(type, Type.INT_TYPE); | 
|  | case Opcodes.FALOAD: | 
|  | case Opcodes.FASTORE: | 
|  | return Slot.isCompatible(type, Type.FLOAT_TYPE); | 
|  | case Opcodes.LALOAD: | 
|  | case Opcodes.LASTORE: | 
|  | return Slot.isCompatible(type, Type.LONG_TYPE); | 
|  | case Opcodes.DALOAD: | 
|  | case Opcodes.DASTORE: | 
|  | return Slot.isCompatible(type, Type.DOUBLE_TYPE); | 
|  | case Opcodes.AALOAD: | 
|  | case Opcodes.AASTORE: | 
|  | return Slot.isCompatible(type, JarState.REFERENCE_TYPE); | 
|  | case Opcodes.BALOAD: | 
|  | case Opcodes.BASTORE: | 
|  | return Slot.isCompatible(type, Type.BYTE_TYPE) | 
|  | || Slot.isCompatible(type, Type.BOOLEAN_TYPE); | 
|  | case Opcodes.CALOAD: | 
|  | case Opcodes.CASTORE: | 
|  | return Slot.isCompatible(type, Type.CHAR_TYPE); | 
|  | case Opcodes.SALOAD: | 
|  | case Opcodes.SASTORE: | 
|  | return Slot.isCompatible(type, Type.SHORT_TYPE); | 
|  | default: | 
|  | throw new Unreachable("Unexpected array opcode " + opcode); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static If.Type ifType(int opcode) { | 
|  | switch (opcode) { | 
|  | case Opcodes.IFEQ: | 
|  | case Opcodes.IF_ICMPEQ: | 
|  | case Opcodes.IF_ACMPEQ: | 
|  | return If.Type.EQ; | 
|  | case Opcodes.IFNE: | 
|  | case Opcodes.IF_ICMPNE: | 
|  | case Opcodes.IF_ACMPNE: | 
|  | return If.Type.NE; | 
|  | case Opcodes.IFLT: | 
|  | case Opcodes.IF_ICMPLT: | 
|  | return If.Type.LT; | 
|  | case Opcodes.IFGE: | 
|  | case Opcodes.IF_ICMPGE: | 
|  | return If.Type.GE; | 
|  | case Opcodes.IFGT: | 
|  | case Opcodes.IF_ICMPGT: | 
|  | return If.Type.GT; | 
|  | case Opcodes.IFLE: | 
|  | case Opcodes.IF_ICMPLE: | 
|  | return If.Type.LE; | 
|  | default: | 
|  | throw new Unreachable("Unexpected If instruction opcode: " + opcode); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static Type opType(int opcode) { | 
|  | switch (opcode) { | 
|  | case Opcodes.IADD: | 
|  | case Opcodes.ISUB: | 
|  | case Opcodes.IMUL: | 
|  | case Opcodes.IDIV: | 
|  | case Opcodes.IREM: | 
|  | case Opcodes.INEG: | 
|  | case Opcodes.ISHL: | 
|  | case Opcodes.ISHR: | 
|  | case Opcodes.IUSHR: | 
|  | return Type.INT_TYPE; | 
|  | case Opcodes.LADD: | 
|  | case Opcodes.LSUB: | 
|  | case Opcodes.LMUL: | 
|  | case Opcodes.LDIV: | 
|  | case Opcodes.LREM: | 
|  | case Opcodes.LNEG: | 
|  | case Opcodes.LSHL: | 
|  | case Opcodes.LSHR: | 
|  | case Opcodes.LUSHR: | 
|  | return Type.LONG_TYPE; | 
|  | case Opcodes.FADD: | 
|  | case Opcodes.FSUB: | 
|  | case Opcodes.FMUL: | 
|  | case Opcodes.FDIV: | 
|  | case Opcodes.FREM: | 
|  | case Opcodes.FNEG: | 
|  | return Type.FLOAT_TYPE; | 
|  | case Opcodes.DADD: | 
|  | case Opcodes.DSUB: | 
|  | case Opcodes.DMUL: | 
|  | case Opcodes.DDIV: | 
|  | case Opcodes.DREM: | 
|  | case Opcodes.DNEG: | 
|  | return Type.DOUBLE_TYPE; | 
|  | default: | 
|  | throw new Unreachable("Unexpected opcode " + opcode); | 
|  | } | 
|  | } | 
|  |  | 
|  | // State updating procedures. | 
|  |  | 
|  | private void updateState(AbstractInsnNode insn) { | 
|  | switch (insn.getType()) { | 
|  | case AbstractInsnNode.INSN: | 
|  | updateState((InsnNode) insn); | 
|  | break; | 
|  | case AbstractInsnNode.INT_INSN: | 
|  | updateState((IntInsnNode) insn); | 
|  | break; | 
|  | case AbstractInsnNode.VAR_INSN: | 
|  | updateState((VarInsnNode) insn); | 
|  | break; | 
|  | case AbstractInsnNode.TYPE_INSN: | 
|  | updateState((TypeInsnNode) insn); | 
|  | break; | 
|  | case AbstractInsnNode.FIELD_INSN: | 
|  | updateState((FieldInsnNode) insn); | 
|  | break; | 
|  | case AbstractInsnNode.METHOD_INSN: | 
|  | updateState((MethodInsnNode) insn); | 
|  | break; | 
|  | case AbstractInsnNode.INVOKE_DYNAMIC_INSN: | 
|  | updateState((InvokeDynamicInsnNode) insn); | 
|  | break; | 
|  | case AbstractInsnNode.JUMP_INSN: | 
|  | updateState((JumpInsnNode) insn); | 
|  | break; | 
|  | case AbstractInsnNode.LABEL: | 
|  | updateState((LabelNode) insn); | 
|  | break; | 
|  | case AbstractInsnNode.LDC_INSN: | 
|  | updateState((LdcInsnNode) insn); | 
|  | break; | 
|  | case AbstractInsnNode.IINC_INSN: | 
|  | updateState((IincInsnNode) insn); | 
|  | break; | 
|  | case AbstractInsnNode.TABLESWITCH_INSN: | 
|  | updateState((TableSwitchInsnNode) insn); | 
|  | break; | 
|  | case AbstractInsnNode.LOOKUPSWITCH_INSN: | 
|  | updateState((LookupSwitchInsnNode) insn); | 
|  | break; | 
|  | case AbstractInsnNode.MULTIANEWARRAY_INSN: | 
|  | updateState((MultiANewArrayInsnNode) insn); | 
|  | break; | 
|  | case AbstractInsnNode.LINE: | 
|  | updateState((LineNumberNode) insn); | 
|  | break; | 
|  | default: | 
|  | throw new Unreachable("Unexpected instruction " + insn); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void updateState(InsnNode insn) { | 
|  | int opcode = insn.getOpcode(); | 
|  | switch (opcode) { | 
|  | case Opcodes.NOP: | 
|  | // Intentionally left empty. | 
|  | break; | 
|  | case Opcodes.ACONST_NULL: | 
|  | state.push(JarState.NULL_TYPE); | 
|  | break; | 
|  | case Opcodes.ICONST_M1: | 
|  | case Opcodes.ICONST_0: | 
|  | case Opcodes.ICONST_1: | 
|  | case Opcodes.ICONST_2: | 
|  | case Opcodes.ICONST_3: | 
|  | case Opcodes.ICONST_4: | 
|  | case Opcodes.ICONST_5: | 
|  | state.push(Type.INT_TYPE); | 
|  | break; | 
|  | case Opcodes.LCONST_0: | 
|  | case Opcodes.LCONST_1: | 
|  | state.push(Type.LONG_TYPE); | 
|  | break; | 
|  | case Opcodes.FCONST_0: | 
|  | case Opcodes.FCONST_1: | 
|  | case Opcodes.FCONST_2: | 
|  | state.push(Type.FLOAT_TYPE); | 
|  | break; | 
|  | case Opcodes.DCONST_0: | 
|  | case Opcodes.DCONST_1: | 
|  | state.push(Type.DOUBLE_TYPE); | 
|  | break; | 
|  | case Opcodes.IALOAD: | 
|  | case Opcodes.LALOAD: | 
|  | case Opcodes.FALOAD: | 
|  | case Opcodes.DALOAD: | 
|  | case Opcodes.AALOAD: | 
|  | case Opcodes.BALOAD: | 
|  | case Opcodes.CALOAD: | 
|  | case Opcodes.SALOAD: { | 
|  | state.pop(); | 
|  | Type elementType = state.pop(JarState.ARRAY_TYPE).getArrayElementType(); | 
|  | if (elementType == null) { | 
|  | // We propagate the byte-or-bool type, which will then get resolved to an | 
|  | // actual type if we have a concrete byte type or bool type on another flow edge. | 
|  | elementType = (Opcodes.BALOAD == opcode) | 
|  | ? JarState.BYTE_OR_BOOL_TYPE | 
|  | : getArrayElementTypeForOpcode(opcode); | 
|  | } | 
|  | state.push(elementType); | 
|  | break; | 
|  | } | 
|  | case Opcodes.IASTORE: | 
|  | case Opcodes.LASTORE: | 
|  | case Opcodes.FASTORE: | 
|  | case Opcodes.DASTORE: | 
|  | case Opcodes.AASTORE: | 
|  | case Opcodes.BASTORE: | 
|  | case Opcodes.CASTORE: | 
|  | case Opcodes.SASTORE: { | 
|  | state.pop(); | 
|  | state.pop(); | 
|  | state.pop(); | 
|  | break; | 
|  | } | 
|  | case Opcodes.POP: { | 
|  | Slot value = state.pop(); | 
|  | value.isCategory1(); | 
|  | break; | 
|  | } | 
|  | case Opcodes.POP2: { | 
|  | Slot value = state.pop(); | 
|  | if (value.isCategory1()) { | 
|  | Slot value2 = state.pop(); | 
|  | assert value2.isCategory1(); | 
|  | } | 
|  | break; | 
|  | } | 
|  | case Opcodes.DUP: { | 
|  | Slot value = state.peek(); | 
|  | assert value.isCategory1(); | 
|  | state.push(value.type); | 
|  | break; | 
|  | } | 
|  | case Opcodes.DUP_X1: { | 
|  | // Stack transformation: ..., v2, v1 -> ..., v1, v2, v1 | 
|  | Slot value1 = state.pop(); | 
|  | Slot value2 = state.pop(); | 
|  | assert value1.isCategory1() && value2.isCategory1(); | 
|  | int stack2 = state.push(value1.type); | 
|  | int stack1 = state.push(value2.type); | 
|  | state.push(value1.type); | 
|  | assert value2.register == stack2; | 
|  | assert value1.register == stack1; | 
|  | break; | 
|  | } | 
|  | case Opcodes.DUP_X2: { | 
|  | Slot value1 = state.pop(); | 
|  | Slot value2 = state.pop(); | 
|  | assert value1.isCategory1(); | 
|  | if (value2.isCategory1()) { | 
|  | Slot value3 = state.pop(); | 
|  | assert value3.isCategory1(); | 
|  | // Stack transformation: ..., v3, v2, v1 -> ..., v1, v3, v2, v1 | 
|  | updateStateForDupOneBelowTwo(value3, value2, value1); | 
|  | } else { | 
|  | // Stack transformation: ..., w2, v1 -> ..., v1, w2, v1 | 
|  | updateStateForDupOneBelowOne(value2, value1); | 
|  | } | 
|  | break; | 
|  | } | 
|  | case Opcodes.DUP2: { | 
|  | Slot value1 = state.pop(); | 
|  | if (value1.isCategory1()) { | 
|  | Slot value2 = state.pop(); | 
|  | // Stack transformation: ..., v2, v1 -> ..., v2, v1, v2, v1 | 
|  | assert value2.isCategory1(); | 
|  | state.push(value2.type); | 
|  | state.push(value1.type); | 
|  | state.push(value2.type); | 
|  | state.push(value1.type); | 
|  | } else { | 
|  | // Stack transformation: ..., w1 -> ..., w1, w1 | 
|  | state.push(value1.type); | 
|  | state.push(value1.type); | 
|  | } | 
|  | break; | 
|  | } | 
|  | case Opcodes.DUP2_X1: { | 
|  | Slot value1 = state.pop(); | 
|  | Slot value2 = state.pop(); | 
|  | assert value2.isCategory1(); | 
|  | if (value1.isCategory1()) { | 
|  | // Stack transformation: ..., v3, v2, v1 -> v2, v1, v3, v2, v1 | 
|  | Slot value3 = state.pop(); | 
|  | assert value3.isCategory1(); | 
|  | updateStateForDupTwoBelowOne(value3, value2, value1); | 
|  | } else { | 
|  | // Stack transformation: ..., v2, w1 -> ..., w1, v2, w1 | 
|  | updateStateForDupOneBelowOne(value2, value1); | 
|  | } | 
|  | break; | 
|  | } | 
|  | case Opcodes.DUP2_X2: { | 
|  | Slot value1 = state.pop(); | 
|  | Slot value2 = state.pop(); | 
|  | if (!value1.isCategory1() && !value2.isCategory1()) { | 
|  | // State transformation: ..., w2, w1 -> w1, w2, w1 | 
|  | updateStateForDupOneBelowOne(value2, value1); | 
|  | } else { | 
|  | Slot value3 = state.pop(); | 
|  | if (!value1.isCategory1()) { | 
|  | assert value2.isCategory1(); | 
|  | assert value3.isCategory1(); | 
|  | // State transformation: ..., v3, v2, w1 -> w1, v3, v2, w1 | 
|  | updateStateForDupOneBelowTwo(value3, value2, value1); | 
|  | } else if (!value3.isCategory1()) { | 
|  | assert value1.isCategory1(); | 
|  | assert value2.isCategory1(); | 
|  | // State transformation: ..., w3, v2, v1 -> v2, v1, w3, v2, v1 | 
|  | updateStateForDupTwoBelowOne(value3, value2, value1); | 
|  | } else { | 
|  | Slot value4 = state.pop(); | 
|  | assert value1.isCategory1(); | 
|  | assert value2.isCategory1(); | 
|  | assert value3.isCategory1(); | 
|  | assert value4.isCategory1(); | 
|  | // State transformation: ..., v4, v3, v2, v1 -> v2, v1, v4, v3, v2, v1 | 
|  | updateStateForDupTwoBelowTwo(value4, value3, value2, value1); | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  | case Opcodes.SWAP: { | 
|  | Slot value1 = state.pop(); | 
|  | Slot value2 = state.pop(); | 
|  | assert value1.isCategory1() && value2.isCategory1(); | 
|  | state.push(value1.type); | 
|  | state.push(value2.type); | 
|  | break; | 
|  | } | 
|  | case Opcodes.IADD: | 
|  | case Opcodes.LADD: | 
|  | case Opcodes.FADD: | 
|  | case Opcodes.DADD: | 
|  | case Opcodes.ISUB: | 
|  | case Opcodes.LSUB: | 
|  | case Opcodes.FSUB: | 
|  | case Opcodes.DSUB: | 
|  | case Opcodes.IMUL: | 
|  | case Opcodes.LMUL: | 
|  | case Opcodes.FMUL: | 
|  | case Opcodes.DMUL: | 
|  | case Opcodes.IDIV: | 
|  | case Opcodes.LDIV: | 
|  | case Opcodes.FDIV: | 
|  | case Opcodes.DDIV: | 
|  | case Opcodes.IREM: | 
|  | case Opcodes.LREM: | 
|  | case Opcodes.FREM: | 
|  | case Opcodes.DREM: { | 
|  | Type type = opType(opcode); | 
|  | state.pop(); | 
|  | state.pop(); | 
|  | state.push(type); | 
|  | break; | 
|  | } | 
|  | case Opcodes.INEG: | 
|  | case Opcodes.LNEG: | 
|  | case Opcodes.FNEG: | 
|  | case Opcodes.DNEG: { | 
|  | Type type = opType(opcode); | 
|  | state.pop(); | 
|  | state.push(type); | 
|  | break; | 
|  | } | 
|  | case Opcodes.ISHL: | 
|  | case Opcodes.LSHL: | 
|  | case Opcodes.ISHR: | 
|  | case Opcodes.LSHR: | 
|  | case Opcodes.IUSHR: | 
|  | case Opcodes.LUSHR: { | 
|  | Type type = opType(opcode); | 
|  | state.pop(); | 
|  | state.pop(); | 
|  | state.push(type); | 
|  | break; | 
|  | } | 
|  | case Opcodes.IAND: | 
|  | case Opcodes.LAND: { | 
|  | Type type = opcode == Opcodes.IAND ? Type.INT_TYPE : Type.LONG_TYPE; | 
|  | state.pop(); | 
|  | state.pop(); | 
|  | state.push(type); | 
|  | break; | 
|  | } | 
|  | case Opcodes.IOR: | 
|  | case Opcodes.LOR: { | 
|  | Type type = opcode == Opcodes.IOR ? Type.INT_TYPE : Type.LONG_TYPE; | 
|  | state.pop(); | 
|  | state.pop(); | 
|  | state.push(type); | 
|  | break; | 
|  | } | 
|  | case Opcodes.IXOR: | 
|  | case Opcodes.LXOR: { | 
|  | Type type = opcode == Opcodes.IXOR ? Type.INT_TYPE : Type.LONG_TYPE; | 
|  | state.pop(); | 
|  | state.pop(); | 
|  | state.push(type); | 
|  | break; | 
|  | } | 
|  | case Opcodes.I2L: | 
|  | updateStateForConversion(Type.INT_TYPE, Type.LONG_TYPE); | 
|  | break; | 
|  | case Opcodes.I2F: | 
|  | updateStateForConversion(Type.INT_TYPE, Type.FLOAT_TYPE); | 
|  | break; | 
|  | case Opcodes.I2D: | 
|  | updateStateForConversion(Type.INT_TYPE, Type.DOUBLE_TYPE); | 
|  | break; | 
|  | case Opcodes.L2I: | 
|  | updateStateForConversion(Type.LONG_TYPE, Type.INT_TYPE); | 
|  | break; | 
|  | case Opcodes.L2F: | 
|  | updateStateForConversion(Type.LONG_TYPE, Type.FLOAT_TYPE); | 
|  | break; | 
|  | case Opcodes.L2D: | 
|  | updateStateForConversion(Type.LONG_TYPE, Type.DOUBLE_TYPE); | 
|  | break; | 
|  | case Opcodes.F2I: | 
|  | updateStateForConversion(Type.FLOAT_TYPE, Type.INT_TYPE); | 
|  | break; | 
|  | case Opcodes.F2L: | 
|  | updateStateForConversion(Type.FLOAT_TYPE, Type.LONG_TYPE); | 
|  | break; | 
|  | case Opcodes.F2D: | 
|  | updateStateForConversion(Type.FLOAT_TYPE, Type.DOUBLE_TYPE); | 
|  | break; | 
|  | case Opcodes.D2I: | 
|  | updateStateForConversion(Type.DOUBLE_TYPE, Type.INT_TYPE); | 
|  | break; | 
|  | case Opcodes.D2L: | 
|  | updateStateForConversion(Type.DOUBLE_TYPE, Type.LONG_TYPE); | 
|  | break; | 
|  | case Opcodes.D2F: | 
|  | updateStateForConversion(Type.DOUBLE_TYPE, Type.FLOAT_TYPE); | 
|  | break; | 
|  | case Opcodes.I2B: | 
|  | updateStateForConversion(Type.INT_TYPE, Type.BYTE_TYPE); | 
|  | break; | 
|  | case Opcodes.I2C: | 
|  | updateStateForConversion(Type.INT_TYPE, Type.CHAR_TYPE); | 
|  | break; | 
|  | case Opcodes.I2S: | 
|  | updateStateForConversion(Type.INT_TYPE, Type.SHORT_TYPE); | 
|  | break; | 
|  | case Opcodes.LCMP: { | 
|  | state.pop(); | 
|  | state.pop(); | 
|  | state.push(Type.INT_TYPE); | 
|  | break; | 
|  | } | 
|  | case Opcodes.FCMPL: | 
|  | case Opcodes.FCMPG: { | 
|  | state.pop(); | 
|  | state.pop(); | 
|  | state.push(Type.INT_TYPE); | 
|  | break; | 
|  | } | 
|  | case Opcodes.DCMPL: | 
|  | case Opcodes.DCMPG: { | 
|  | state.pop(); | 
|  | state.pop(); | 
|  | state.push(Type.INT_TYPE); | 
|  | break; | 
|  | } | 
|  | case Opcodes.IRETURN: { | 
|  | state.pop(); | 
|  | break; | 
|  | } | 
|  | case Opcodes.LRETURN: { | 
|  | state.pop(); | 
|  | break; | 
|  | } | 
|  | case Opcodes.FRETURN: { | 
|  | state.pop(); | 
|  | break; | 
|  | } | 
|  | case Opcodes.DRETURN: { | 
|  | state.pop(); | 
|  | break; | 
|  | } | 
|  | case Opcodes.ARETURN: { | 
|  | state.pop(JarState.REFERENCE_TYPE); | 
|  | break; | 
|  | } | 
|  | case Opcodes.RETURN: { | 
|  | break; | 
|  | } | 
|  | case Opcodes.ARRAYLENGTH: { | 
|  | state.pop(JarState.ARRAY_TYPE); | 
|  | state.push(Type.INT_TYPE); | 
|  | break; | 
|  | } | 
|  | case Opcodes.ATHROW: { | 
|  | state.pop(JarState.OBJECT_TYPE); | 
|  | break; | 
|  | } | 
|  | case Opcodes.MONITORENTER: { | 
|  | state.pop(JarState.REFERENCE_TYPE); | 
|  | break; | 
|  | } | 
|  | case Opcodes.MONITOREXIT: { | 
|  | state.pop(JarState.REFERENCE_TYPE); | 
|  | break; | 
|  | } | 
|  | default: | 
|  | throw new Unreachable("Unexpected Insn opcode: " + insn.getOpcode()); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void updateStateForDupOneBelowTwo(Slot value3, Slot value2, Slot value1) { | 
|  | state.push(value1.type); | 
|  | state.push(value3.type); | 
|  | state.push(value2.type); | 
|  | state.push(value1.type); | 
|  | } | 
|  |  | 
|  | private void updateStateForDupOneBelowOne(Slot value2, Slot value1) { | 
|  | state.push(value1.type); | 
|  | state.push(value2.type); | 
|  | state.push(value1.type); | 
|  | } | 
|  |  | 
|  | private void updateStateForDupTwoBelowOne(Slot value3, Slot value2, Slot value1) { | 
|  | state.push(value2.type); | 
|  | state.push(value1.type); | 
|  | state.push(value3.type); | 
|  | state.push(value2.type); | 
|  | state.push(value1.type); | 
|  | } | 
|  |  | 
|  | private void updateStateForDupTwoBelowTwo(Slot value4, Slot value3, Slot value2, Slot value1) { | 
|  | state.push(value2.type); | 
|  | state.push(value1.type); | 
|  | state.push(value4.type); | 
|  | state.push(value3.type); | 
|  | state.push(value2.type); | 
|  | state.push(value1.type); | 
|  | } | 
|  |  | 
|  | private void updateState(IntInsnNode insn) { | 
|  | switch (insn.getOpcode()) { | 
|  | case Opcodes.BIPUSH: | 
|  | case Opcodes.SIPUSH: { | 
|  | state.push(Type.INT_TYPE); | 
|  | break; | 
|  | } | 
|  | case Opcodes.NEWARRAY: { | 
|  | String desc = arrayTypeDesc(insn.operand); | 
|  | Type type = application.getAsmType(desc); | 
|  | state.pop(); | 
|  | state.push(type); | 
|  | break; | 
|  | } | 
|  | default: | 
|  | throw new Unreachable("Unexpected IntInsn opcode: " + insn.getOpcode()); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void updateState(VarInsnNode insn) { | 
|  | int opcode = insn.getOpcode(); | 
|  | Type expectedType; | 
|  | switch (opcode) { | 
|  | case Opcodes.ILOAD: | 
|  | case Opcodes.ISTORE: | 
|  | expectedType = Type.INT_TYPE; | 
|  | break; | 
|  | case Opcodes.FLOAD: | 
|  | case Opcodes.FSTORE: | 
|  | expectedType = Type.FLOAT_TYPE; | 
|  | break; | 
|  | case Opcodes.LLOAD: | 
|  | case Opcodes.LSTORE: | 
|  | expectedType = Type.LONG_TYPE; | 
|  | break; | 
|  | case Opcodes.DLOAD: | 
|  | case Opcodes.DSTORE: | 
|  | expectedType = Type.DOUBLE_TYPE; | 
|  | break; | 
|  | case Opcodes.ALOAD: | 
|  | case Opcodes.ASTORE: | 
|  | expectedType = JarState.REFERENCE_TYPE; | 
|  | break; | 
|  | case Opcodes.RET: { | 
|  | throw new Unreachable("RET should be handled by the ASM jsr inliner"); | 
|  | } | 
|  | default: | 
|  | throw new Unreachable("Unexpected VarInsn opcode: " + insn.getOpcode()); | 
|  | } | 
|  | if (Opcodes.ILOAD <= opcode && opcode <= Opcodes.ALOAD) { | 
|  | Slot src = state.readLocal(insn.var, expectedType); | 
|  | state.push(src.type); | 
|  | } else { | 
|  | assert Opcodes.ISTORE <= opcode && opcode <= Opcodes.ASTORE; | 
|  | Slot slot = state.pop(); | 
|  | if (slot.type == JarState.NULL_TYPE && expectedType != JarState.REFERENCE_TYPE) { | 
|  | state.writeLocal(insn.var, expectedType); | 
|  | } else { | 
|  | state.writeLocal(insn.var, slot.type); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private void updateState(TypeInsnNode insn) { | 
|  | Type type = application.getAsmObjectType(insn.desc); | 
|  | switch (insn.getOpcode()) { | 
|  | case Opcodes.NEW: { | 
|  | state.push(type); | 
|  | break; | 
|  | } | 
|  | case Opcodes.ANEWARRAY: { | 
|  | Type arrayType = makeArrayType(type); | 
|  | state.pop(); | 
|  | state.push(arrayType); | 
|  | break; | 
|  | } | 
|  | case Opcodes.CHECKCAST: { | 
|  | // Pop the top value and push it back on with the checked type. | 
|  | state.pop(type); | 
|  | state.push(type); | 
|  | break; | 
|  | } | 
|  | case Opcodes.INSTANCEOF: { | 
|  | state.pop(JarState.REFERENCE_TYPE); | 
|  | state.push(Type.INT_TYPE); | 
|  | break; | 
|  | } | 
|  | default: | 
|  | throw new Unreachable("Unexpected TypeInsn opcode: " + insn.getOpcode()); | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | private void updateState(FieldInsnNode insn) { | 
|  | Type type = application.getAsmType(insn.desc); | 
|  | switch (insn.getOpcode()) { | 
|  | case Opcodes.GETSTATIC: | 
|  | state.push(type); | 
|  | break; | 
|  | case Opcodes.PUTSTATIC: | 
|  | state.pop(); | 
|  | break; | 
|  | case Opcodes.GETFIELD: { | 
|  | state.pop(JarState.OBJECT_TYPE); | 
|  | state.push(type); | 
|  | break; | 
|  | } | 
|  | case Opcodes.PUTFIELD: { | 
|  | state.pop(); | 
|  | state.pop(JarState.OBJECT_TYPE); | 
|  | break; | 
|  | } | 
|  | default: | 
|  | throw new Unreachable("Unexpected FieldInsn opcode: " + insn.getOpcode()); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void updateState(MethodInsnNode insn) { | 
|  | updateStateForInvoke(insn.desc, insn.getOpcode() != Opcodes.INVOKESTATIC); | 
|  | } | 
|  |  | 
|  | private void updateState(InvokeDynamicInsnNode insn) { | 
|  | updateStateForInvoke(insn.desc, false /* receiver passed explicitly */); | 
|  | } | 
|  |  | 
|  | private void updateStateForInvoke(String desc, boolean implicitReceiver) { | 
|  | // Pop arguments. | 
|  | state.popReverse(application.getArgumentCount(desc)); | 
|  | // Pop implicit receiver if needed. | 
|  | if (implicitReceiver) { | 
|  | state.pop(); | 
|  | } | 
|  | // Push return value if needed. | 
|  | Type returnType = application.getReturnType(desc); | 
|  | if (returnType != Type.VOID_TYPE) { | 
|  | state.push(returnType); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void updateState(JumpInsnNode insn) { | 
|  | int[] targets = getTargets(insn); | 
|  | int opcode = insn.getOpcode(); | 
|  | if (Opcodes.IFEQ <= opcode && opcode <= Opcodes.IF_ACMPNE) { | 
|  | assert targets.length == 2; | 
|  | if (opcode <= Opcodes.IFLE) { | 
|  | state.pop(); | 
|  | } else { | 
|  | state.pop(); | 
|  | state.pop(); | 
|  | } | 
|  | } else { | 
|  | switch (opcode) { | 
|  | case Opcodes.GOTO: { | 
|  | assert targets.length == 1; | 
|  | break; | 
|  | } | 
|  | case Opcodes.IFNULL: | 
|  | case Opcodes.IFNONNULL: { | 
|  | state.pop(); | 
|  | break; | 
|  | } | 
|  | case Opcodes.JSR: { | 
|  | throw new Unreachable("JSR should be handled by the ASM jsr inliner"); | 
|  | } | 
|  | default: | 
|  | throw new Unreachable("Unexpected JumpInsn opcode: " + insn.getOpcode()); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private void updateState(LabelNode insn) { | 
|  | // Intentionally empty. | 
|  | } | 
|  |  | 
|  | private void updateState(LdcInsnNode insn) { | 
|  | if (insn.cst instanceof Type) { | 
|  | Type type = (Type) insn.cst; | 
|  | state.push(type); | 
|  | } else if (insn.cst instanceof String) { | 
|  | state.push(STRING_TYPE); | 
|  | } else if (insn.cst instanceof Long) { | 
|  | state.push(Type.LONG_TYPE); | 
|  | } else if (insn.cst instanceof Double) { | 
|  | state.push(Type.DOUBLE_TYPE); | 
|  | } else if (insn.cst instanceof Integer) { | 
|  | state.push(Type.INT_TYPE); | 
|  | } else if (insn.cst instanceof Float) { | 
|  | state.push(Type.FLOAT_TYPE); | 
|  | } else if (insn.cst instanceof Handle) { | 
|  | state.push(METHOD_HANDLE_TYPE); | 
|  | } else { | 
|  | throw new CompilationError("Unsupported constant: " + insn.cst.toString()); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void updateState(IincInsnNode insn) { | 
|  | state.readLocal(insn.var, Type.INT_TYPE); | 
|  | } | 
|  |  | 
|  | private void updateState(TableSwitchInsnNode insn) { | 
|  | state.pop(); | 
|  | } | 
|  |  | 
|  | private void updateState(LookupSwitchInsnNode insn) { | 
|  | state.pop(); | 
|  | } | 
|  |  | 
|  | private void updateState(MultiANewArrayInsnNode insn) { | 
|  | // Type of the full array. | 
|  | Type arrayType = application.getAsmObjectType(insn.desc); | 
|  | state.popReverse(insn.dims, Type.INT_TYPE); | 
|  | state.push(arrayType); | 
|  | } | 
|  |  | 
|  | private void updateState(LineNumberNode insn) { | 
|  | // Intentionally empty. | 
|  | } | 
|  |  | 
|  | private void updateStateForConversion(Type from, Type to) { | 
|  | state.pop(); | 
|  | state.push(to); | 
|  | } | 
|  |  | 
|  | // IR instruction building procedures. | 
|  |  | 
|  | private void build(AbstractInsnNode insn, IRBuilder builder) { | 
|  | switch (insn.getType()) { | 
|  | case AbstractInsnNode.INSN: | 
|  | build((InsnNode) insn, builder); | 
|  | break; | 
|  | case AbstractInsnNode.INT_INSN: | 
|  | build((IntInsnNode) insn, builder); | 
|  | break; | 
|  | case AbstractInsnNode.VAR_INSN: | 
|  | build((VarInsnNode) insn, builder); | 
|  | break; | 
|  | case AbstractInsnNode.TYPE_INSN: | 
|  | build((TypeInsnNode) insn, builder); | 
|  | break; | 
|  | case AbstractInsnNode.FIELD_INSN: | 
|  | build((FieldInsnNode) insn, builder); | 
|  | break; | 
|  | case AbstractInsnNode.METHOD_INSN: | 
|  | build((MethodInsnNode) insn, builder); | 
|  | break; | 
|  | case AbstractInsnNode.INVOKE_DYNAMIC_INSN: | 
|  | build((InvokeDynamicInsnNode) insn, builder); | 
|  | break; | 
|  | case AbstractInsnNode.JUMP_INSN: | 
|  | build((JumpInsnNode) insn, builder); | 
|  | break; | 
|  | case AbstractInsnNode.LABEL: | 
|  | build((LabelNode) insn, builder); | 
|  | break; | 
|  | case AbstractInsnNode.LDC_INSN: | 
|  | build((LdcInsnNode) insn, builder); | 
|  | break; | 
|  | case AbstractInsnNode.IINC_INSN: | 
|  | build((IincInsnNode) insn, builder); | 
|  | break; | 
|  | case AbstractInsnNode.TABLESWITCH_INSN: | 
|  | build((TableSwitchInsnNode) insn, builder); | 
|  | break; | 
|  | case AbstractInsnNode.LOOKUPSWITCH_INSN: | 
|  | build((LookupSwitchInsnNode) insn, builder); | 
|  | break; | 
|  | case AbstractInsnNode.MULTIANEWARRAY_INSN: | 
|  | build((MultiANewArrayInsnNode) insn, builder); | 
|  | break; | 
|  | case AbstractInsnNode.LINE: | 
|  | build((LineNumberNode) insn, builder); | 
|  | break; | 
|  | default: | 
|  | throw new Unreachable("Unexpected instruction " + insn); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void processLocalVariablesAtExit(AbstractInsnNode insn, IRBuilder builder) { | 
|  | assert isReturn(insn) || isThrow(insn); | 
|  | // Read all locals live at exit to ensure liveness. | 
|  | for (Local local : state.getLocals()) { | 
|  | if (local.info != null) { | 
|  | builder.addDebugLocalEnd(local.slot.register, local.info); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private void build(InsnNode insn, IRBuilder builder) { | 
|  | int opcode = insn.getOpcode(); | 
|  | switch (opcode) { | 
|  | case Opcodes.NOP: | 
|  | // Intentionally left empty. | 
|  | break; | 
|  | case Opcodes.ACONST_NULL: | 
|  | builder.addNullConst(state.push(JarState.NULL_TYPE)); | 
|  | break; | 
|  | case Opcodes.ICONST_M1: | 
|  | case Opcodes.ICONST_0: | 
|  | case Opcodes.ICONST_1: | 
|  | case Opcodes.ICONST_2: | 
|  | case Opcodes.ICONST_3: | 
|  | case Opcodes.ICONST_4: | 
|  | case Opcodes.ICONST_5: | 
|  | builder.addIntConst(state.push(Type.INT_TYPE), opcode - Opcodes.ICONST_0); | 
|  | break; | 
|  | case Opcodes.LCONST_0: | 
|  | case Opcodes.LCONST_1: | 
|  | builder.addLongConst(state.push(Type.LONG_TYPE), opcode - Opcodes.LCONST_0); | 
|  | break; | 
|  | case Opcodes.FCONST_0: | 
|  | case Opcodes.FCONST_1: | 
|  | case Opcodes.FCONST_2: | 
|  | builder.addFloatConst(state.push(Type.FLOAT_TYPE), | 
|  | Float.floatToRawIntBits(opcode - Opcodes.FCONST_0)); | 
|  | break; | 
|  | case Opcodes.DCONST_0: | 
|  | case Opcodes.DCONST_1: | 
|  | builder.addDoubleConst(state.push(Type.DOUBLE_TYPE), | 
|  | Double.doubleToRawLongBits(opcode - Opcodes.DCONST_0)); | 
|  | break; | 
|  | case Opcodes.IALOAD: | 
|  | case Opcodes.LALOAD: | 
|  | case Opcodes.FALOAD: | 
|  | case Opcodes.DALOAD: | 
|  | case Opcodes.AALOAD: | 
|  | case Opcodes.BALOAD: | 
|  | case Opcodes.CALOAD: | 
|  | case Opcodes.SALOAD: { | 
|  | Slot index = state.pop(Type.INT_TYPE); | 
|  | Slot array = state.pop(JarState.ARRAY_TYPE); | 
|  | Type elementType = array.getArrayElementType(); | 
|  | if (elementType == null) { | 
|  | elementType = getArrayElementTypeForOpcode(opcode); | 
|  | } | 
|  | int dest = state.push(elementType); | 
|  | assert isCompatibleArrayElementType(opcode, elementType); | 
|  | builder.addArrayGet(memberType(elementType), dest, array.register, index.register); | 
|  | break; | 
|  | } | 
|  | case Opcodes.IASTORE: | 
|  | case Opcodes.LASTORE: | 
|  | case Opcodes.FASTORE: | 
|  | case Opcodes.DASTORE: | 
|  | case Opcodes.AASTORE: | 
|  | case Opcodes.BASTORE: | 
|  | case Opcodes.CASTORE: | 
|  | case Opcodes.SASTORE: { | 
|  | Slot value = state.pop(); | 
|  | Slot index = state.pop(Type.INT_TYPE); | 
|  | Slot array = state.pop(JarState.ARRAY_TYPE); | 
|  | Type elementType = array.getArrayElementType(); | 
|  | if (elementType == null) { | 
|  | elementType = getArrayElementTypeForOpcode(opcode); | 
|  | } | 
|  | assert isCompatibleArrayElementType(opcode, elementType); | 
|  | assert isCompatibleArrayElementType(opcode, value.type); | 
|  | builder.addArrayPut( | 
|  | memberType(elementType), value.register, array.register, index.register); | 
|  | break; | 
|  | } | 
|  | case Opcodes.POP: { | 
|  | Slot value = state.pop(); | 
|  | assert value.isCategory1(); | 
|  | break; | 
|  | } | 
|  | case Opcodes.POP2: { | 
|  | Slot value = state.pop(); | 
|  | if (value.isCategory1()) { | 
|  | Slot value2 = state.pop(); | 
|  | assert value2.isCategory1(); | 
|  | } | 
|  | break; | 
|  | } | 
|  | case Opcodes.DUP: { | 
|  | Slot value = state.peek(); | 
|  | assert value.isCategory1(); | 
|  | int copy = state.push(value.type); | 
|  | builder.addMove(valueType(value.type), copy, value.register); | 
|  | break; | 
|  | } | 
|  | case Opcodes.DUP_X1: { | 
|  | // Stack transformation: ..., v2, v1 -> ..., v1, v2, v1 | 
|  | Slot value1 = state.pop(); | 
|  | Slot value2 = state.pop(); | 
|  | assert value1.isCategory1() && value2.isCategory1(); | 
|  | int stack2 = state.push(value1.type); | 
|  | int stack1 = state.push(value2.type); | 
|  | int stack0 = state.push(value1.type); | 
|  | assert value2.register == stack2; | 
|  | assert value1.register == stack1; | 
|  | // stack0 is new top-of-stack. | 
|  | builder.addMove(valueType(value1.type), stack0, stack1); | 
|  | builder.addMove(valueType(value2.type), stack1, stack2); | 
|  | builder.addMove(valueType(value1.type), stack2, stack0); | 
|  | break; | 
|  | } | 
|  | case Opcodes.DUP_X2: { | 
|  | Slot value1 = state.pop(); | 
|  | Slot value2 = state.pop(); | 
|  | assert value1.isCategory1(); | 
|  | if (value2.isCategory1()) { | 
|  | Slot value3 = state.pop(); | 
|  | assert value3.isCategory1(); | 
|  | // Stack transformation: ..., v3, v2, v1 -> ..., v1, v3, v2, v1 | 
|  | dupOneBelowTwo(value3, value2, value1, builder); | 
|  | } else { | 
|  | // Stack transformation: ..., w2, v1 -> ..., v1, w2, v1 | 
|  | dupOneBelowOne(value2, value1, builder); | 
|  | } | 
|  | break; | 
|  | } | 
|  | case Opcodes.DUP2: { | 
|  | Slot value1 = state.pop(); | 
|  | if (value1.isCategory1()) { | 
|  | Slot value2 = state.pop(); | 
|  | // Stack transformation: ..., v2, v1 -> ..., v2, v1, v2, v1 | 
|  | assert value2.isCategory1(); | 
|  | state.push(value2.type); | 
|  | state.push(value1.type); | 
|  | int copy2 = state.push(value2.type); | 
|  | int copy1 = state.push(value1.type); | 
|  | builder.addMove(valueType(value1.type), copy1, value1.register); | 
|  | builder.addMove(valueType(value2.type), copy2, value2.register); | 
|  | } else { | 
|  | // Stack transformation: ..., w1 -> ..., w1, w1 | 
|  | state.push(value1.type); | 
|  | int copy1 = state.push(value1.type); | 
|  | builder.addMove(valueType(value1.type), copy1, value1.register); | 
|  | } | 
|  | break; | 
|  | } | 
|  | case Opcodes.DUP2_X1: { | 
|  | Slot value1 = state.pop(); | 
|  | Slot value2 = state.pop(); | 
|  | assert value2.isCategory1(); | 
|  | if (value1.isCategory1()) { | 
|  | // Stack transformation: ..., v3, v2, v1 -> v2, v1, v3, v2, v1 | 
|  | Slot value3 = state.pop(); | 
|  | assert value3.isCategory1(); | 
|  | dupTwoBelowOne(value3, value2, value1, builder); | 
|  | } else { | 
|  | // Stack transformation: ..., v2, w1 -> ..., w1, v2, w1 | 
|  | dupOneBelowOne(value2, value1, builder); | 
|  | } | 
|  | break; | 
|  | } | 
|  | case Opcodes.DUP2_X2: { | 
|  | Slot value1 = state.pop(); | 
|  | Slot value2 = state.pop(); | 
|  | if (!value1.isCategory1() && !value2.isCategory1()) { | 
|  | // State transformation: ..., w2, w1 -> w1, w2, w1 | 
|  | dupOneBelowOne(value2, value1, builder); | 
|  | } else { | 
|  | Slot value3 = state.pop(); | 
|  | if (!value1.isCategory1()) { | 
|  | assert value2.isCategory1(); | 
|  | assert value3.isCategory1(); | 
|  | // State transformation: ..., v3, v2, w1 -> w1, v3, v2, w1 | 
|  | dupOneBelowTwo(value3, value2, value1, builder); | 
|  | } else if (!value3.isCategory1()) { | 
|  | assert value1.isCategory1(); | 
|  | assert value2.isCategory1(); | 
|  | // State transformation: ..., w3, v2, v1 -> v2, v1, w3, v2, v1 | 
|  | dupTwoBelowOne(value3, value2, value1, builder); | 
|  | } else { | 
|  | Slot value4 = state.pop(); | 
|  | assert value1.isCategory1(); | 
|  | assert value2.isCategory1(); | 
|  | assert value3.isCategory1(); | 
|  | assert value4.isCategory1(); | 
|  | // State transformation: ..., v4, v3, v2, v1 -> v2, v1, v4, v3, v2, v1 | 
|  | dupTwoBelowTwo(value4, value3, value2, value1, builder); | 
|  | } | 
|  | } | 
|  | break; | 
|  | } | 
|  | case Opcodes.SWAP: { | 
|  | Slot value1 = state.pop(); | 
|  | Slot value2 = state.pop(); | 
|  | assert value1.isCategory1() && value2.isCategory1(); | 
|  | state.push(value1.type); | 
|  | state.push(value2.type); | 
|  | int tmp = state.push(value1.type); | 
|  | builder.addMove(valueType(value1.type), tmp, value1.register); | 
|  | builder.addMove(valueType(value2.type), value1.register, value2.register); | 
|  | builder.addMove(valueType(value1.type), value2.register, tmp); | 
|  | state.pop(); // Remove temp. | 
|  | break; | 
|  | } | 
|  | case Opcodes.IADD: | 
|  | case Opcodes.LADD: | 
|  | case Opcodes.FADD: | 
|  | case Opcodes.DADD: | 
|  | case Opcodes.ISUB: | 
|  | case Opcodes.LSUB: | 
|  | case Opcodes.FSUB: | 
|  | case Opcodes.DSUB: | 
|  | case Opcodes.IMUL: | 
|  | case Opcodes.LMUL: | 
|  | case Opcodes.FMUL: | 
|  | case Opcodes.DMUL: | 
|  | case Opcodes.IDIV: | 
|  | case Opcodes.LDIV: | 
|  | case Opcodes.FDIV: | 
|  | case Opcodes.DDIV: | 
|  | case Opcodes.IREM: | 
|  | case Opcodes.LREM: | 
|  | case Opcodes.FREM: | 
|  | case Opcodes.DREM: { | 
|  | Type type = opType(opcode); | 
|  | NumericType numericType = numericType(type); | 
|  | int right = state.pop(type).register; | 
|  | int left = state.pop(type).register; | 
|  | int dest = state.push(type); | 
|  | if (opcode <= Opcodes.DADD) { | 
|  | builder.addAdd(numericType, dest, left, right); | 
|  | } else if (opcode <= Opcodes.DSUB) { | 
|  | builder.addSub(numericType, dest, left, right); | 
|  | } else if (opcode <= Opcodes.DMUL) { | 
|  | builder.addMul(numericType, dest, left, right); | 
|  | } else if (opcode <= Opcodes.DDIV) { | 
|  | builder.addDiv(numericType, dest, left, right); | 
|  | } else { | 
|  | assert Opcodes.IREM <= opcode && opcode <= Opcodes.DREM; | 
|  | builder.addRem(numericType, dest, left, right); | 
|  | } | 
|  | break; | 
|  | } | 
|  | case Opcodes.INEG: | 
|  | case Opcodes.LNEG: | 
|  | case Opcodes.FNEG: | 
|  | case Opcodes.DNEG: { | 
|  | Type type = opType(opcode); | 
|  | NumericType numericType = numericType(type); | 
|  | int value = state.pop(type).register; | 
|  | int dest = state.push(type); | 
|  | builder.addNeg(numericType, dest, value); | 
|  | break; | 
|  | } | 
|  | case Opcodes.ISHL: | 
|  | case Opcodes.LSHL: | 
|  | case Opcodes.ISHR: | 
|  | case Opcodes.LSHR: | 
|  | case Opcodes.IUSHR: | 
|  | case Opcodes.LUSHR: { | 
|  | Type type = opType(opcode); | 
|  | NumericType numericType = numericType(type); | 
|  | int right = state.pop(Type.INT_TYPE).register; | 
|  | int left = state.pop(type).register; | 
|  | int dest = state.push(type); | 
|  | if (opcode <= Opcodes.LSHL) { | 
|  | builder.addShl(numericType, dest, left, right); | 
|  | } else if (opcode <= Opcodes.LSHR) { | 
|  | builder.addShr(numericType, dest, left, right); | 
|  | } else { | 
|  | assert opcode == Opcodes.IUSHR || opcode == Opcodes.LUSHR; | 
|  | builder.addUshr(numericType, dest, left, right); | 
|  | } | 
|  | break; | 
|  | } | 
|  | case Opcodes.IAND: | 
|  | case Opcodes.LAND: { | 
|  | Type type = opcode == Opcodes.IAND ? Type.INT_TYPE : Type.LONG_TYPE; | 
|  | int right = state.pop(type).register; | 
|  | int left = state.pop(type).register; | 
|  | int dest = state.push(type); | 
|  | builder.addAnd(numericType(type), dest, left, right); | 
|  | break; | 
|  | } | 
|  | case Opcodes.IOR: | 
|  | case Opcodes.LOR: { | 
|  | Type type = opcode == Opcodes.IOR ? Type.INT_TYPE : Type.LONG_TYPE; | 
|  | int right = state.pop(type).register; | 
|  | int left = state.pop(type).register; | 
|  | int dest = state.push(type); | 
|  | builder.addOr(numericType(type), dest, left, right); | 
|  | break; | 
|  | } | 
|  | case Opcodes.IXOR: | 
|  | case Opcodes.LXOR: { | 
|  | Type type = opcode == Opcodes.IXOR ? Type.INT_TYPE : Type.LONG_TYPE; | 
|  | int right = state.pop(type).register; | 
|  | int left = state.pop(type).register; | 
|  | int dest = state.push(type); | 
|  | builder.addXor(numericType(type), dest, left, right); | 
|  | break; | 
|  | } | 
|  | case Opcodes.I2L: | 
|  | buildConversion(Type.INT_TYPE, Type.LONG_TYPE, builder); | 
|  | break; | 
|  | case Opcodes.I2F: | 
|  | buildConversion(Type.INT_TYPE, Type.FLOAT_TYPE, builder); | 
|  | break; | 
|  | case Opcodes.I2D: | 
|  | buildConversion(Type.INT_TYPE, Type.DOUBLE_TYPE, builder); | 
|  | break; | 
|  | case Opcodes.L2I: | 
|  | buildConversion(Type.LONG_TYPE, Type.INT_TYPE, builder); | 
|  | break; | 
|  | case Opcodes.L2F: | 
|  | buildConversion(Type.LONG_TYPE, Type.FLOAT_TYPE, builder); | 
|  | break; | 
|  | case Opcodes.L2D: | 
|  | buildConversion(Type.LONG_TYPE, Type.DOUBLE_TYPE, builder); | 
|  | break; | 
|  | case Opcodes.F2I: | 
|  | buildConversion(Type.FLOAT_TYPE, Type.INT_TYPE, builder); | 
|  | break; | 
|  | case Opcodes.F2L: | 
|  | buildConversion(Type.FLOAT_TYPE, Type.LONG_TYPE, builder); | 
|  | break; | 
|  | case Opcodes.F2D: | 
|  | buildConversion(Type.FLOAT_TYPE, Type.DOUBLE_TYPE, builder); | 
|  | break; | 
|  | case Opcodes.D2I: | 
|  | buildConversion(Type.DOUBLE_TYPE, Type.INT_TYPE, builder); | 
|  | break; | 
|  | case Opcodes.D2L: | 
|  | buildConversion(Type.DOUBLE_TYPE, Type.LONG_TYPE, builder); | 
|  | break; | 
|  | case Opcodes.D2F: | 
|  | buildConversion(Type.DOUBLE_TYPE, Type.FLOAT_TYPE, builder); | 
|  | break; | 
|  | case Opcodes.I2B: | 
|  | buildConversion(Type.INT_TYPE, Type.BYTE_TYPE, builder); | 
|  | break; | 
|  | case Opcodes.I2C: | 
|  | buildConversion(Type.INT_TYPE, Type.CHAR_TYPE, builder); | 
|  | break; | 
|  | case Opcodes.I2S: | 
|  | buildConversion(Type.INT_TYPE, Type.SHORT_TYPE, builder); | 
|  | break; | 
|  | case Opcodes.LCMP: { | 
|  | Slot right = state.pop(Type.LONG_TYPE); | 
|  | Slot left = state.pop(Type.LONG_TYPE); | 
|  | int dest = state.push(Type.INT_TYPE); | 
|  | builder.addCmp(NumericType.LONG, Bias.NONE, dest, left.register, right.register); | 
|  | break; | 
|  | } | 
|  | case Opcodes.FCMPL: | 
|  | case Opcodes.FCMPG: { | 
|  | Slot right = state.pop(Type.FLOAT_TYPE); | 
|  | Slot left = state.pop(Type.FLOAT_TYPE); | 
|  | int dest = state.push(Type.INT_TYPE); | 
|  | Bias bias = opcode == Opcodes.FCMPL ? Bias.LT : Bias.GT; | 
|  | builder.addCmp(NumericType.FLOAT, bias, dest, left.register, right.register); | 
|  | break; | 
|  | } | 
|  | case Opcodes.DCMPL: | 
|  | case Opcodes.DCMPG: { | 
|  | Slot right = state.pop(Type.DOUBLE_TYPE); | 
|  | Slot left = state.pop(Type.DOUBLE_TYPE); | 
|  | int dest = state.push(Type.INT_TYPE); | 
|  | Bias bias = opcode == Opcodes.DCMPL ? Bias.LT : Bias.GT; | 
|  | builder.addCmp(NumericType.DOUBLE, bias, dest, left.register, right.register); | 
|  | break; | 
|  | } | 
|  | case Opcodes.IRETURN: { | 
|  | Slot value = state.pop(Type.INT_TYPE); | 
|  | addReturn(insn, ValueTypeConstraint.INT, value.register, builder); | 
|  | break; | 
|  | } | 
|  | case Opcodes.LRETURN: { | 
|  | Slot value = state.pop(Type.LONG_TYPE); | 
|  | addReturn(insn, ValueTypeConstraint.LONG, value.register, builder); | 
|  | break; | 
|  | } | 
|  | case Opcodes.FRETURN: { | 
|  | Slot value = state.pop(Type.FLOAT_TYPE); | 
|  | addReturn(insn, ValueTypeConstraint.FLOAT, value.register, builder); | 
|  | break; | 
|  | } | 
|  | case Opcodes.DRETURN: { | 
|  | Slot value = state.pop(Type.DOUBLE_TYPE); | 
|  | addReturn(insn, ValueTypeConstraint.DOUBLE, value.register, builder); | 
|  | break; | 
|  | } | 
|  | case Opcodes.ARETURN: { | 
|  | Slot obj = state.pop(JarState.REFERENCE_TYPE); | 
|  | addReturn(insn, ValueTypeConstraint.OBJECT, obj.register, builder); | 
|  | break; | 
|  | } | 
|  | case Opcodes.RETURN: { | 
|  | addReturn(insn, null, -1, builder); | 
|  | break; | 
|  | } | 
|  | case Opcodes.ARRAYLENGTH: { | 
|  | Slot array = state.pop(JarState.ARRAY_TYPE); | 
|  | int dest = state.push(Type.INT_TYPE); | 
|  | builder.addArrayLength(dest, array.register); | 
|  | break; | 
|  | } | 
|  | case Opcodes.ATHROW: { | 
|  | Slot object = state.pop(JarState.OBJECT_TYPE); | 
|  | addThrow(insn, object.register, builder); | 
|  | break; | 
|  | } | 
|  | case Opcodes.MONITORENTER: { | 
|  | Slot object = state.pop(JarState.REFERENCE_TYPE); | 
|  | builder.addMonitor(Monitor.Type.ENTER, object.register); | 
|  | break; | 
|  | } | 
|  | case Opcodes.MONITOREXIT: { | 
|  | Slot object = state.pop(JarState.REFERENCE_TYPE); | 
|  | builder.addMonitor(Monitor.Type.EXIT, object.register); | 
|  | break; | 
|  | } | 
|  | default: | 
|  | throw new Unreachable("Unexpected Insn opcode: " + insn.getOpcode()); | 
|  | } | 
|  | } | 
|  |  | 
|  | private boolean isExitingThrow(InsnNode insn) { | 
|  | List<TryCatchBlock> handlers = getTryHandlers(insn); | 
|  | if (handlers.isEmpty()) { | 
|  | return true; | 
|  | } | 
|  | if (!generateMethodSynchronization() || handlers.size() > 1) { | 
|  | return false; | 
|  | } | 
|  | return handlers.get(0) == EXCEPTIONAL_SYNC_EXIT; | 
|  | } | 
|  |  | 
|  | private void addThrow(InsnNode insn, int register, IRBuilder builder) { | 
|  | if (isExitingThrow(insn)) { | 
|  | processLocalVariablesAtExit(insn, builder); | 
|  | } else { | 
|  | int offset = getOffset(insn); | 
|  | Int2ReferenceSortedMap<BlockInfo> cfg = builder.getCFG(); | 
|  | BlockInfo info = cfg.get(cfg.headMap(offset + 1).lastIntKey()); | 
|  | assert info.normalSuccessors.isEmpty(); | 
|  | assert !info.exceptionalSuccessors.isEmpty(); | 
|  | Int2ReferenceMap<DebugLocalInfo> ending = new Int2ReferenceOpenHashMap<>(); | 
|  | for (int successorOffset : info.exceptionalSuccessors) { | 
|  | if (successorOffset == EXCEPTIONAL_SYNC_EXIT_OFFSET) { | 
|  | // TODO(zerny): It would likely be beneficial to keep locals live until the exit from the | 
|  | // exceptional sync exit block. | 
|  | continue; | 
|  | } | 
|  | LocalChangeAtOffset localChange = state.getLocalChange(offset, successorOffset); | 
|  | for (Local localEnd : localChange.getLocalsToClose()) { | 
|  | ending.put(localEnd.slot.register, localEnd.info); | 
|  | } | 
|  | } | 
|  | ending.forEach(builder::addDebugLocalEnd); | 
|  | } | 
|  | builder.addThrow(register); | 
|  | } | 
|  |  | 
|  | private void addReturn( | 
|  | InsnNode insn, ValueTypeConstraint constraint, int register, IRBuilder builder) { | 
|  | processLocalVariablesAtExit(insn, builder); | 
|  | if (constraint == null) { | 
|  | assert register == -1; | 
|  | builder.addReturn(); | 
|  | } else { | 
|  | builder.addReturn(register); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void dupOneBelowTwo(Slot value3, Slot value2, Slot value1, IRBuilder builder) { | 
|  | int stack3 = state.push(value1.type); | 
|  | int stack2 = state.push(value3.type); | 
|  | int stack1 = state.push(value2.type); | 
|  | int stack0 = state.push(value1.type); | 
|  | assert value3.register == stack3; | 
|  | assert value2.register == stack2; | 
|  | assert value1.register == stack1; | 
|  | builder.addMove(valueType(value1.type), stack0, stack1); | 
|  | builder.addMove(valueType(value2.type), stack1, stack2); | 
|  | builder.addMove(valueType(value3.type), stack2, stack3); | 
|  | builder.addMove(valueType(value1.type), stack3, stack0); | 
|  | } | 
|  |  | 
|  | private void dupOneBelowOne(Slot value2, Slot value1, IRBuilder builder) { | 
|  | int stack2 = state.push(value1.type); | 
|  | int stack1 = state.push(value2.type); | 
|  | int stack0 = state.push(value1.type); | 
|  | assert value2.register == stack2; | 
|  | assert value1.register == stack1; | 
|  | builder.addMove(valueType(value1.type), stack0, stack1); | 
|  | builder.addMove(valueType(value2.type), stack1, stack2); | 
|  | builder.addMove(valueType(value1.type), stack2, stack0); | 
|  | } | 
|  |  | 
|  | private void dupTwoBelowOne(Slot value3, Slot value2, Slot value1, IRBuilder builder) { | 
|  | int stack4 = state.push(value2.type); | 
|  | int stack3 = state.push(value1.type); | 
|  | int stack2 = state.push(value3.type); | 
|  | int stack1 = state.push(value2.type); | 
|  | int stack0 = state.push(value1.type); | 
|  | assert value3.register == stack4; | 
|  | assert value2.register == stack3; | 
|  | assert value1.register == stack2; | 
|  | builder.addMove(valueType(value1.type), stack0, stack2); | 
|  | builder.addMove(valueType(value2.type), stack1, stack3); | 
|  | builder.addMove(valueType(value3.type), stack2, stack4); | 
|  | builder.addMove(valueType(value1.type), stack3, stack0); | 
|  | builder.addMove(valueType(value2.type), stack4, stack1); | 
|  | } | 
|  |  | 
|  | private void dupTwoBelowTwo(Slot value4, Slot value3, Slot value2, Slot value1, | 
|  | IRBuilder builder) { | 
|  | int stack5 = state.push(value2.type); | 
|  | int stack4 = state.push(value1.type); | 
|  | int stack3 = state.push(value4.type); | 
|  | int stack2 = state.push(value3.type); | 
|  | int stack1 = state.push(value2.type); | 
|  | int stack0 = state.push(value1.type); | 
|  | assert value4.register == stack5; | 
|  | assert value3.register == stack4; | 
|  | assert value2.register == stack3; | 
|  | assert value1.register == stack2; | 
|  | builder.addMove(valueType(value1.type), stack0, stack2); | 
|  | builder.addMove(valueType(value2.type), stack1, stack3); | 
|  | builder.addMove(valueType(value3.type), stack2, stack4); | 
|  | builder.addMove(valueType(value4.type), stack3, stack5); | 
|  | builder.addMove(valueType(value1.type), stack4, stack0); | 
|  | builder.addMove(valueType(value2.type), stack5, stack1); | 
|  | } | 
|  |  | 
|  | private void buildConversion(Type from, Type to, IRBuilder builder) { | 
|  | int source = state.pop(from).register; | 
|  | int dest = state.push(to); | 
|  | builder.addConversion(numericType(to), numericType(from), dest, source); | 
|  | } | 
|  |  | 
|  | private void build(IntInsnNode insn, IRBuilder builder) { | 
|  | switch (insn.getOpcode()) { | 
|  | case Opcodes.BIPUSH: | 
|  | case Opcodes.SIPUSH: { | 
|  | int dest = state.push(Type.INT_TYPE); | 
|  | builder.addIntConst(dest, insn.operand); | 
|  | break; | 
|  | } | 
|  | case Opcodes.NEWARRAY: { | 
|  | String desc = arrayTypeDesc(insn.operand); | 
|  | Type type = application.getAsmType(desc); | 
|  | DexType dexType = application.getTypeFromDescriptor(desc); | 
|  | int count = state.pop(Type.INT_TYPE).register; | 
|  | int array = state.push(type); | 
|  | builder.addNewArrayEmpty(array, count, dexType); | 
|  | break; | 
|  | } | 
|  | default: | 
|  | throw new Unreachable("Unexpected IntInsn opcode: " + insn.getOpcode()); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void build(VarInsnNode insn, IRBuilder builder) { | 
|  | int opcode = insn.getOpcode(); | 
|  | Type expectedType; | 
|  | switch (opcode) { | 
|  | case Opcodes.ILOAD: | 
|  | case Opcodes.ISTORE: | 
|  | expectedType = Type.INT_TYPE; | 
|  | break; | 
|  | case Opcodes.FLOAD: | 
|  | case Opcodes.FSTORE: | 
|  | expectedType = Type.FLOAT_TYPE; | 
|  | break; | 
|  | case Opcodes.LLOAD: | 
|  | case Opcodes.LSTORE: | 
|  | expectedType = Type.LONG_TYPE; | 
|  | break; | 
|  | case Opcodes.DLOAD: | 
|  | case Opcodes.DSTORE: | 
|  | expectedType = Type.DOUBLE_TYPE; | 
|  | break; | 
|  | case Opcodes.ALOAD: | 
|  | case Opcodes.ASTORE: | 
|  | expectedType = JarState.REFERENCE_TYPE; | 
|  | break; | 
|  | case Opcodes.RET: { | 
|  | throw new Unreachable("RET should be handled by the ASM jsr inliner"); | 
|  | } | 
|  | default: | 
|  | throw new Unreachable("Unexpected VarInsn opcode: " + insn.getOpcode()); | 
|  | } | 
|  | if (Opcodes.ILOAD <= opcode && opcode <= Opcodes.ALOAD) { | 
|  | Slot src = state.readLocal(insn.var, expectedType); | 
|  | int dest = state.push(src.type); | 
|  | builder.addMove(valueType(src.type), dest, src.register); | 
|  | } else { | 
|  | assert Opcodes.ISTORE <= opcode && opcode <= Opcodes.ASTORE; | 
|  | Slot src = state.pop(expectedType); | 
|  | int dest = state.getLocalRegister(insn.var, src.type); | 
|  | builder.addMove(valueType(src.type), dest, src.register); | 
|  | state.writeLocal(insn.var, src.type); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void build(TypeInsnNode insn, IRBuilder builder) { | 
|  | Type type = application.getAsmObjectType(insn.desc); | 
|  | switch (insn.getOpcode()) { | 
|  | case Opcodes.NEW: { | 
|  | DexType dexType = application.getTypeFromName(insn.desc); | 
|  | int dest = state.push(type); | 
|  | builder.addNewInstance(dest, dexType); | 
|  | break; | 
|  | } | 
|  | case Opcodes.ANEWARRAY: { | 
|  | Type arrayType = makeArrayType(type); | 
|  | DexType dexArrayType = application.getTypeFromDescriptor(arrayType.getDescriptor()); | 
|  | int count = state.pop(Type.INT_TYPE).register; | 
|  | int dest = state.push(arrayType); | 
|  | builder.addNewArrayEmpty(dest, count, dexArrayType); | 
|  | break; | 
|  | } | 
|  | case Opcodes.CHECKCAST: { | 
|  | DexType dexType = application.getTypeFromDescriptor(type.getDescriptor()); | 
|  | // Pop the top value and push it back on with the checked type. | 
|  | state.pop(type); | 
|  | int object = state.push(type); | 
|  | builder.addCheckCast(object, dexType); | 
|  | break; | 
|  | } | 
|  | case Opcodes.INSTANCEOF: { | 
|  | int obj = state.pop(JarState.REFERENCE_TYPE).register; | 
|  | int dest = state.push(Type.INT_TYPE); | 
|  | DexType dexType = application.getTypeFromDescriptor(type.getDescriptor()); | 
|  | builder.addInstanceOf(dest, obj, dexType); | 
|  | break; | 
|  | } | 
|  | default: | 
|  | throw new Unreachable("Unexpected TypeInsn opcode: " + insn.getOpcode()); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void build(FieldInsnNode insn, IRBuilder builder) { | 
|  | DexField field = application.getField(insn.owner, insn.name, insn.desc); | 
|  | Type type = application.getAsmType(insn.desc); | 
|  | switch (insn.getOpcode()) { | 
|  | case Opcodes.GETSTATIC: | 
|  | builder.addStaticGet(state.push(type), field); | 
|  | break; | 
|  | case Opcodes.PUTSTATIC: | 
|  | builder.addStaticPut(state.pop(type).register, field); | 
|  | break; | 
|  | case Opcodes.GETFIELD: { | 
|  | Slot object = state.pop(JarState.OBJECT_TYPE); | 
|  | int dest = state.push(type); | 
|  | builder.addInstanceGet(dest, object.register, field); | 
|  | break; | 
|  | } | 
|  | case Opcodes.PUTFIELD: { | 
|  | Slot value = state.pop(type); | 
|  | Slot object = state.pop(JarState.OBJECT_TYPE); | 
|  | builder.addInstancePut(value.register, object.register, field); | 
|  | break; | 
|  | } | 
|  | default: | 
|  | throw new Unreachable("Unexpected FieldInsn opcode: " + insn.getOpcode()); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void build(MethodInsnNode insn, IRBuilder builder) { | 
|  | // Resolve the target method of the invoke. | 
|  | DexMethod method = application.getMethod(insn.owner, insn.name, insn.desc); | 
|  |  | 
|  | buildInvoke( | 
|  | insn.desc, | 
|  | application.getAsmObjectType(insn.owner), | 
|  | insn.getOpcode() != Opcodes.INVOKESTATIC, | 
|  | builder, | 
|  | (types, registers) -> { | 
|  | Invoke.Type invokeType = invokeType(insn); | 
|  | DexProto callSiteProto = null; | 
|  | DexMethod targetMethod = method; | 
|  | if (invokeType == Invoke.Type.POLYMORPHIC) { | 
|  | if (insn.owner.equals(INTERNAL_NAME_METHOD_HANDLE)) { | 
|  | targetMethod = application | 
|  | .getMethod(insn.owner, insn.name, POLYMORPHIC_DEFAULT_SIGNATURE_DESC); | 
|  | } else if (insn.owner.equals(INTERNAL_NAME_VAR_HANDLE)) { | 
|  | switch (insn.name) { | 
|  | case "compareAndExchange": | 
|  | case "compareAndExchangeAcquire": | 
|  | case "compareAndExchangeRelease": | 
|  | case "get": | 
|  | case "getAcquire": | 
|  | case "getAndAdd": | 
|  | case "getAndAddAcquire": | 
|  | case "getAndAddRelease": | 
|  | case "getAndBitwiseAnd": | 
|  | case "getAndBitwiseAndAcquire": | 
|  | case "getAndBitwiseAndRelease": | 
|  | case "getAndBitwiseOr": | 
|  | case "getAndBitwiseOrAcquire": | 
|  | case "getAndBitwiseOrRelease": | 
|  | case "getAndBitwiseXor": | 
|  | case "getAndBitwiseXorAcquire": | 
|  | case "getAndBitwiseXorRelease": | 
|  | case "getAndSet": | 
|  | case "getAndSetAcquire": | 
|  | case "getAndSetRelease": | 
|  | case "getOpaque": | 
|  | case "getVolatile": { | 
|  | targetMethod = application | 
|  | .getMethod(insn.owner, insn.name, POLYMORPHIC_DEFAULT_SIGNATURE_DESC); | 
|  | break; | 
|  | } | 
|  | case "set": | 
|  | case "setOpaque": | 
|  | case "setRelease": | 
|  | case "setVolatile": { | 
|  | targetMethod = application | 
|  | .getMethod(insn.owner, insn.name, POLYMORPHIC_VARHANDLE_SET_SIGNATURE_DESC); | 
|  | break; | 
|  | } | 
|  | case "compareAndSet": | 
|  | case "weakCompareAndSet": | 
|  | case "weakCompareAndSetAcquire": | 
|  | case "weakCompareAndSetPlain": | 
|  | case "weakCompareAndSetRelease": { | 
|  | targetMethod = application.getMethod(insn.owner, insn.name, | 
|  | POLYMORPHIC_VARHANDLE_COMPARE_AND_SET_SIGNATURE_DESC); | 
|  | break; | 
|  | } | 
|  | default: | 
|  | throw new Unreachable(); | 
|  | } | 
|  | } else { | 
|  | throw new Unreachable(); | 
|  | } | 
|  | callSiteProto = application.getProto(insn.desc); | 
|  | } | 
|  | builder.addInvoke(invokeType, targetMethod, callSiteProto, types, registers, insn.itf); | 
|  | }); | 
|  | } | 
|  |  | 
|  | private void buildInvoke( | 
|  | String methodDesc, | 
|  | Type methodOwner, | 
|  | boolean addImplicitReceiver, | 
|  | IRBuilder builder, | 
|  | BiConsumer<List<ValueType>, List<Integer>> creator) { | 
|  |  | 
|  | // Build the argument list of the form [owner, param1, ..., paramN]. | 
|  | // The arguments are in reverse order on the stack, so we pop off the parameters here. | 
|  | Type[] parameterTypes = application.getArgumentTypes(methodDesc); | 
|  | Slot[] parameterRegisters = state.popReverse(parameterTypes.length); | 
|  |  | 
|  | List<ValueType> types = new ArrayList<>(parameterTypes.length + 1); | 
|  | List<Integer> registers = new ArrayList<>(parameterTypes.length + 1); | 
|  |  | 
|  | // Add receiver argument for non-static calls. | 
|  | if (addImplicitReceiver) { | 
|  | addArgument(types, registers, methodOwner, state.pop()); | 
|  | } | 
|  |  | 
|  | // The remaining arguments are the parameters of the method. | 
|  | for (int i = 0; i < parameterTypes.length; i++) { | 
|  | addArgument(types, registers, parameterTypes[i], parameterRegisters[i]); | 
|  | } | 
|  |  | 
|  | // Create the invoke. | 
|  | creator.accept(types, registers); | 
|  |  | 
|  | // Move the result to the "top of stack". | 
|  | Type returnType = application.getReturnType(methodDesc); | 
|  | if (returnType != Type.VOID_TYPE) { | 
|  | builder.addMoveResult(state.push(returnType)); | 
|  | } | 
|  | } | 
|  |  | 
|  | private static void addArgument( | 
|  | List<ValueType> types, | 
|  | List<Integer> registers, | 
|  | Type type, | 
|  | Slot slot) { | 
|  | assert slot.isCompatibleWith(type); | 
|  | types.add(valueType(type)); | 
|  | registers.add(slot.register); | 
|  | } | 
|  |  | 
|  | private void build(InvokeDynamicInsnNode insn, IRBuilder builder) { | 
|  | DexCallSite callSite = DexCallSite.fromAsmInvokeDynamic(insn, application, clazz); | 
|  |  | 
|  | buildInvoke(insn.desc, null /* Not needed */, | 
|  | false /* Receiver is passed explicitly */, builder, | 
|  | (types, registers) -> builder.addInvokeCustom(callSite, types, registers)); | 
|  | } | 
|  |  | 
|  | private void build(JumpInsnNode insn, IRBuilder builder) { | 
|  | int[] targets = getTargets(insn); | 
|  | int opcode = insn.getOpcode(); | 
|  | if (Opcodes.IFEQ <= opcode && opcode <= Opcodes.IF_ACMPNE) { | 
|  | assert targets.length == 2; | 
|  | if (opcode <= Opcodes.IFLE) { | 
|  | Slot value = state.pop(Type.INT_TYPE); | 
|  | builder.addIfZero( | 
|  | ifType(opcode), ValueTypeConstraint.INT, value.register, targets[0], targets[1]); | 
|  | } else { | 
|  | ValueTypeConstraint valueType; | 
|  | Type expectedType; | 
|  | if (opcode < Opcodes.IF_ACMPEQ) { | 
|  | valueType = ValueTypeConstraint.INT; | 
|  | expectedType = Type.INT_TYPE; | 
|  | } else { | 
|  | valueType = ValueTypeConstraint.OBJECT; | 
|  | expectedType = JarState.REFERENCE_TYPE; | 
|  | } | 
|  | Slot value2 = state.pop(expectedType); | 
|  | Slot value1 = state.pop(expectedType); | 
|  | builder.addIf( | 
|  | ifType(opcode), valueType, value1.register, value2.register, targets[0], targets[1]); | 
|  | } | 
|  | } else { | 
|  | switch (opcode) { | 
|  | case Opcodes.GOTO: { | 
|  | assert targets.length == 1; | 
|  | builder.addGoto(targets[0]); | 
|  | break; | 
|  | } | 
|  | case Opcodes.IFNULL: | 
|  | case Opcodes.IFNONNULL: { | 
|  | Slot value = state.pop(JarState.REFERENCE_TYPE); | 
|  | If.Type type = opcode == Opcodes.IFNULL ? If.Type.EQ : If.Type.NE; | 
|  | builder.addIfZero( | 
|  | type, ValueTypeConstraint.OBJECT, value.register, targets[0], targets[1]); | 
|  | break; | 
|  | } | 
|  | case Opcodes.JSR: { | 
|  | throw new Unreachable("JSR should be handled by the ASM jsr inliner"); | 
|  | } | 
|  | default: | 
|  | throw new Unreachable("Unexpected JumpInsn opcode: " + insn.getOpcode()); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private void build(LabelNode insn, IRBuilder builder) { | 
|  | // Intentionally empty. | 
|  | } | 
|  |  | 
|  | private void build(LdcInsnNode insn, IRBuilder builder) { | 
|  | if (insn.cst instanceof Type) { | 
|  | Type type = (Type) insn.cst; | 
|  | if (type.getSort() == Type.METHOD) { | 
|  | int dest = state.push(METHOD_TYPE_TYPE); | 
|  | builder.addConstMethodType(dest, application.getProto(type.getDescriptor())); | 
|  | } else { | 
|  | int dest = state.push(type); | 
|  | builder.addConstClass(dest, application.getTypeFromDescriptor(type.getDescriptor())); | 
|  | } | 
|  | } else if (insn.cst instanceof String) { | 
|  | int dest = state.push(STRING_TYPE); | 
|  | builder.addConstString(dest, application.getString((String) insn.cst)); | 
|  | } else if (insn.cst instanceof Long) { | 
|  | int dest = state.push(Type.LONG_TYPE); | 
|  | builder.addLongConst(dest, (Long) insn.cst); | 
|  | } else if (insn.cst instanceof Double) { | 
|  | int dest = state.push(Type.DOUBLE_TYPE); | 
|  | builder.addDoubleConst(dest, Double.doubleToRawLongBits((Double) insn.cst)); | 
|  | } else if (insn.cst instanceof Integer) { | 
|  | int dest = state.push(Type.INT_TYPE); | 
|  | builder.addIntConst(dest, (Integer) insn.cst); | 
|  | } else if (insn.cst instanceof Float) { | 
|  | int dest = state.push(Type.FLOAT_TYPE); | 
|  | builder.addFloatConst(dest, Float.floatToRawIntBits((Float) insn.cst)); | 
|  | } else if (insn.cst instanceof Handle) { | 
|  | Handle handle = (Handle) insn.cst; | 
|  | int dest = state.push(METHOD_HANDLE_TYPE); | 
|  | builder.addConstMethodHandle(dest, DexMethodHandle.fromAsmHandle(handle, application, clazz)); | 
|  | } else { | 
|  | throw new CompilationError("Unsupported constant: " + insn.cst.toString()); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void build(IincInsnNode insn, IRBuilder builder) { | 
|  | int local = state.readLocal(insn.var, Type.INT_TYPE).register; | 
|  | builder.addAddLiteral(NumericType.INT, local, local, insn.incr); | 
|  | } | 
|  |  | 
|  | private void build(TableSwitchInsnNode insn, IRBuilder builder) { | 
|  | buildSwitch(insn.dflt, insn.labels, new int[]{insn.min}, builder); | 
|  | } | 
|  |  | 
|  | private void build(LookupSwitchInsnNode insn, IRBuilder builder) { | 
|  | int[] keys = new int[insn.keys.size()]; | 
|  | for (int i = 0; i < insn.keys.size(); i++) { | 
|  | keys[i] = (int) insn.keys.get(i); | 
|  | } | 
|  | buildSwitch(insn.dflt, insn.labels, keys, builder); | 
|  | } | 
|  |  | 
|  | private void buildSwitch(LabelNode dflt, List labels, int[] keys, | 
|  | IRBuilder builder) { | 
|  | int index = state.pop(Type.INT_TYPE).register; | 
|  | int fallthroughOffset = getOffset(dflt); | 
|  | int[] labelOffsets = new int[labels.size()]; | 
|  | for (int i = 0; i < labels.size(); i++) { | 
|  | int offset = getOffset((LabelNode) labels.get(i)); | 
|  | labelOffsets[i] = offset; | 
|  | } | 
|  | builder.addSwitch(index, keys, fallthroughOffset, labelOffsets); | 
|  | } | 
|  |  | 
|  | private void build(MultiANewArrayInsnNode insn, IRBuilder builder) { | 
|  | // Type of the full array. | 
|  | Type arrayType = application.getAsmObjectType(insn.desc); | 
|  | DexType dexArrayType = application.getType(arrayType); | 
|  | Slot[] slots = state.popReverse(insn.dims, Type.INT_TYPE); | 
|  | int[] dimensions = new int[insn.dims]; | 
|  | for (int i = 0; i < insn.dims; i++) { | 
|  | dimensions[i] = slots[i].register; | 
|  | } | 
|  | if (builder.isGeneratingClassFiles()) { | 
|  | int result = state.push(arrayType); | 
|  | builder.addMultiNewArray(dexArrayType, result, dimensions); | 
|  | return; | 
|  | } | 
|  | // Type of the members. Can itself be of array type, eg, 'int[]' for 'new int[x][y][]' | 
|  | // Note that this is not the same as 'arrayType.toBaseType' as is the case in the above 'int[]'. | 
|  | DexType memberType = application.getTypeFromDescriptor(insn.desc.substring(insn.dims)); | 
|  | // Push an array containing the dimensions of the desired multi-dimensional array. | 
|  | DexType dimArrayType = application.getTypeFromDescriptor(INT_ARRAY_DESC); | 
|  | builder.addInvokeNewArray(dimArrayType, insn.dims, dimensions); | 
|  | int dimensionsDestTemp = state.push(INT_ARRAY_TYPE); | 
|  | builder.addMoveResult(dimensionsDestTemp); | 
|  | // Push the class object for the member type of the array. | 
|  | int classDestTemp = state.push(CLASS_TYPE); | 
|  | builder.ensureBlockForThrowingInstruction(); | 
|  | builder.addConstClass(classDestTemp, memberType); | 
|  | // Create the actual multi-dimensional array using java.lang.reflect.Array::newInstance | 
|  | DexType reflectArrayClass = application.getTypeFromDescriptor(REFLECT_ARRAY_DESC); | 
|  | DexMethod newInstance = application.getMethod(reflectArrayClass, | 
|  | REFLECT_ARRAY_NEW_INSTANCE_NAME, REFLECT_ARRAY_NEW_INSTANCE_DESC); | 
|  | List<ValueType> argumentTypes = Arrays.asList(valueType(CLASS_TYPE), valueType(INT_ARRAY_TYPE)); | 
|  | List<Integer> argumentRegisters = Arrays.asList(classDestTemp, dimensionsDestTemp); | 
|  | builder.ensureBlockForThrowingInstruction(); | 
|  | builder.addInvoke(Invoke.Type.STATIC, newInstance, null, argumentTypes, argumentRegisters); | 
|  | // Pop the temporaries and push the final result. | 
|  | state.pop(); // classDestTemp. | 
|  | state.pop(); // dimensionsDestTemp. | 
|  | int result = state.push(arrayType); | 
|  | builder.addMoveResult(result); | 
|  | // Insert cast check to satisfy verification. | 
|  | builder.ensureBlockForThrowingInstruction(); | 
|  | builder.addCheckCast(result, dexArrayType); | 
|  | } | 
|  |  | 
|  | private void build(LineNumberNode insn, IRBuilder builder) { | 
|  | currentPosition = getCanonicalPosition(insn.line); | 
|  | builder.addDebugPosition(currentPosition); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Position getCanonicalDebugPositionAtOffset(int offset) { | 
|  | if (offset == EXCEPTIONAL_SYNC_EXIT_OFFSET) { | 
|  | return getExceptionalExitPosition(); | 
|  | } | 
|  | int index = instructionIndex(offset); | 
|  | if (index < 0 || instructionCount() <= index) { | 
|  | assert false; | 
|  | return getPreamblePosition(); | 
|  | } | 
|  | AbstractInsnNode insn = node.instructions.get(index); | 
|  | if (insn instanceof LabelNode) { | 
|  | insn = insn.getNext(); | 
|  | } | 
|  | while (insn != null && !(insn instanceof LineNumberNode)) { | 
|  | insn = insn.getPrevious(); | 
|  | } | 
|  | if (insn != null) { | 
|  | LineNumberNode line = (LineNumberNode) insn; | 
|  | return getCanonicalPosition(line.line); | 
|  | } | 
|  | return getPreamblePosition(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public Position getCurrentPosition() { | 
|  | return currentPosition; | 
|  | } | 
|  |  | 
|  | private Position getCanonicalPosition(int line) { | 
|  | return canonicalPositions.computeIfAbsent( | 
|  | line, l -> new Position(l, null, originalMethod, callerPosition)); | 
|  | } | 
|  |  | 
|  | private Position getPreamblePosition() { | 
|  | if (preamblePosition == null) { | 
|  | preamblePosition = Position.synthetic(0, originalMethod, null); | 
|  | } | 
|  | return preamblePosition; | 
|  | } | 
|  |  | 
|  | // If we need to emit a synthetic position for exceptional monitor exits, we try to cook up a | 
|  | // position that is not actually a valid program position, so as not to incorrectly position the | 
|  | // user on an exit that is not the actual exit being taken. Our heuristic for this is that if the | 
|  | // method has at least two positions we use the first position minus one as the synthetic exit. | 
|  | // If the method only has one position it is safe to just use that position. | 
|  | private Position getExceptionalExitPosition() { | 
|  | if (syntheticPosition == null) { | 
|  | if (debug) { | 
|  | int min = Integer.MAX_VALUE; | 
|  | int max = Integer.MIN_VALUE; | 
|  | for (Iterator it = node.instructions.iterator(); it.hasNext(); ) { | 
|  | Object insn = it.next(); | 
|  | if (insn instanceof LineNumberNode) { | 
|  | LineNumberNode lineNode = (LineNumberNode) insn; | 
|  | min = Math.min(min, lineNode.line); | 
|  | max = Math.max(max, lineNode.line); | 
|  | } | 
|  | } | 
|  | syntheticPosition = | 
|  | (min == Integer.MAX_VALUE) | 
|  | ? getPreamblePosition() | 
|  | : Position.synthetic(min < max ? min - 1 : min, originalMethod, callerPosition); | 
|  | } else { | 
|  | // If in release mode we explicitly associate a synthetic none position with monitor exit. | 
|  | // This is safe as the runtime must never throw at this position because the monitor cannot | 
|  | // be null and the thread calling exit can only be the same thread that entered the monitor | 
|  | // at method entry. | 
|  | syntheticPosition = Position.syntheticNone(); | 
|  | } | 
|  | } | 
|  | return syntheticPosition; | 
|  | } | 
|  |  | 
|  | // Printing helpers. | 
|  |  | 
|  | @Override | 
|  | public String toString() { | 
|  | StringBuilder builder = new StringBuilder(); | 
|  | builder.append("node.name = [").append(node.name).append("]"); | 
|  | builder.append("\n"); | 
|  | builder.append("node.desc = ").append(node.desc); | 
|  | builder.append("\n"); | 
|  | builder.append("node.maxStack = ").append(node.maxStack); | 
|  | builder.append("\n"); | 
|  | builder.append("node.maxLocals = ").append(node.maxLocals); | 
|  | builder.append("\n"); | 
|  | builder.append("node.locals.size = ").append(node.localVariables.size()); | 
|  | builder.append("\n"); | 
|  | builder.append("node.insns.size = ").append(node.instructions.size()); | 
|  | builder.append("\n"); | 
|  | for (int i = 0; i < parameterTypes.size(); i++) { | 
|  | builder.append("arg ").append(i).append(", type: ").append(parameterTypes.get(i)) | 
|  | .append("\n"); | 
|  | } | 
|  | for (int i = 0; i < node.localVariables.size(); i++) { | 
|  | LocalVariableNode local = (LocalVariableNode) node.localVariables.get(i); | 
|  | builder.append("local ").append(i) | 
|  | .append(", name: ").append(local.name) | 
|  | .append(", desc: ").append(local.desc) | 
|  | .append(", index: ").append(local.index) | 
|  | .append(", [").append(getOffset(local.start)) | 
|  | .append("..").append(getOffset(local.end)) | 
|  | .append("[\n"); | 
|  | } | 
|  | for (int i = 0; i < node.tryCatchBlocks.size(); i++) { | 
|  | TryCatchBlockNode tryCatchBlockNode = (TryCatchBlockNode) node.tryCatchBlocks.get(i); | 
|  | builder.append("[").append(getOffset(tryCatchBlockNode.start)) | 
|  | .append("..").append(getOffset(tryCatchBlockNode.end)).append("[ ") | 
|  | .append(tryCatchBlockNode.type).append(" -> ") | 
|  | .append(getOffset(tryCatchBlockNode.handler)) | 
|  | .append("\n"); | 
|  | } | 
|  | for (int i = 0; i < node.instructions.size(); i++) { | 
|  | AbstractInsnNode insn = node.instructions.get(i); | 
|  | builder.append(String.format("%4d: ", i)).append(instructionToString(insn)); | 
|  | } | 
|  | return builder.toString(); | 
|  | } | 
|  |  | 
|  | private String instructionToString(AbstractInsnNode insn) { | 
|  | if (printVisitor == null) { | 
|  | printVisitor = new TraceMethodVisitor(new Textifier()); | 
|  | } | 
|  | insn.accept(printVisitor); | 
|  | StringWriter writer = new StringWriter(); | 
|  | printVisitor.p.print(new PrintWriter(writer)); | 
|  | printVisitor.p.getText().clear(); | 
|  | return writer.toString(); | 
|  | } | 
|  |  | 
|  | public static boolean isCallToPolymorphicSignatureMethod(String owner, String name) { | 
|  | if (owner.equals("java/lang/invoke/MethodHandle")) { | 
|  | switch (name) { | 
|  | case "invoke": | 
|  | case "invokeExact": | 
|  | return true; | 
|  | default : | 
|  | return false; | 
|  | } | 
|  | } else if (owner.equals("java/lang/invoke/VarHandle")) { | 
|  | switch (name) { | 
|  | case "compareAndExchange": | 
|  | case "compareAndExchangeAcquire": | 
|  | case "compareAndExchangeRelease": | 
|  | case "compareAndSet": | 
|  | case "get": | 
|  | case "getAcquire": | 
|  | case "getAndAdd": | 
|  | case "getAndAddAcquire": | 
|  | case "getAndAddRelease": | 
|  | case "getAndBitwiseAnd": | 
|  | case "getAndBitwiseAndAcquire": | 
|  | case "getAndBitwiseAndRelease": | 
|  | case "getAndBitwiseOr": | 
|  | case "getAndBitwiseOrAcquire": | 
|  | case "getAndBitwiseOrRelease": | 
|  | case "getAndBitwiseXor": | 
|  | case "getAndBitwiseXorAcquire": | 
|  | case "getAndBitwiseXorRelease": | 
|  | case "getAndSet": | 
|  | case "getAndSetAcquire": | 
|  | case "getAndSetRelease": | 
|  | case "getOpaque": | 
|  | case "getVolatile": | 
|  | case "set": | 
|  | case "setOpaque": | 
|  | case "setRelease": | 
|  | case "setVolatile": | 
|  | case "weakCompareAndSet": | 
|  | case "weakCompareAndSetAcquire": | 
|  | case "weakCompareAndSetPlain": | 
|  | case "weakCompareAndSetRelease": | 
|  | return true; | 
|  | default : | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  | } |