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;
///