Browse Source

Fix #1242: Discard unreachable code.

Unreachable code is not part of the dominator tree, which most of our transforms are based on.

In particular, dominance-based loop detection runs into the problem where unreachable code might have jumps into two independent loops. In that case, it's impossible to place the unreachable code in a way that avoids assertions / generating invalid C#.

We establish the invariant that all blocks in a BlockContainer must be statically reachable from the entry point (-> every block is part of the dominator tree). This means transforms no longer have to deal with special cases for unreachable code.

The "Remove dead and side effect free code" option still has an effect on dead stores, but unreachable code is now always removed (previously this also was dependent on this option).
pull/1612/head
Daniel Grunwald 7 years ago
parent
commit
8117dfff4a
  1. 4
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1047.cs
  2. 3
      ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs
  3. 32
      ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs
  4. 2
      ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs
  5. 12
      ICSharpCode.Decompiler/IL/ILReader.cs
  6. 48
      ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs
  7. 1
      ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs

4
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/Issue1047.cs

@ -6,12 +6,8 @@
private void ProblemMethod() private void ProblemMethod()
{ {
IL_0000:
while (!dummy) { while (!dummy) {
} }
return;
IL_0014:
goto IL_0000;
} }
} }
} }

3
ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs

@ -158,9 +158,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
// Remove return blocks that are no longer reachable: // Remove return blocks that are no longer reachable:
container.Blocks.RemoveAll(b => b.IncomingEdgeCount == 0 && b.Instructions.Count == 0); container.Blocks.RemoveAll(b => b.IncomingEdgeCount == 0 && b.Instructions.Count == 0);
if (context.Settings.RemoveDeadCode) {
container.SortBlocks(deleteUnreachableBlocks: true);
}
} }
} }

32
ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs

@ -107,7 +107,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
IncludeNestedContainers(loop); IncludeNestedContainers(loop);
// Try to extend the loop to reduce the number of exit points: // Try to extend the loop to reduce the number of exit points:
ExtendLoop(h, loop, out var exitPoint); ExtendLoop(h, loop, out var exitPoint);
IncludeUnreachablePredecessors(loop);
// Sort blocks in the loop in reverse post-order to make the output look a bit nicer. // Sort blocks in the loop in reverse post-order to make the output look a bit nicer.
// (if the loop doesn't contain nested loops, this is a topological sort) // (if the loop doesn't contain nested loops, this is a topological sort)
@ -115,7 +114,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
Debug.Assert(loop[0] == h); Debug.Assert(loop[0] == h);
foreach (var node in loop) { foreach (var node in loop) {
node.Visited = false; // reset visited flag so that we can find outer loops node.Visited = false; // reset visited flag so that we can find outer loops
Debug.Assert(h.Dominates(node) || !node.IsReachable, "The loop body must be dominated by the loop head"); Debug.Assert(h.Dominates(node), "The loop body must be dominated by the loop head");
} }
ConstructLoop(loop, exitPoint); ConstructLoop(loop, exitPoint);
} }
@ -150,7 +149,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// (the entry-point itself doesn't have a CFG node, because it's newly created by this transform) // (the entry-point itself doesn't have a CFG node, because it's newly created by this transform)
for (int i = 1; i < nestedContainer.Blocks.Count; i++) { for (int i = 1; i < nestedContainer.Blocks.Count; i++) {
var node = context.ControlFlowGraph.GetNode(nestedContainer.Blocks[i]); var node = context.ControlFlowGraph.GetNode(nestedContainer.Blocks[i]);
Debug.Assert(loop[0].Dominates(node) || !node.IsReachable); Debug.Assert(loop[0].Dominates(node));
if (!node.Visited) { if (!node.Visited) {
node.Visited = true; node.Visited = true;
loop.Add(node); loop.Add(node);
@ -602,30 +601,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
#endregion #endregion
/// <summary>
/// While our normal dominance logic ensures the loop has just a single reachable entry point,
/// it's possible that there are unreachable code blocks that have jumps into the loop.
/// We'll also include those into the loop.
///
/// Requires and maintains the invariant that a node is marked as visited iff it is contained in the loop.
/// </summary>
private void IncludeUnreachablePredecessors(List<ControlFlowNode> loop)
{
for (int i = 1; i < loop.Count; i++) {
Debug.Assert(loop[i].Visited);
foreach (var pred in loop[i].Predecessors) {
if (!pred.Visited) {
if (pred.IsReachable) {
Debug.Fail("All jumps into the loop body should go through the entry point");
} else {
pred.Visited = true;
loop.Add(pred);
}
}
}
}
}
/// <summary> /// <summary>
/// Move the blocks associated with the loop into a new block container. /// Move the blocks associated with the loop into a new block container.
/// </summary> /// </summary>
@ -708,7 +683,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
exitPoint = null; exitPoint = null;
} }
IncludeUnreachablePredecessors(nodesInSwitch);
context.Step("Create BlockContainer for switch", switchInst); context.Step("Create BlockContainer for switch", switchInst);
// Sort blocks in the loop in reverse post-order to make the output look a bit nicer. // Sort blocks in the loop in reverse post-order to make the output look a bit nicer.
@ -717,7 +691,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
Debug.Assert(nodesInSwitch[0] == h); Debug.Assert(nodesInSwitch[0] == h);
foreach (var node in nodesInSwitch) { foreach (var node in nodesInSwitch) {
node.Visited = false; // reset visited flag so that we can find outer loops node.Visited = false; // reset visited flag so that we can find outer loops
Debug.Assert(h.Dominates(node) || !node.IsReachable, "The switch body must be dominated by the switch head"); Debug.Assert(h.Dominates(node), "The switch body must be dominated by the switch head");
} }
BlockContainer switchContainer = new BlockContainer(ContainerKind.Switch); BlockContainer switchContainer = new BlockContainer(ContainerKind.Switch);

2
ICSharpCode.Decompiler/IL/ControlFlow/YieldReturnDecompiler.cs

@ -142,7 +142,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
function.Body = newBody; function.Body = newBody;
// register any locals used in newBody // register any locals used in newBody
function.Variables.AddRange(newBody.Descendants.OfType<IInstructionWithVariableOperand>().Select(inst => inst.Variable).Distinct()); function.Variables.AddRange(newBody.Descendants.OfType<IInstructionWithVariableOperand>().Select(inst => inst.Variable).Distinct());
function.CheckInvariant(ILPhase.Normal);
PrintFinallyMethodStateRanges(newBody); PrintFinallyMethodStateRanges(newBody);
@ -164,6 +163,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// Note: because this only deletes blocks outright, the 'stateChanges' entries remain valid // Note: because this only deletes blocks outright, the 'stateChanges' entries remain valid
// (though some may point to now-deleted blocks) // (though some may point to now-deleted blocks)
newBody.SortBlocks(deleteUnreachableBlocks: true); newBody.SortBlocks(deleteUnreachableBlocks: true);
function.CheckInvariant(ILPhase.Normal);
if (!isCompiledWithMono) { if (!isCompiledWithMono) {
DecompileFinallyBlocks(); DecompileFinallyBlocks();

12
ICSharpCode.Decompiler/IL/ILReader.cs

@ -493,8 +493,18 @@ namespace ICSharpCode.Decompiler.IL
CollectionExtensions.AddRange(function.Variables, stackVariables); CollectionExtensions.AddRange(function.Variables, stackVariables);
CollectionExtensions.AddRange(function.Variables, variableByExceptionHandler.Values); CollectionExtensions.AddRange(function.Variables, variableByExceptionHandler.Values);
function.AddRef(); // mark the root node function.AddRef(); // mark the root node
var removedBlocks = new List<Block>();
foreach (var c in function.Descendants.OfType<BlockContainer>()) { foreach (var c in function.Descendants.OfType<BlockContainer>()) {
c.SortBlocks(); var newOrder = c.TopologicalSort(deleteUnreachableBlocks: true);
if (newOrder.Count < c.Blocks.Count) {
removedBlocks.AddRange(c.Blocks.Except(newOrder));
}
c.Blocks.ReplaceList(newOrder);
}
if (removedBlocks.Count > 0) {
removedBlocks.SortBy(b => b.StartILOffset);
function.Warnings.Add("Discarded unreachable code: "
+ string.Join(", ", removedBlocks.Select(b => $"IL_{b.StartILOffset:x4}")));
} }
function.Warnings.AddRange(Warnings); function.Warnings.AddRange(Warnings);
return function; return function;

48
ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs

@ -184,6 +184,7 @@ namespace ICSharpCode.Decompiler.IL
Debug.Assert(EntryPoint == null || Parent is ILFunction || !HasILRange); Debug.Assert(EntryPoint == null || Parent is ILFunction || !HasILRange);
Debug.Assert(Blocks.All(b => b.HasFlag(InstructionFlags.EndPointUnreachable))); Debug.Assert(Blocks.All(b => b.HasFlag(InstructionFlags.EndPointUnreachable)));
Debug.Assert(Blocks.All(b => b.Kind == BlockKind.ControlFlow)); // this also implies that the blocks don't use FinalInstruction Debug.Assert(Blocks.All(b => b.Kind == BlockKind.ControlFlow)); // this also implies that the blocks don't use FinalInstruction
Debug.Assert(TopologicalSort(deleteUnreachableBlocks: true).Count == Blocks.Count, "Container should not have any unreachable blocks");
Block bodyStartBlock; Block bodyStartBlock;
switch (Kind) { switch (Kind) {
case ContainerKind.Normal: case ContainerKind.Normal:
@ -239,43 +240,54 @@ namespace ICSharpCode.Decompiler.IL
} }
/// <summary> /// <summary>
/// Sort the blocks in reverse post-order over the control flow graph between the blocks. /// Topologically sort the blocks.
/// The new order is returned without modifying the BlockContainer.
/// </summary> /// </summary>
public void SortBlocks(bool deleteUnreachableBlocks = false) /// <param name="deleteUnreachableBlocks">If true, unreachable blocks are not included in the new order.</param>
public List<Block> TopologicalSort(bool deleteUnreachableBlocks = false)
{ {
if (Blocks.Count < 2)
return;
// Visit blocks in post-order // Visit blocks in post-order
BitSet visited = new BitSet(Blocks.Count); BitSet visited = new BitSet(Blocks.Count);
List<Block> postOrder = new List<Block>(); List<Block> postOrder = new List<Block>();
Visit(EntryPoint);
postOrder.Reverse();
if (!deleteUnreachableBlocks) {
for (int i = 0; i < Blocks.Count; i++) {
if (!visited[i])
postOrder.Add(Blocks[i]);
}
}
return postOrder;
Action<Block> visit = null; void Visit(Block block)
visit = delegate(Block block) { {
Debug.Assert(block.Parent == this); Debug.Assert(block.Parent == this);
if (!visited[block.ChildIndex]) { if (!visited[block.ChildIndex]) {
visited[block.ChildIndex] = true; visited[block.ChildIndex] = true;
foreach (var branch in block.Descendants.OfType<Branch>()) { foreach (var branch in block.Descendants.OfType<Branch>()) {
if (branch.TargetBlock.Parent == this) { if (branch.TargetBlock.Parent == this) {
visit(branch.TargetBlock); Visit(branch.TargetBlock);
} }
} }
postOrder.Add(block); postOrder.Add(block);
} }
}; };
visit(EntryPoint); }
postOrder.Reverse(); /// <summary>
if (!deleteUnreachableBlocks) { /// Topologically sort the blocks.
for (int i = 0; i < Blocks.Count; i++) { /// </summary>
if (!visited[i]) /// <param name="deleteUnreachableBlocks">If true, delete unreachable blocks.</param>
postOrder.Add(Blocks[i]); public void SortBlocks(bool deleteUnreachableBlocks = false)
} {
} if (Blocks.Count < 2)
Debug.Assert(postOrder[0] == Blocks[0]); return;
Blocks.ReplaceList(postOrder);
var newOrder = TopologicalSort(deleteUnreachableBlocks);
Debug.Assert(newOrder[0] == Blocks[0]);
Blocks.ReplaceList(newOrder);
} }
public static BlockContainer FindClosestContainer(ILInstruction inst) public static BlockContainer FindClosestContainer(ILInstruction inst)

1
ICSharpCode.Decompiler/IL/Transforms/BlockTransform.cs

@ -85,7 +85,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
context.CancellationToken.ThrowIfCancellationRequested(); context.CancellationToken.ThrowIfCancellationRequested();
blockContext.ControlFlowGraph = new ControlFlowGraph(container, context.CancellationToken); blockContext.ControlFlowGraph = new ControlFlowGraph(container, context.CancellationToken);
VisitBlock(blockContext.ControlFlowGraph.GetNode(container.EntryPoint), blockContext); VisitBlock(blockContext.ControlFlowGraph.GetNode(container.EntryPoint), blockContext);
// TODO: handle unreachable code?
} }
} finally { } finally {
running = false; running = false;

Loading…
Cancel
Save