using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using ICSharpCode.Decompiler.FlowAnalysis; using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.IL.ControlFlow { /// /// Holds the control flow graph. /// A separate graph is computed for each BlockContainer at the start of the block transforms /// (before loop detection). /// public class ControlFlowGraph { readonly BlockContainer container; /// /// The container for which the ControlFlowGraph was created. /// /// This may differ from the container currently holding a block, /// because a transform could have moved the block since the CFG was created. /// public BlockContainer Container { get { return container; } } /// /// Nodes array, indexed by original block index. /// /// Originally cfg[i].UserData == container.Blocks[i], /// but the ILAst blocks may be moved/reordered by transforms. /// internal readonly ControlFlowNode[] cfg; /// /// Dictionary from Block to ControlFlowNode. /// Unlike the cfg array, this can be used to discover control flow nodes even after /// blocks were moved/reordered by transforms. /// readonly Dictionary dict = new Dictionary(); /// /// nodeHasDirectExitOutOfContainer[i] == true iff cfg[i] directly contains a /// branch/leave instruction leaving the container. /// readonly BitSet nodeHasDirectExitOutOfContainer; /// /// nodeHasReachableExit[i] == true iff there is a path from cfg[i] to a node not dominated by cfg[i], /// or if there is a path from cfg[i] to a branch/leave instruction leaving the container. /// readonly BitSet nodeHasReachableExit; /// /// Constructs a control flow graph for the blocks in the given block container. /// /// Return statements, exceptions, or branches leaving the block container are not /// modeled by the control flow graph. /// public ControlFlowGraph(BlockContainer container, CancellationToken cancellationToken = default(CancellationToken)) { this.container = container; this.cfg = new ControlFlowNode[container.Blocks.Count]; this.nodeHasDirectExitOutOfContainer = new BitSet(cfg.Length); for (int i = 0; i < cfg.Length; i++) { Block block = container.Blocks[i]; cfg[i] = new ControlFlowNode { UserIndex = i, UserData = block }; dict.Add(block, cfg[i]); } CreateEdges(cancellationToken); Dominance.ComputeDominance(cfg[0], cancellationToken); this.nodeHasReachableExit = Dominance.MarkNodesWithReachableExits(cfg); this.nodeHasReachableExit.UnionWith(FindNodesWithExitsOutOfContainer()); } void CreateEdges(CancellationToken cancellationToken) { for (int i = 0; i < container.Blocks.Count; i++) { cancellationToken.ThrowIfCancellationRequested(); var block = container.Blocks[i]; var sourceNode = cfg[i]; foreach (var node in block.Descendants) { if (node is Branch branch) { if (branch.TargetBlock.Parent == container) { sourceNode.AddEdgeTo(cfg[container.Blocks.IndexOf(branch.TargetBlock)]); } else if (branch.TargetBlock.IsDescendantOf(container)) { // Internal control flow within a nested container. } else { // Branch out of this container into a parent container. // Like return statements and exceptional exits, // we ignore this for the CFG and the dominance calculation. // However, it's relevant for HasReachableExit(). nodeHasDirectExitOutOfContainer.Set(i); } } else if (node is Leave leave && !leave.TargetContainer.IsDescendantOf(block)) { // Leave instructions (like other exits out of the container) // are ignored for the CFG and dominance, // but is relevant for HasReachableExit(). // However, a 'leave' that exits the whole function represents a void return, // and is not considered a reachable exit (just like non-void returns). if (!(leave.TargetContainer.Parent is ILFunction)) { nodeHasDirectExitOutOfContainer.Set(i); } } } } } BitSet FindNodesWithExitsOutOfContainer() { // Also mark the nodes that exit the block container altogether. // Invariant: leaving[n.UserIndex] == true implies leaving[n.ImmediateDominator.UserIndex] == true var leaving = new BitSet(cfg.Length); foreach (var node in cfg) { if (leaving[node.UserIndex]) continue; if (nodeHasDirectExitOutOfContainer[node.UserIndex]) { for (ControlFlowNode p = node; p != null; p = p.ImmediateDominator) { if (leaving[p.UserIndex]) { // we can stop marking when we've reached an already-marked node break; } leaving.Set(p.UserIndex); } } } return leaving; } bool LeavesCurrentBlockContainer(Block block) { foreach (var node in block.Descendants) { if (node is Branch branch && !branch.TargetBlock.IsDescendantOf(container)) { // control flow that isn't internal to the block container return true; } if (node is Leave leave && !leave.TargetContainer.IsDescendantOf(block)) { return true; } } return false; } /// /// Gets the ControlFlowNode for the block. /// /// Precondition: the block belonged to the container at the start of the block transforms /// (when the control flow graph was created). /// public ControlFlowNode GetNode(Block block) { return dict[block]; } /// /// Returns true iff there is a control flow path from node to one of the following: /// * branch or leave instruction leaving this.Container /// * branch instruction within this container to another node that is not dominated by node. /// /// If this function returns false, the only way control flow can leave the set of nodes /// dominated by node is by executing a return or throw instruction. /// public bool HasReachableExit(ControlFlowNode node) { Debug.Assert(cfg[node.UserIndex] == node); return nodeHasReachableExit[node.UserIndex]; } /// /// Gets whether the control flow node directly contains a branch/leave instruction /// exiting the container. /// public bool HasDirectExitOutOfContainer(ControlFlowNode node) { Debug.Assert(cfg[node.UserIndex] == node); return nodeHasDirectExitOutOfContainer[node.UserIndex]; } } }