diff --git a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowEdge.cs b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowEdge.cs index 2f2470342..ba7e94fe8 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowEdge.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowEdge.cs @@ -20,8 +20,14 @@ using System; namespace ICSharpCode.Decompiler.FlowAnalysis { + /// + /// + /// public enum JumpType { + /// + /// A regular control flow edge. + /// Normal, /// /// Jump to exception handler (an exception occurred) @@ -33,15 +39,21 @@ namespace ICSharpCode.Decompiler.FlowAnalysis /// LeaveTry, /// - /// Jump from one catch block to its sibling + /// Jump from one catch block to its sibling (e.g. in "try {} catch (A) {} catch (B) {}") /// MutualProtection, /// - /// non-determistic jump at end finally (to any of the potential leave targets) + /// Jump at endfinally (to any of the potential leave targets). + /// For any leave-instruction, control flow enters the finally block - the edge to the leave target (LeaveTry) is not a real control flow edge. + /// EndFinally edges are inserted at the end of the finally block, jumping to any of the targets of the leave instruction. + /// This edge type is only used when copying of finally blocks is disabled (with copying, a normal deterministic edge is used at each copy of the endfinally node). /// EndFinally } + /// + /// Represents an edge in the control flow graph, pointing from Source to Target. + /// public sealed class ControlFlowEdge { public readonly ControlFlowNode Source; diff --git a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraph.cs b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraph.cs index b8c0ef2ba..be7ba835b 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraph.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraph.cs @@ -31,6 +31,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis /// /// Contains the control flow graph. /// + /// Use ControlFlowGraph builder to create instances of the ControlFlowGraph. public sealed class ControlFlowGraph { readonly ReadOnlyCollection nodes; @@ -90,13 +91,19 @@ namespace ICSharpCode.Decompiler.FlowAnalysis } #endif - internal void ResetVisited() + /// + /// Resets "Visited" to false for all nodes in this graph. + /// + public void ResetVisited() { foreach (ControlFlowNode node in nodes) { node.Visited = false; } } + /// + /// Computes the dominator tree. + /// public void ComputeDominance(CancellationToken cancellationToken = default(CancellationToken)) { // A Simple, Fast Dominance Algorithm @@ -151,6 +158,10 @@ namespace ICSharpCode.Decompiler.FlowAnalysis throw new Exception("No common dominator found!"); } + /// + /// Computes dominance frontiers. + /// This method requires that the dominator tree is already computed! + /// public void ComputeDominanceFrontier() { ResetVisited(); diff --git a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraphBuilder.cs b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraphBuilder.cs index 9b9a04bbd..e33338bdb 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraphBuilder.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowGraphBuilder.cs @@ -25,6 +25,9 @@ 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) @@ -32,7 +35,12 @@ namespace ICSharpCode.Decompiler.FlowAnalysis 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 @@ -56,6 +64,9 @@ namespace ICSharpCode.Decompiler.FlowAnalysis 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); @@ -63,6 +74,9 @@ namespace ICSharpCode.Decompiler.FlowAnalysis return index; } + /// + /// Builds the ControlFlowGraph. + /// public ControlFlowGraph Build() { CalculateHasIncomingJumps(); @@ -76,6 +90,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis 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) { @@ -93,9 +108,12 @@ namespace ICSharpCode.Decompiler.FlowAnalysis 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); @@ -116,6 +134,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis 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(); @@ -127,7 +146,9 @@ namespace ICSharpCode.Decompiler.FlowAnalysis 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); @@ -172,7 +193,9 @@ namespace ICSharpCode.Decompiler.FlowAnalysis } } } + #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) { @@ -237,11 +260,12 @@ namespace ICSharpCode.Decompiler.FlowAnalysis } return exceptionalExit; } + #endregion - void CopyFinallyBlocksIntoLeaveEdges() + #region Step 5a: replace LeaveTry edges with EndFinally edges + // this is used only for copyFinallyBlocks==false; see Step 5b otherwise + void TransformLeaveEdges() { - // 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) { @@ -255,15 +279,18 @@ namespace ICSharpCode.Decompiler.FlowAnalysis ControlFlowNode handler = FindInnermostExceptionHandlerNode(node.End.Offset); Debug.Assert(handler.NodeType == ControlFlowNodeType.FinallyOrFaultHandler); - ControlFlowNode copy = CopyFinallySubGraph(handler, handler.EndFinallyOrFaultNode, target); - CreateEdge(node, copy, JumpType.Normal); + CreateEdge(node, handler, JumpType.Normal); + CreateEdge(handler.EndFinallyOrFaultNode, target, JumpType.EndFinally); } } } + #endregion - - void TransformLeaveEdges() + #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) { @@ -277,8 +304,8 @@ namespace ICSharpCode.Decompiler.FlowAnalysis ControlFlowNode handler = FindInnermostExceptionHandlerNode(node.End.Offset); Debug.Assert(handler.NodeType == ControlFlowNodeType.FinallyOrFaultHandler); - CreateEdge(node, handler, JumpType.Normal); - CreateEdge(handler.EndFinallyOrFaultNode, target, JumpType.EndFinally); + ControlFlowNode copy = CopyFinallySubGraph(handler, handler.EndFinallyOrFaultNode, target); + CreateEdge(node, copy, JumpType.Normal); } } } @@ -366,6 +393,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis return oldNode; } } + #endregion #region CreateEdge methods void CreateEdge(ControlFlowNode fromNode, Instruction toInstruction, JumpType type) diff --git a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowNode.cs b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowNode.cs index d2d110ced..2606ae23a 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowNode.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/ControlFlowNode.cs @@ -27,27 +27,72 @@ using Mono.Cecil.Cil; namespace ICSharpCode.Decompiler.FlowAnalysis { + /// + /// Type of the control flow node + /// public enum ControlFlowNodeType { + /// + /// A normal node represents a basic block. + /// Normal, + /// + /// The entry point of the method. + /// EntryPoint, + /// + /// The exit point of the method (every ret instruction branches to this node) + /// RegularExit, + /// + /// This node represents leaving a method irregularly by throwing an exception. + /// ExceptionalExit, + /// + /// This node is used as a header for exception handler blocks. + /// CatchHandler, + /// + /// This node is used as a header for finally blocks and fault blocks. + /// Every leave instruction in the try block leads to the handler of the containing finally block; + /// and exceptional control flow also leads to this handler. + /// FinallyOrFaultHandler, + /// + /// This node is used as footer for finally blocks and fault blocks. + /// Depending on the "copyFinallyBlocks" option used when creating the graph, it is connected with all leave targets using + /// EndFinally edges (when not copying); or with a specific leave target using a normal edge (when copying). + /// For fault blocks, an exception edge is used to represent the "re-throwing" of the exception. + /// EndFinallyOrFault } + /// + /// Represents a block in the control flow graph. + /// public sealed class ControlFlowNode { + /// + /// Index of this node in the ControlFlowGraph.Nodes collection. + /// public readonly int BlockIndex; + + /// + /// Type of the node. + /// public readonly ControlFlowNodeType NodeType; + + /// + /// If this node is a FinallyOrFaultHandler node, this field points to the corresponding EndFinallyOrFault node. + /// Otherwise, this field is null. + /// public readonly ControlFlowNode EndFinallyOrFaultNode; /// - /// Visited flag that's used in various algorithms. + /// Visited flag, used in various algorithms. + /// Before using it in your algorithm, reset it to false by calling ControlFlowGraph.ResetVisited(); /// - internal bool Visited; + public bool Visited; /// /// Signalizes that this node is a copy of another node. @@ -55,21 +100,33 @@ namespace ICSharpCode.Decompiler.FlowAnalysis public ControlFlowNode CopyFrom { get; internal set; } /// - /// Gets the immediate dominator. + /// Gets the immediate dominator (the parent in the dominator tree). + /// Null if dominance has not been calculated; or if the node is unreachable. /// public ControlFlowNode ImmediateDominator { get; internal set; } + /// + /// List of children in the dominator tree. + /// public readonly List DominatorTreeChildren = new List(); + /// + /// The dominance frontier of this node. + /// This is the set of nodes for which this node dominates a predecessor, but which are not strictly dominated by this node. + /// + /// + /// b.DominanceFrontier = { y in CFG; (exists p in predecessors(y): b dominates p) and not (b strictly dominates y)} + /// public HashSet DominanceFrontier; /// - /// Start of code block represented by this node. Only set for nodetype == Normal. + /// Start of code block represented by this node. Only set for nodetype == Normal. /// public readonly Instruction Start; /// /// End of the code block represented by this node. Only set for nodetype == Normal. + /// The end is exclusive, the end instruction itself does not belong to this block. /// public readonly Instruction End; @@ -79,7 +136,14 @@ namespace ICSharpCode.Decompiler.FlowAnalysis /// public readonly ExceptionHandler ExceptionHandler; + /// + /// List of incoming control flow edges. + /// public readonly List Incoming = new List(); + + /// + /// List of outgoing control flow edges. + /// public readonly List Outgoing = new List(); internal ControlFlowNode(int blockIndex, ControlFlowNodeType nodeType) @@ -109,18 +173,28 @@ namespace ICSharpCode.Decompiler.FlowAnalysis Debug.Assert((exceptionHandler.HandlerType == ExceptionHandlerType.Finally || exceptionHandler.HandlerType == ExceptionHandlerType.Fault) == (endFinallyOrFaultNode != null)); } + /// + /// Gets all predecessors (=sources of incoming edges) + /// public IEnumerable Predecessors { get { return Incoming.Select(e => e.Source); } } + /// + /// Gets all successors (=targets of outgoing edges) + /// public IEnumerable Successors { get { return Outgoing.Select(e => e.Target); } } + /// + /// Gets all instructions in this node. + /// Returns an empty list for special nodes that don't have any instructions. + /// public IEnumerable Instructions { get { Instruction inst = Start; @@ -186,8 +260,12 @@ namespace ICSharpCode.Decompiler.FlowAnalysis return writer.ToString(); } + /// + /// Gets whether this dominates . + /// public bool Dominates(ControlFlowNode node) { + // TODO: this can be made O(1) by numbering the dominator tree ControlFlowNode tmp = node; while (tmp != null) { if (tmp == this) diff --git a/ICSharpCode.Decompiler/FlowAnalysis/ControlStructureDetector.cs b/ICSharpCode.Decompiler/FlowAnalysis/ControlStructureDetector.cs index a5cf5f5c8..08f251303 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/ControlStructureDetector.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/ControlStructureDetector.cs @@ -34,15 +34,20 @@ namespace ICSharpCode.Decompiler.FlowAnalysis public static ControlStructure DetectStructure(ControlFlowGraph g, IEnumerable exceptionHandlers, CancellationToken cancellationToken) { ControlStructure root = new ControlStructure(new HashSet(g.Nodes), g.EntryPoint, ControlStructureType.Root); + // First build a structure tree out of the exception table DetectExceptionHandling(root, g, exceptionHandlers); + // Then run the loop detection. DetectLoops(g, root, cancellationToken); - g.ResetVisited(); return root; } #region Exception Handling static void DetectExceptionHandling(ControlStructure current, ControlFlowGraph g, IEnumerable exceptionHandlers) { + // We rely on the fact that the exception handlers are sorted so that the innermost come first. + // For each exception handler, we determine the nodes and substructures inside that handler, and move them into a new substructure. + // This is always possible because exception handlers are guaranteed (by the CLR spec) to be properly nested and non-overlapping; + // so they directly form the tree that we need. foreach (ExceptionHandler eh in exceptionHandlers) { var tryNodes = FindNodes(current, eh.TryStart, eh.TryEnd); current.Nodes.ExceptWith(tryNodes); @@ -112,6 +117,13 @@ namespace ICSharpCode.Decompiler.FlowAnalysis #endregion #region Loop Detection + // Loop detection works like this: + // We find a top-level loop by looking for its entry point, which is characterized by a node dominating its own predecessor. + // Then we determine all other nodes that belong to such a loop (all nodes which lead to the entry point, and are dominated by it). + // Finally, we check whether our result conforms with potential existing exception structures, and create the substructure for the loop if successful. + + // This algorithm is applied recursively for any substructures (both detected loops and exception blocks) + static void DetectLoops(ControlFlowGraph g, ControlStructure current, CancellationToken cancellationToken) { g.ResetVisited(); @@ -170,20 +182,56 @@ namespace ICSharpCode.Decompiler.FlowAnalysis public enum ControlStructureType { + /// + /// The root block of the method + /// Root, + /// + /// A nested control structure representing a loop. + /// Loop, + /// + /// A nested control structure representing a try block. + /// Try, + /// + /// A nested control structure representing a catch, finally, or fault block. + /// Handler, + /// + /// A nested control structure representing an exception filter block. + /// Filter } + /// + /// Represents the structure detected by the . + /// + /// This is a tree of ControlStructure nodes. Each node contains a set of CFG nodes, and every CFG node is contained in exactly one ControlStructure node. + /// public class ControlStructure { public readonly ControlStructureType Type; public readonly List Children = new List(); + + /// + /// The nodes in this control structure. + /// public readonly HashSet Nodes; + + /// + /// The nodes in this control structure and in all child control structures. + /// public readonly HashSet AllNodes; + + /// + /// The entry point of this control structure. + /// public readonly ControlFlowNode EntryPoint; + + /// + /// The exception handler associated with this Try,Handler or Finally structure. + /// public ExceptionHandler ExceptionHandler; public ControlStructure(HashSet nodes, ControlFlowNode entryPoint, ControlStructureType type) diff --git a/ICSharpCode.Decompiler/FlowAnalysis/SimplifyByRefCalls.cs b/ICSharpCode.Decompiler/FlowAnalysis/SimplifyByRefCalls.cs index 881a18c19..6b0e03950 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/SimplifyByRefCalls.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/SimplifyByRefCalls.cs @@ -25,6 +25,11 @@ using Mono.Cecil.Cil; namespace ICSharpCode.Decompiler.FlowAnalysis { + /// + /// This is a transformation working on SSA form. + /// It removes ldloca instructions and replaces them with SpecialOpCode.PrepareByOutCall or SpecialOpCode.PrepareByRefCall. + /// This then allows the variable that had its address taken to also be transformed into SSA. + /// sealed class SimplifyByRefCalls { public static bool MakeByRefCallsSimple(SsaForm ssaForm) diff --git a/ICSharpCode.Decompiler/FlowAnalysis/SsaBlock.cs b/ICSharpCode.Decompiler/FlowAnalysis/SsaBlock.cs index f1db4d788..44022e30e 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/SsaBlock.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/SsaBlock.cs @@ -22,12 +22,22 @@ using System.IO; namespace ICSharpCode.Decompiler.FlowAnalysis { + /// + /// A block in a control flow graph; with instructions represented by "SsaInstructions" (instructions use variables, no evaluation stack). + /// Usually these variables are in SSA form to make analysis easier. + /// public sealed class SsaBlock { public readonly List Successors = new List(); public readonly List Predecessors = new List(); public readonly ControlFlowNodeType NodeType; public readonly List Instructions = new List(); + + /// + /// The block index in the control flow graph. + /// This correspons to the node index in ControlFlowGraph.Nodes, so it can be used to retrieve the original CFG node and look + /// up additional information (e.g. dominance). + /// public readonly int BlockIndex; internal SsaBlock(ControlFlowNode node) diff --git a/ICSharpCode.Decompiler/FlowAnalysis/SsaForm.cs b/ICSharpCode.Decompiler/FlowAnalysis/SsaForm.cs index de8001b1c..ed4e2892c 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/SsaForm.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/SsaForm.cs @@ -27,6 +27,9 @@ using Mono.Cecil.Cil; namespace ICSharpCode.Decompiler.FlowAnalysis { + /// + /// Represents a graph of SsaBlocks. + /// public sealed class SsaForm { readonly SsaVariable[] parameters; diff --git a/ICSharpCode.Decompiler/FlowAnalysis/SsaOptimization.cs b/ICSharpCode.Decompiler/FlowAnalysis/SsaOptimization.cs index 42741bda8..5466bf462 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/SsaOptimization.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/SsaOptimization.cs @@ -24,6 +24,9 @@ using Mono.Cecil.Cil; namespace ICSharpCode.Decompiler.FlowAnalysis { + /// + /// Contains some very simple optimizations that work on the SSA form. + /// static class SsaOptimization { public static void Optimize(SsaForm ssaForm) diff --git a/ICSharpCode.Decompiler/FlowAnalysis/SsaVariable.cs b/ICSharpCode.Decompiler/FlowAnalysis/SsaVariable.cs index 503d57cce..a4c16f2a1 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/SsaVariable.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/SsaVariable.cs @@ -23,6 +23,10 @@ using Mono.Cecil.Cil; namespace ICSharpCode.Decompiler.FlowAnalysis { + /// + /// Represents a variable used with the SsaInstruction register-based instructions. + /// Despite what the name suggests, the variable is not necessarily in single-assignment form - take a look at "bool IsSingleAssignment". + /// public sealed class SsaVariable { public int OriginalVariableIndex; @@ -68,6 +72,8 @@ namespace ICSharpCode.Decompiler.FlowAnalysis /// Gets whether this variable has only a single assignment. /// This field is initialized in TransformToSsa step. /// + /// Not all variables can be transformed to single assignment form: variables that have their address taken + /// cannot be represented in SSA (although SimplifyByRefCalls will get rid of the address-taking instruction in almost all cases) public bool IsSingleAssignment; ///