/// <summary> /// Creates the new instructions, inlining each instantiation of each subroutine until the code is /// fully elaborated. /// </summary> private void EmitCode() { var worklist = new LinkedList <Instantiation >(); // Create an instantiation of the main "subroutine", which is just the main routine. worklist.AddLast(new Instantiation(this, null, mainSubroutineInsns) ); // Emit instantiations of each subroutine we encounter, including the main subroutine. var newInstructions = new InsnList(); var newTryCatchBlocks = new List <TryCatchBlockNode>(); var newLocalVariables = new List <LocalVariableNode>(); while (!(worklist.Count == 0)) { var instantiation = Collections.RemoveFirst(worklist ); EmitInstantiation(instantiation, worklist, newInstructions, newTryCatchBlocks, newLocalVariables ); } instructions = newInstructions; tryCatchBlocks = newTryCatchBlocks; localVariables = newLocalVariables; }
/// <summary>Analyzes the given method.</summary> /// <param name="owner">the internal name of the class to which 'method' belongs.</param> /// <param name="method">the method to be analyzed.</param> /// <returns> /// the symbolic state of the execution stack frame at each bytecode instruction of the /// method. The size of the returned array is equal to the number of instructions (and labels) /// of the method. A given frame is /// <literal>null</literal> /// if and only if the corresponding /// instruction cannot be reached (dead code). /// </returns> /// <exception cref="AnalyzerException">if a problem occurs during the analysis.</exception> /// <exception cref="AnalyzerException" /> public virtual Frame <V>[] Analyze(string owner, MethodNode method) { if ((method.access & (AccessFlags.Abstract | AccessFlags.Native )) != 0) { frames = new Frame <V> [0]; return(frames); } insnList = method.instructions; insnListSize = insnList.Size(); handlers = (IList <TryCatchBlockNode>[]) new IList <object> [insnListSize]; frames = new Frame <V> [insnListSize]; subroutines = new Subroutine[insnListSize]; inInstructionsToProcess = new bool[insnListSize]; instructionsToProcess = new int[insnListSize]; numInstructionsToProcess = 0; // For each exception handler, and each instruction within its range, record in 'handlers' the // fact that execution can flow from this instruction to the exception handler. for (var i = 0; i < method.tryCatchBlocks.Count; ++i) { var tryCatchBlock = method.tryCatchBlocks[i]; var startIndex = insnList.IndexOf(tryCatchBlock.start); var endIndex = insnList.IndexOf(tryCatchBlock.end); for (var j = startIndex; j < endIndex; ++j) { var insnHandlers = handlers[j]; if (insnHandlers == null) { insnHandlers = new List <TryCatchBlockNode>(); handlers[j] = insnHandlers; } insnHandlers.Add(tryCatchBlock); } } // For each instruction, compute the subroutine to which it belongs. // Follow the main 'subroutine', and collect the jsr instructions to nested subroutines. var main = new Subroutine(null, method.maxLocals, null); IList <AbstractInsnNode> jsrInsns = new List <AbstractInsnNode>(); FindSubroutine(0, main, jsrInsns); // Follow the nested subroutines, and collect their own nested subroutines, until all // subroutines are found. IDictionary <LabelNode, Subroutine> jsrSubroutines = new Dictionary <LabelNode, Subroutine >(); while (!(jsrInsns.Count == 0)) { var jsrInsn = (JumpInsnNode)jsrInsns.RemoveAtReturningValue(0); var subroutine = jsrSubroutines.GetOrNull(jsrInsn.label); if (subroutine == null) { subroutine = new Subroutine(jsrInsn.label, method.maxLocals, jsrInsn); Collections.Put(jsrSubroutines, jsrInsn.label, subroutine); FindSubroutine(insnList.IndexOf(jsrInsn.label), subroutine, jsrInsns); } else { subroutine.callers.Add(jsrInsn); } } // Clear the main 'subroutine', which is not a real subroutine (and was used only as an // intermediate step above to find the real ones). for (var i = 0; i < insnListSize; ++i) { if (subroutines[i] != null && subroutines[i].start == null) { subroutines[i] = null; } } // Initializes the data structures for the control flow analysis. var currentFrame = ComputeInitialFrame(owner, method); Merge(0, currentFrame, null); Init(owner, method); // Control flow analysis. while (numInstructionsToProcess > 0) { // Get and remove one instruction from the list of instructions to process. var insnIndex = instructionsToProcess[--numInstructionsToProcess]; var oldFrame = frames[insnIndex]; var subroutine = subroutines[insnIndex]; inInstructionsToProcess[insnIndex] = false; // Simulate the execution of this instruction. AbstractInsnNode insnNode = null; try { insnNode = method.instructions.Get(insnIndex); var insnOpcode = insnNode.GetOpcode(); var insnType = insnNode.GetType(); if (insnType == AbstractInsnNode.Label || insnType == AbstractInsnNode.Line || insnType == AbstractInsnNode.Frame) { Merge(insnIndex + 1, oldFrame, subroutine); NewControlFlowEdge(insnIndex, insnIndex + 1); } else { currentFrame.Init(oldFrame).Execute(insnNode, interpreter); subroutine = subroutine == null ? null : new Subroutine(subroutine); if (insnNode is JumpInsnNode) { var jumpInsn = (JumpInsnNode)insnNode; if (insnOpcode != OpcodesConstants.Goto && insnOpcode != OpcodesConstants.Jsr) { currentFrame.InitJumpTarget(insnOpcode, null); /* target = */ Merge(insnIndex + 1, currentFrame, subroutine); NewControlFlowEdge(insnIndex, insnIndex + 1); } var jumpInsnIndex = insnList.IndexOf(jumpInsn.label); currentFrame.InitJumpTarget(insnOpcode, jumpInsn.label); if (insnOpcode == OpcodesConstants.Jsr) { Merge(jumpInsnIndex, currentFrame, new Subroutine(jumpInsn.label, method.maxLocals , jumpInsn)); } else { Merge(jumpInsnIndex, currentFrame, subroutine); } NewControlFlowEdge(insnIndex, jumpInsnIndex); } else if (insnNode is LookupSwitchInsnNode) { var lookupSwitchInsn = (LookupSwitchInsnNode)insnNode; var targetInsnIndex = insnList.IndexOf(lookupSwitchInsn.dflt); currentFrame.InitJumpTarget(insnOpcode, lookupSwitchInsn.dflt); Merge(targetInsnIndex, currentFrame, subroutine); NewControlFlowEdge(insnIndex, targetInsnIndex); for (var i = 0; i < lookupSwitchInsn.labels.Count; ++i) { var label = lookupSwitchInsn.labels[i]; targetInsnIndex = insnList.IndexOf(label); currentFrame.InitJumpTarget(insnOpcode, label); Merge(targetInsnIndex, currentFrame, subroutine); NewControlFlowEdge(insnIndex, targetInsnIndex); } } else if (insnNode is TableSwitchInsnNode) { var tableSwitchInsn = (TableSwitchInsnNode)insnNode; var targetInsnIndex = insnList.IndexOf(tableSwitchInsn.dflt); currentFrame.InitJumpTarget(insnOpcode, tableSwitchInsn.dflt); Merge(targetInsnIndex, currentFrame, subroutine); NewControlFlowEdge(insnIndex, targetInsnIndex); for (var i = 0; i < tableSwitchInsn.labels.Count; ++i) { var label = tableSwitchInsn.labels[i]; currentFrame.InitJumpTarget(insnOpcode, label); targetInsnIndex = insnList.IndexOf(label); Merge(targetInsnIndex, currentFrame, subroutine); NewControlFlowEdge(insnIndex, targetInsnIndex); } } else if (insnOpcode == OpcodesConstants.Ret) { if (subroutine == null) { throw new AnalyzerException(insnNode, "RET instruction outside of a subroutine"); } for (var i = 0; i < subroutine.callers.Count; ++i) { var caller = subroutine.callers[i]; var jsrInsnIndex = insnList.IndexOf(caller); if (frames[jsrInsnIndex] != null) { Merge(jsrInsnIndex + 1, frames[jsrInsnIndex], currentFrame, subroutines[jsrInsnIndex ], subroutine.localsUsed); NewControlFlowEdge(insnIndex, jsrInsnIndex + 1); } } } else if (insnOpcode != OpcodesConstants.Athrow && (insnOpcode < OpcodesConstants.Ireturn || insnOpcode > OpcodesConstants.Return)) { if (subroutine != null) { if (insnNode is VarInsnNode) { var var = ((VarInsnNode)insnNode).var; subroutine.localsUsed[var] = true; if (insnOpcode == OpcodesConstants.Lload || insnOpcode == OpcodesConstants.Dload || insnOpcode == OpcodesConstants.Lstore || insnOpcode == OpcodesConstants.Dstore) { subroutine.localsUsed[var + 1] = true; } } else if (insnNode is IincInsnNode) { var var = ((IincInsnNode)insnNode).var; subroutine.localsUsed[var] = true; } } Merge(insnIndex + 1, currentFrame, subroutine); NewControlFlowEdge(insnIndex, insnIndex + 1); } } var insnHandlers = handlers[insnIndex]; if (insnHandlers != null) { foreach (var tryCatchBlock in insnHandlers) { Type catchType; if (tryCatchBlock.type == null) { catchType = Type.GetObjectType("java/lang/Throwable"); } else { catchType = Type.GetObjectType(tryCatchBlock.type); } if (NewControlFlowExceptionEdge(insnIndex, tryCatchBlock)) { var handler = NewFrame(oldFrame); handler.ClearStack(); handler.Push(interpreter.NewExceptionValue(tryCatchBlock, handler, catchType)); Merge(insnList.IndexOf(tryCatchBlock.handler), handler, subroutine); } } } } catch (AnalyzerException e) { throw new AnalyzerException(e.node, "Error at instruction " + insnIndex + ": " + e.Message, e); } catch (Exception e) { // DontCheck(IllegalCatch): can't be fixed, for backward compatibility. throw new AnalyzerException(insnNode, "Error at instruction " + insnIndex + ": " + e.Message, e); } } return(frames); }
/// <summary> /// Emits an instantiation of a subroutine, specified by <code>instantiation</code>. /// </summary> /// <remarks> /// Emits an instantiation of a subroutine, specified by <code>instantiation</code>. May add new /// instantiations that are invoked by this one to the <code>worklist</code>, and new try/catch /// blocks to <code>newTryCatchBlocks</code>. /// </remarks> /// <param name="instantiation">the instantiation that must be performed.</param> /// <param name="worklist">list of the instantiations that remain to be done.</param> /// <param name="newInstructions"> /// the instruction list to which the instantiated code must be appended. /// </param> /// <param name="newTryCatchBlocks"> /// the exception handler list to which the instantiated handlers must be /// appended. /// </param> /// <param name="newLocalVariables"> /// the local variables list to which the instantiated local variables /// must be appended. /// </param> private void EmitInstantiation(Instantiation instantiation, LinkedList <Instantiation> worklist, InsnList newInstructions, IList <TryCatchBlockNode > newTryCatchBlocks, IList <LocalVariableNode> newLocalVariables) { LabelNode previousLabelNode = null; for (var i = 0; i < instructions.Size(); ++i) { var insnNode = instructions.Get(i); if (insnNode.GetType() == AbstractInsnNode.Label) { // Always clone all labels, while avoiding to add the same label more than once. var labelNode = (LabelNode)insnNode; var clonedLabelNode = instantiation.GetClonedLabel(labelNode); if (clonedLabelNode != previousLabelNode) { newInstructions.Add(clonedLabelNode); previousLabelNode = clonedLabelNode; } } else if (instantiation.FindOwner(i) == instantiation) { // Don't emit instructions that were already emitted by an ancestor subroutine. Note that it // is still possible for a given instruction to be emitted twice because it may belong to // two subroutines that do not invoke each other. if (insnNode.GetOpcode() == OpcodesConstants.Ret) { // Translate RET instruction(s) to a jump to the return label for the appropriate // instantiation. The problem is that the subroutine may "fall through" to the ret of a // parent subroutine; therefore, to find the appropriate ret label we find the oldest // instantiation that claims to own this instruction. LabelNode retLabel = null; for (var retLabelOwner = instantiation; retLabelOwner != null; retLabelOwner = retLabelOwner.parent) { if (retLabelOwner.subroutineInsns.Get(i)) { retLabel = retLabelOwner.returnLabel; } } if (retLabel == null) { // This is only possible if the mainSubroutine owns a RET instruction, which should // never happen for verifiable code. throw new ArgumentException("Instruction #" + i + " is a RET not owned by any subroutine" ); } newInstructions.Add(new JumpInsnNode(OpcodesConstants.Goto, retLabel)); } else if (insnNode.GetOpcode() == OpcodesConstants.Jsr) { var jsrLabelNode = ((JumpInsnNode)insnNode).label; var subroutineInsns = subroutinesInsns.GetOrNull(jsrLabelNode); var newInstantiation = new Instantiation (this, instantiation, subroutineInsns); var clonedJsrLabelNode = newInstantiation.GetClonedLabelForJumpInsn(jsrLabelNode ); // Replace the JSR instruction with a GOTO to the instantiated subroutine, and push NULL // for what was once the return address value. This hack allows us to avoid doing any sort // of data flow analysis to figure out which instructions manipulate the old return // address value pointer which is now known to be unneeded. newInstructions.Add(new InsnNode(OpcodesConstants.Aconst_Null)); newInstructions.Add(new JumpInsnNode(OpcodesConstants.Goto, clonedJsrLabelNode)); newInstructions.Add(newInstantiation.returnLabel); // Insert this new instantiation into the queue to be emitted later. worklist.AddLast(newInstantiation); } else { newInstructions.Add(insnNode.Clone(instantiation)); } } } // Emit the try/catch blocks that are relevant for this instantiation. foreach (var tryCatchBlockNode in tryCatchBlocks) { var start = instantiation.GetClonedLabel(tryCatchBlockNode.start); var end = instantiation.GetClonedLabel(tryCatchBlockNode.end); if (start != end) { var handler = instantiation.GetClonedLabelForJumpInsn(tryCatchBlockNode.handler ); if (start == null || end == null || handler == null) { throw new AssertionError("Internal error!"); } newTryCatchBlocks.Add(new TryCatchBlockNode(start, end, handler, tryCatchBlockNode .type)); } } // Emit the local variable nodes that are relevant for this instantiation. foreach (var localVariableNode in localVariables) { var start = instantiation.GetClonedLabel(localVariableNode.start); var end = instantiation.GetClonedLabel(localVariableNode.end); if (start != end) { newLocalVariables.Add(new LocalVariableNode(localVariableNode.name, localVariableNode .desc, localVariableNode.signature, start, end, localVariableNode.index)); } } }