You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
185 lines
6.2 KiB
185 lines
6.2 KiB
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Threading;
|
|
|
|
using ICSharpCode.Decompiler.FlowAnalysis;
|
|
using ICSharpCode.Decompiler.Util;
|
|
|
|
namespace ICSharpCode.Decompiler.IL.ControlFlow
|
|
{
|
|
/// <summary>
|
|
/// Holds the control flow graph.
|
|
/// A separate graph is computed for each BlockContainer at the start of the block transforms
|
|
/// (before loop detection).
|
|
/// </summary>
|
|
public class ControlFlowGraph
|
|
{
|
|
readonly BlockContainer container;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public BlockContainer Container { get { return container; } }
|
|
|
|
/// <summary>
|
|
/// Nodes array, indexed by original block index.
|
|
///
|
|
/// Originally <c>cfg[i].UserData == container.Blocks[i]</c>,
|
|
/// but the ILAst blocks may be moved/reordered by transforms.
|
|
/// </summary>
|
|
internal readonly ControlFlowNode[] cfg;
|
|
|
|
/// <inheritdoc cref="cfg"/>
|
|
public IReadOnlyList<ControlFlowNode> Nodes => cfg;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
readonly Dictionary<Block, ControlFlowNode> dict = new Dictionary<Block, ControlFlowNode>();
|
|
|
|
/// <summary>
|
|
/// nodeHasDirectExitOutOfContainer[i] == true iff cfg[i] directly contains a
|
|
/// branch/leave instruction leaving the <c>container</c>.
|
|
/// </summary>
|
|
readonly BitSet nodeHasDirectExitOutOfContainer;
|
|
|
|
/// <summary>
|
|
/// 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 <c>container</c>.
|
|
/// </summary>
|
|
readonly BitSet nodeHasReachableExit;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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 return,
|
|
// and is not considered a reachable exit.
|
|
if (!leave.IsLeavingFunction)
|
|
{
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the ControlFlowNode for the block.
|
|
///
|
|
/// Precondition: the block belonged to the <c>container</c> at the start of the block transforms
|
|
/// (when the control flow graph was created).
|
|
/// </summary>
|
|
public ControlFlowNode GetNode(Block block)
|
|
{
|
|
return dict[block];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true iff there is a control flow path from <c>node</c> to one of the following:
|
|
/// * branch or leave instruction leaving <c>this.Container</c>
|
|
/// * branch instruction within this container to another node that is not dominated by <c>node</c>.
|
|
///
|
|
/// If this function returns false, the only way control flow can leave the set of nodes
|
|
/// dominated by <c>node</c> is by executing a <c>return</c> or <c>throw</c> instruction.
|
|
/// </summary>
|
|
public bool HasReachableExit(ControlFlowNode node)
|
|
{
|
|
Debug.Assert(cfg[node.UserIndex] == node);
|
|
return nodeHasReachableExit[node.UserIndex];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether the control flow node directly contains a branch/leave instruction
|
|
/// exiting the container.
|
|
/// </summary>
|
|
public bool HasDirectExitOutOfContainer(ControlFlowNode node)
|
|
{
|
|
Debug.Assert(cfg[node.UserIndex] == node);
|
|
return nodeHasDirectExitOutOfContainer[node.UserIndex];
|
|
}
|
|
}
|
|
}
|