Browse Source

DetectExitPoints: introduce exit points for loops+switch

This allows reverting the changes to HighLevelLoopTransform+ReduceNestingTransform from the previous commit, which fixes a bug in loop detection (the previous commit did not handle loops where the loop BlockContainer didn't have a Block as parent).
pull/2425/head
Daniel Grunwald 4 years ago
parent
commit
685a79dc31
  1. 26
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ReduceNesting.cs
  2. 4
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  3. 17
      ICSharpCode.Decompiler/IL/ControlFlow/ExitPoints.cs
  4. 25
      ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs
  5. 4
      ICSharpCode.Decompiler/IL/Transforms/ReduceNestingTransform.cs

26
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ReduceNesting.cs

@ -198,15 +198,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -198,15 +198,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
case 1:
Console.WriteLine("case 1");
return;
default:
if (B(1))
{
Console.WriteLine(1);
}
return;
}
if (B(1))
{
Console.WriteLine(1);
}
}
Console.WriteLine("else");
else
{
Console.WriteLine("else");
}
}
// nesting should not be reduced as maximum nesting level is 1
@ -346,13 +347,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -346,13 +347,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Console.WriteLine();
if (!B(1))
{
return;
}
for (int i = 0; i < 10; i++)
if (B(1))
{
Console.WriteLine(i);
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
}
}
}
catch

4
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -93,7 +93,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -93,7 +93,7 @@ namespace ICSharpCode.Decompiler.CSharp
new YieldReturnDecompiler(), // must run after inlining but before loop detection
new AsyncAwaitDecompiler(), // must run after inlining but before loop detection
new DetectCatchWhenConditionBlocks(), // must run after inlining but before loop detection
new DetectExitPoints(canIntroduceExitForReturn: false),
new DetectExitPoints(),
new EarlyExpressionTransforms(),
// RemoveDeadVariableInit must run after EarlyExpressionTransforms so that stobj(ldloca V, ...)
// is already collapsed into stloc(V, ...).
@ -117,7 +117,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -117,7 +117,7 @@ namespace ICSharpCode.Decompiler.CSharp
}
},
// re-run DetectExitPoints after loop detection
new DetectExitPoints(canIntroduceExitForReturn: false),
new DetectExitPoints(),
new BlockILTransform { // per-block transforms
PostOrderTransforms = {
new ConditionDetection(),

17
ICSharpCode.Decompiler/IL/ControlFlow/ExitPoints.cs

@ -52,13 +52,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -52,13 +52,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
static readonly Nop ExitNotYetDetermined = new Nop { Comment = "ExitNotYetDetermined" };
static readonly Nop NoExit = new Nop { Comment = "NoExit" };
bool canIntroduceExitForReturn;
public DetectExitPoints(bool canIntroduceExitForReturn)
{
this.canIntroduceExitForReturn = canIntroduceExitForReturn;
}
/// <summary>
/// Gets the next instruction after <paramref name="inst"/> is executed.
/// Returns NoExit when the next instruction cannot be identified;
@ -214,12 +207,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -214,12 +207,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
static ILInstruction ChooseExit(List<ILInstruction> potentialExits)
{
ILInstruction first = potentialExits[0];
if (first is Leave l && l.IsLeavingFunction)
if (first is Leave { IsLeavingFunction: true })
{
for (int i = 1; i < potentialExits.Count; i++)
{
var exit = potentialExits[i];
if (!(exit is Leave l2 && l2.IsLeavingFunction))
if (!(exit is Leave { IsLeavingFunction: true }))
return exit;
}
}
@ -256,9 +249,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -256,9 +249,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// we can't introduce any additional exits
return false;
}
if (inst is Leave l && l.IsLeavingFunction)
if (inst is Leave { IsLeavingFunction: true })
{
return canIntroduceExitForReturn;
// Only convert 'return;' to an exit in a context where we can turn it into 'break;'.
// In other contexts we risk turning it into 'goto'.
return currentContainer.Kind != ContainerKind.Normal;
}
else
{

25
ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs

@ -38,25 +38,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -38,25 +38,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
this.context = context;
foreach (var block in function.Descendants.OfType<Block>())
foreach (BlockContainer loop in function.Descendants.OfType<BlockContainer>())
{
for (int i = 0; i < block.Instructions.Count; i++)
if (loop.Kind != ContainerKind.Loop)
continue;
if (MatchWhileLoop(loop, out var condition, out var loopBody))
{
var loop = block.Instructions[i] as BlockContainer;
if (loop == null || loop.Kind != ContainerKind.Loop)
continue;
// convert a "return" to a "break" so that we can match the high-level
// loop patterns
RemoveRedundantReturn.ReturnToBreak(block, loop, context);
if (MatchWhileLoop(loop, out var condition, out var loopBody))
{
if (context.Settings.ForStatement)
MatchForLoop(loop, condition, loopBody);
continue;
}
if (context.Settings.DoWhileStatement && MatchDoWhileLoop(loop))
continue;
if (context.Settings.ForStatement)
MatchForLoop(loop, condition, loopBody);
continue;
}
if (context.Settings.DoWhileStatement && MatchDoWhileLoop(loop))
continue;
}
}

4
ICSharpCode.Decompiler/IL/Transforms/ReduceNestingTransform.cs

@ -103,10 +103,6 @@ namespace ICSharpCode.Decompiler.IL @@ -103,10 +103,6 @@ namespace ICSharpCode.Decompiler.IL
// visit the contents of the container
Visit(container, continueTarget);
if (container.Kind == ContainerKind.Switch)
{
RemoveRedundantReturn.ReturnToBreak(block, container, context);
}
// reduce nesting in switch blocks
if (container.Kind == ContainerKind.Switch &&
CanDuplicateExit(NextInsn(), continueTarget, out var keywordExit1) &&

Loading…
Cancel
Save