/// <summary>Merges the given subroutine into this subroutine.</summary> /// <remarks> /// Merges the given subroutine into this subroutine. The local variables read or written by the /// given subroutine are marked as read or written by this one, and the callers of the given /// subroutine are added as callers of this one (if both have the same start). /// </remarks> /// <param name="subroutine"> /// another subroutine. This subroutine is left unchanged by this method. /// </param> /// <returns>whether this subroutine has been modified by this method.</returns> public bool Merge(Subroutine subroutine) { var changed = false; for (var i = 0; i < localsUsed.Length; ++i) { if (subroutine.localsUsed[i] && !localsUsed[i]) { localsUsed[i] = true; changed = true; } } if (subroutine.start == start) { for (var i = 0; i < subroutine.callers.Count; ++i) { var caller = subroutine.callers[i]; if (!callers.Contains(caller)) { callers.Add(caller); changed = true; } } } return(changed); }
/// <summary> /// Merges the given frame and subroutine into the frame and subroutines at the given instruction /// index (case of a RET instruction). /// </summary> /// <remarks> /// Merges the given frame and subroutine into the frame and subroutines at the given instruction /// index (case of a RET instruction). If the frame or the subroutine at the given instruction /// index changes as a result of this merge, the instruction index is added to the list of /// instructions to process (if it is not already the case). /// </remarks> /// <param name="insnIndex"> /// the index of an instruction immediately following a jsr instruction. /// </param> /// <param name="frameBeforeJsr"> /// the execution stack frame before the jsr instruction. This frame is /// merged into 'frameAfterRet'. /// </param> /// <param name="frameAfterRet"> /// the execution stack frame after a ret instruction of the subroutine. This /// frame is merged into the frame at 'insnIndex' (after it has itself been merge with /// 'frameBeforeJsr'). /// </param> /// <param name="subroutineBeforeJsr"> /// if the jsr is itself part of a subroutine (case of nested /// subroutine), the subroutine it belongs to. /// </param> /// <param name="localsUsed">the local variables read or written in the subroutine.</param> /// <exception cref="AnalyzerException">if the frames have incompatible sizes.</exception> /// <exception cref="AnalyzerException" /> private void Merge(int insnIndex, Frame <V> frameBeforeJsr, Frame <V> frameAfterRet , Subroutine subroutineBeforeJsr, bool[] localsUsed) { frameAfterRet.Merge(frameBeforeJsr, localsUsed); bool changed; var oldFrame = frames[insnIndex]; if (oldFrame == null) { frames[insnIndex] = NewFrame(frameAfterRet); changed = true; } else { changed = oldFrame.Merge(frameAfterRet, interpreter); } var oldSubroutine = subroutines[insnIndex]; if (oldSubroutine != null && subroutineBeforeJsr != null) { changed |= oldSubroutine.Merge(subroutineBeforeJsr); } if (changed && !inInstructionsToProcess[insnIndex]) { inInstructionsToProcess[insnIndex] = true; instructionsToProcess[numInstructionsToProcess++] = insnIndex; } }
// ----------------------------------------------------------------------------------------------- /// <summary> /// Merges the given frame and subroutine into the frame and subroutines at the given instruction /// index. /// </summary> /// <remarks> /// Merges the given frame and subroutine into the frame and subroutines at the given instruction /// index. If the frame or the subroutine at the given instruction index changes as a result of /// this merge, the instruction index is added to the list of instructions to process (if it is not /// already the case). /// </remarks> /// <param name="insnIndex">an instruction index.</param> /// <param name="frame">a frame. This frame is left unchanged by this method.</param> /// <param name="subroutine"> /// a subroutine. This subroutine is left unchanged by this method. /// </param> /// <exception cref="AnalyzerException">if the frames have incompatible sizes.</exception> /// <exception cref="AnalyzerException" /> private void Merge(int insnIndex, Frame <V> frame, Subroutine subroutine) { bool changed; var oldFrame = frames[insnIndex]; if (oldFrame == null) { frames[insnIndex] = NewFrame(frame); changed = true; } else { changed = oldFrame.Merge(frame, interpreter); } var oldSubroutine = subroutines[insnIndex]; if (oldSubroutine == null) { if (subroutine != null) { subroutines[insnIndex] = new Subroutine(subroutine); changed = true; } } else if (subroutine != null) { changed |= oldSubroutine.Merge(subroutine); } if (changed && !inInstructionsToProcess[insnIndex]) { inInstructionsToProcess[insnIndex] = true; instructionsToProcess[numInstructionsToProcess++] = insnIndex; } }
/// <summary> /// Constructs a copy of the given /// <see cref="Subroutine" /> /// . /// </summary> /// <param name="subroutine">the subroutine to copy.</param> internal Subroutine(Subroutine subroutine) { start = subroutine.start; localsUsed = (bool[])subroutine.localsUsed.Clone(); callers = new List <JumpInsnNode>(subroutine.callers); }
/// <summary> /// Follows the control flow graph of the currently analyzed method, starting at the given /// instruction index, and stores a copy of the given subroutine in /// <see cref="Analyzer{V}.subroutines" /> /// for each /// encountered instruction. Jumps to nested subroutines are <i>not</i> followed: instead, the /// corresponding instructions are put in the given list. /// </summary> /// <param name="insnIndex">an instruction index.</param> /// <param name="subroutine">a subroutine.</param> /// <param name="jsrInsns"> /// where the jsr instructions for nested subroutines must be put. /// </param> /// <exception cref="AnalyzerException"> /// if the control flow graph can fall off the end of the code. /// </exception> /// <exception cref="AnalyzerException" /> private void FindSubroutine(int insnIndex, Subroutine subroutine, IList <AbstractInsnNode > jsrInsns) { var instructionIndicesToProcess = new List <int>(); instructionIndicesToProcess.Add(insnIndex); while (!(instructionIndicesToProcess.Count == 0)) { var currentInsnIndex = instructionIndicesToProcess.RemoveAtReturningValue(instructionIndicesToProcess .Count - 1); if (currentInsnIndex < 0 || currentInsnIndex >= insnListSize) { throw new AnalyzerException(null, "Execution can fall off the end of the code"); } if (subroutines[currentInsnIndex] != null) { continue; } subroutines[currentInsnIndex] = new Subroutine(subroutine); var currentInsn = insnList.Get(currentInsnIndex); // Push the normal successors of currentInsn onto instructionIndicesToProcess. if (currentInsn is JumpInsnNode) { if (currentInsn.GetOpcode() == OpcodesConstants.Jsr) { // Do not follow a jsr, it leads to another subroutine! jsrInsns.Add(currentInsn); } else { var jumpInsn = (JumpInsnNode)currentInsn; instructionIndicesToProcess.Add(insnList.IndexOf(jumpInsn.label)); } } else if (currentInsn is TableSwitchInsnNode) { var tableSwitchInsn = (TableSwitchInsnNode)currentInsn; FindSubroutine(insnList.IndexOf(tableSwitchInsn.dflt), subroutine, jsrInsns); for (var i = tableSwitchInsn.labels.Count - 1; i >= 0; --i) { var labelNode = tableSwitchInsn.labels[i]; instructionIndicesToProcess.Add(insnList.IndexOf(labelNode)); } } else if (currentInsn is LookupSwitchInsnNode) { var lookupSwitchInsn = (LookupSwitchInsnNode)currentInsn; FindSubroutine(insnList.IndexOf(lookupSwitchInsn.dflt), subroutine, jsrInsns); for (var i = lookupSwitchInsn.labels.Count - 1; i >= 0; --i) { var labelNode = lookupSwitchInsn.labels[i]; instructionIndicesToProcess.Add(insnList.IndexOf(labelNode)); } } // Push the exception handler successors of currentInsn onto instructionIndicesToProcess. var insnHandlers = handlers[currentInsnIndex]; if (insnHandlers != null) { foreach (var tryCatchBlock in insnHandlers) { instructionIndicesToProcess.Add(insnList.IndexOf(tryCatchBlock.handler)); } } switch (currentInsn.GetOpcode()) { case OpcodesConstants.Goto: case OpcodesConstants.Ret: case OpcodesConstants.Tableswitch: case OpcodesConstants.Lookupswitch: case OpcodesConstants.Ireturn: case OpcodesConstants.Lreturn: case OpcodesConstants.Freturn: case OpcodesConstants.Dreturn: case OpcodesConstants.Areturn: case OpcodesConstants.Return: case OpcodesConstants.Athrow: { // Push the next instruction, if the control flow can go from currentInsn to the next. break; } default: { instructionIndicesToProcess.Add(currentInsnIndex + 1); break; } } } }
/// <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); }