// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team // // Permission is hereby granted, free of charge, to any person obtaining a copy of this // software and associated documentation files (the "Software"), to deal in the Software // without restriction, including without limitation the rights to use, copy, modify, merge, // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // to whom the Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all copies or // substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Mono.Cecil.Cil; namespace ICSharpCode.Decompiler.FlowAnalysis { /// /// Constructs the Control Flow Graph from a Cecil method body. /// public sealed class ControlFlowGraphBuilder { public static ControlFlowGraph Build(MethodBody methodBody) { return new ControlFlowGraphBuilder(methodBody).Build(); } // This option controls how finally blocks are handled: // false means that the endfinally instruction will jump to any of the leave targets (EndFinally edge type). // true means that a copy of the whole finally block is created for each leave target. In this case, each endfinally node will be connected with the leave // target using a normal edge. bool copyFinallyBlocks = false; MethodBody methodBody; int[] offsets; // array index = instruction index; value = IL offset bool[] hasIncomingJumps; // array index = instruction index List nodes = new List(); ControlFlowNode entryPoint; ControlFlowNode regularExit; ControlFlowNode exceptionalExit; ControlFlowGraphBuilder(MethodBody methodBody) { this.methodBody = methodBody; offsets = methodBody.Instructions.Select(i => i.Offset).ToArray(); hasIncomingJumps = new bool[methodBody.Instructions.Count]; entryPoint = new ControlFlowNode(0, 0, ControlFlowNodeType.EntryPoint); nodes.Add(entryPoint); regularExit = new ControlFlowNode(1, -1, ControlFlowNodeType.RegularExit); nodes.Add(regularExit); exceptionalExit = new ControlFlowNode(2, -1, ControlFlowNodeType.ExceptionalExit); nodes.Add(exceptionalExit); Debug.Assert(nodes.Count == 3); } /// /// Determines the index of the instruction (for use with the hasIncomingJumps array) /// int GetInstructionIndex(Instruction inst) { int index = Array.BinarySearch(offsets, inst.Offset); Debug.Assert(index >= 0); return index; } /// /// Builds the ControlFlowGraph. /// public ControlFlowGraph Build() { CalculateHasIncomingJumps(); CreateNodes(); CreateRegularControlFlow(); CreateExceptionalControlFlow(); if (copyFinallyBlocks) CopyFinallyBlocksIntoLeaveEdges(); else TransformLeaveEdges(); return new ControlFlowGraph(nodes.ToArray()); } #region Step 1: calculate which instructions are the targets of jump instructions. void CalculateHasIncomingJumps() { foreach (Instruction inst in methodBody.Instructions) { if (inst.OpCode.OperandType == OperandType.InlineBrTarget || inst.OpCode.OperandType == OperandType.ShortInlineBrTarget) { hasIncomingJumps[GetInstructionIndex((Instruction)inst.Operand)] = true; } else if (inst.OpCode.OperandType == OperandType.InlineSwitch) { foreach (Instruction i in (Instruction[])inst.Operand) hasIncomingJumps[GetInstructionIndex(i)] = true; } } foreach (ExceptionHandler eh in methodBody.ExceptionHandlers) { if (eh.FilterStart != null) { hasIncomingJumps[GetInstructionIndex(eh.FilterStart)] = true; } hasIncomingJumps[GetInstructionIndex(eh.HandlerStart)] = true; } } #endregion #region Step 2: create nodes void CreateNodes() { // Step 2a: find basic blocks and create nodes for them for (int i = 0; i < methodBody.Instructions.Count; i++) { Instruction blockStart = methodBody.Instructions[i]; ExceptionHandler blockStartEH = FindInnermostExceptionHandler(blockStart.Offset); // try and see how big we can make that block: for (; i + 1 < methodBody.Instructions.Count; i++) { Instruction inst = methodBody.Instructions[i]; if (IsBranch(inst.OpCode) || CanThrowException(inst.OpCode)) break; if (hasIncomingJumps[i + 1]) break; if (inst.Next != null) { // ensure that blocks never contain instructions from different try blocks ExceptionHandler instEH = FindInnermostExceptionHandler(inst.Next.Offset); if (instEH != blockStartEH) break; } } nodes.Add(new ControlFlowNode(nodes.Count, blockStart, methodBody.Instructions[i])); } // Step 2b: Create special nodes for the exception handling constructs foreach (ExceptionHandler handler in methodBody.ExceptionHandlers) { if (handler.HandlerType == ExceptionHandlerType.Filter) throw new NotSupportedException(); ControlFlowNode endFinallyOrFaultNode = null; if (handler.HandlerType == ExceptionHandlerType.Finally || handler.HandlerType == ExceptionHandlerType.Fault) { endFinallyOrFaultNode = new ControlFlowNode(nodes.Count, handler.HandlerEnd.Offset, ControlFlowNodeType.EndFinallyOrFault); nodes.Add(endFinallyOrFaultNode); } nodes.Add(new ControlFlowNode(nodes.Count, handler, endFinallyOrFaultNode)); } } #endregion #region Step 3: create edges for the normal flow of control (assuming no exceptions thrown) void CreateRegularControlFlow() { CreateEdge(entryPoint, methodBody.Instructions[0], JumpType.Normal); foreach (ControlFlowNode node in nodes) { if (node.End != null) { // create normal edges from one instruction to the next if (!OpCodeInfo.IsUnconditionalBranch(node.End.OpCode)) CreateEdge(node, node.End.Next, JumpType.Normal); // create edges for branch instructions if (node.End.OpCode.OperandType == OperandType.InlineBrTarget || node.End.OpCode.OperandType == OperandType.ShortInlineBrTarget) { if (node.End.OpCode == OpCodes.Leave || node.End.OpCode == OpCodes.Leave_S) { var handlerBlock = FindInnermostHandlerBlock(node.End.Offset); if (handlerBlock.NodeType == ControlFlowNodeType.FinallyOrFaultHandler) CreateEdge(node, (Instruction)node.End.Operand, JumpType.LeaveTry); else CreateEdge(node, (Instruction)node.End.Operand, JumpType.Normal); } else { CreateEdge(node, (Instruction)node.End.Operand, JumpType.Normal); } } else if (node.End.OpCode.OperandType == OperandType.InlineSwitch) { foreach (Instruction i in (Instruction[])node.End.Operand) CreateEdge(node, i, JumpType.Normal); } // create edges for return instructions if (node.End.OpCode.FlowControl == FlowControl.Return) { switch (node.End.OpCode.Code) { case Code.Ret: CreateEdge(node, regularExit, JumpType.Normal); break; case Code.Endfinally: ControlFlowNode handlerBlock = FindInnermostHandlerBlock(node.End.Offset); if (handlerBlock.EndFinallyOrFaultNode == null) throw new InvalidProgramException("Found endfinally in block " + handlerBlock); CreateEdge(node, handlerBlock.EndFinallyOrFaultNode, JumpType.Normal); break; default: throw new NotSupportedException(node.End.OpCode.ToString()); } } } } } #endregion #region Step 4: create edges for the exceptional control flow (from instructions that might throw, to the innermost containing exception handler) void CreateExceptionalControlFlow() { foreach (ControlFlowNode node in nodes) { if (node.End != null && CanThrowException(node.End.OpCode)) { CreateEdge(node, FindInnermostExceptionHandlerNode(node.End.Offset), JumpType.JumpToExceptionHandler); } if (node.ExceptionHandler != null) { if (node.EndFinallyOrFaultNode != null) { // For Fault and Finally blocks, create edge from "EndFinally" to next exception handler. // This represents the exception bubbling up after finally block was executed. CreateEdge(node.EndFinallyOrFaultNode, FindParentExceptionHandlerNode(node), JumpType.JumpToExceptionHandler); } else { // For Catch blocks, create edge from "CatchHandler" block (at beginning) to next exception handler. // This represents the exception bubbling up because it did not match the type of the catch block. CreateEdge(node, FindParentExceptionHandlerNode(node), JumpType.JumpToExceptionHandler); } CreateEdge(node, node.ExceptionHandler.HandlerStart, JumpType.Normal); } } } ExceptionHandler FindInnermostExceptionHandler(int instructionOffsetInTryBlock) { foreach (ExceptionHandler h in methodBody.ExceptionHandlers) { if (h.TryStart.Offset <= instructionOffsetInTryBlock && instructionOffsetInTryBlock < h.TryEnd.Offset) { return h; } } return null; } ControlFlowNode FindInnermostExceptionHandlerNode(int instructionOffsetInTryBlock) { ExceptionHandler h = FindInnermostExceptionHandler(instructionOffsetInTryBlock); if (h != null) return nodes.Single(n => n.ExceptionHandler == h && n.CopyFrom == null); else return exceptionalExit; } ControlFlowNode FindInnermostHandlerBlock(int instructionOffset) { foreach (ExceptionHandler h in methodBody.ExceptionHandlers) { if (h.TryStart.Offset <= instructionOffset && instructionOffset < h.TryEnd.Offset || h.HandlerStart.Offset <= instructionOffset && instructionOffset < h.HandlerEnd.Offset) { return nodes.Single(n => n.ExceptionHandler == h && n.CopyFrom == null); } } return exceptionalExit; } ControlFlowNode FindParentExceptionHandlerNode(ControlFlowNode exceptionHandler) { Debug.Assert(exceptionHandler.NodeType == ControlFlowNodeType.CatchHandler || exceptionHandler.NodeType == ControlFlowNodeType.FinallyOrFaultHandler); int offset = exceptionHandler.ExceptionHandler.TryStart.Offset; for (int i = exceptionHandler.BlockIndex + 1; i < nodes.Count; i++) { ExceptionHandler h = nodes[i].ExceptionHandler; if (h != null && h.TryStart.Offset <= offset && offset < h.TryEnd.Offset) return nodes[i]; } return exceptionalExit; } #endregion #region Step 5a: replace LeaveTry edges with EndFinally edges // this is used only for copyFinallyBlocks==false; see Step 5b otherwise void TransformLeaveEdges() { for (int i = nodes.Count - 1; i >= 0; i--) { ControlFlowNode node = nodes[i]; if (node.End != null && node.Outgoing.Count == 1 && node.Outgoing[0].Type == JumpType.LeaveTry) { Debug.Assert(node.End.OpCode == OpCodes.Leave || node.End.OpCode == OpCodes.Leave_S); ControlFlowNode target = node.Outgoing[0].Target; // remove the edge target.Incoming.Remove(node.Outgoing[0]); node.Outgoing.Clear(); ControlFlowNode handler = FindInnermostExceptionHandlerNode(node.End.Offset); Debug.Assert(handler.NodeType == ControlFlowNodeType.FinallyOrFaultHandler); CreateEdge(node, handler, JumpType.Normal); CreateEdge(handler.EndFinallyOrFaultNode, target, JumpType.EndFinally); } } } #endregion #region Step 5b: copy finally blocks into the LeaveTry edges void CopyFinallyBlocksIntoLeaveEdges() { // We need to process try-finally blocks inside-out. // We'll do that by going through all instructions in reverse order for (int i = nodes.Count - 1; i >= 0; i--) { ControlFlowNode node = nodes[i]; if (node.End != null && node.Outgoing.Count == 1 && node.Outgoing[0].Type == JumpType.LeaveTry) { Debug.Assert(node.End.OpCode == OpCodes.Leave || node.End.OpCode == OpCodes.Leave_S); ControlFlowNode target = node.Outgoing[0].Target; // remove the edge target.Incoming.Remove(node.Outgoing[0]); node.Outgoing.Clear(); ControlFlowNode handler = FindInnermostExceptionHandlerNode(node.End.Offset); Debug.Assert(handler.NodeType == ControlFlowNodeType.FinallyOrFaultHandler); ControlFlowNode copy = CopyFinallySubGraph(handler, handler.EndFinallyOrFaultNode, target); CreateEdge(node, copy, JumpType.Normal); } } } /// /// Creates a copy of all nodes pointing to 'end' and replaces those references with references to 'newEnd'. /// Nodes pointing to the copied node are copied recursively to update those references, too. /// This recursion stops at 'start'. The modified version of start is returned. /// ControlFlowNode CopyFinallySubGraph(ControlFlowNode start, ControlFlowNode end, ControlFlowNode newEnd) { return new CopyFinallySubGraphLogic(this, start, end, newEnd).CopyFinallySubGraph(); } class CopyFinallySubGraphLogic { readonly ControlFlowGraphBuilder builder; readonly Dictionary oldToNew = new Dictionary(); readonly ControlFlowNode start; readonly ControlFlowNode end; readonly ControlFlowNode newEnd; public CopyFinallySubGraphLogic(ControlFlowGraphBuilder builder, ControlFlowNode start, ControlFlowNode end, ControlFlowNode newEnd) { this.builder = builder; this.start = start; this.end = end; this.newEnd = newEnd; } internal ControlFlowNode CopyFinallySubGraph() { foreach (ControlFlowNode n in end.Predecessors) { CollectNodes(n); } foreach (var pair in oldToNew) ReconstructEdges(pair.Key, pair.Value); return GetNew(start); } void CollectNodes(ControlFlowNode node) { if (node == end || node == newEnd) throw new InvalidOperationException("unexpected cycle involving finally construct"); if (!oldToNew.ContainsKey(node)) { int newBlockIndex = builder.nodes.Count; ControlFlowNode copy; switch (node.NodeType) { case ControlFlowNodeType.Normal: copy = new ControlFlowNode(newBlockIndex, node.Start, node.End); break; case ControlFlowNodeType.FinallyOrFaultHandler: copy = new ControlFlowNode(newBlockIndex, node.ExceptionHandler, node.EndFinallyOrFaultNode); break; default: // other nodes shouldn't occur when copying finally blocks throw new NotSupportedException(node.NodeType.ToString()); } copy.CopyFrom = node; builder.nodes.Add(copy); oldToNew.Add(node, copy); if (node != start) { foreach (ControlFlowNode n in node.Predecessors) { CollectNodes(n); } } } } void ReconstructEdges(ControlFlowNode oldNode, ControlFlowNode newNode) { foreach (ControlFlowEdge oldEdge in oldNode.Outgoing) { builder.CreateEdge(newNode, GetNew(oldEdge.Target), oldEdge.Type); } } ControlFlowNode GetNew(ControlFlowNode oldNode) { if (oldNode == end) return newEnd; ControlFlowNode newNode; if (oldToNew.TryGetValue(oldNode, out newNode)) return newNode; return oldNode; } } #endregion #region CreateEdge methods void CreateEdge(ControlFlowNode fromNode, Instruction toInstruction, JumpType type) { CreateEdge(fromNode, nodes.Single(n => n.Start == toInstruction), type); } void CreateEdge(ControlFlowNode fromNode, ControlFlowNode toNode, JumpType type) { ControlFlowEdge edge = new ControlFlowEdge(fromNode, toNode, type); fromNode.Outgoing.Add(edge); toNode.Incoming.Add(edge); } #endregion #region OpCode info static bool CanThrowException(OpCode opcode) { if (opcode.OpCodeType == OpCodeType.Prefix) return false; return OpCodeInfo.Get(opcode).CanThrow; } static bool IsBranch(OpCode opcode) { if (opcode.OpCodeType == OpCodeType.Prefix) return false; switch (opcode.FlowControl) { case FlowControl.Cond_Branch: case FlowControl.Branch: case FlowControl.Throw: case FlowControl.Return: return true; case FlowControl.Next: case FlowControl.Call: return false; default: throw new NotSupportedException(opcode.FlowControl.ToString()); } } #endregion } }