Browse Source

Merge PR #2425: Keep return statements around in original form for ConditionDetection

pull/2426/head
Daniel Grunwald 4 years ago
parent
commit
4045d7e338
  1. 22
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExceptionHandling.cs
  2. 21
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.cs
  3. 1
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ReduceNesting.cs
  4. 5
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  5. 22
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  6. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  7. 2
      ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs
  8. 185
      ICSharpCode.Decompiler/IL/ControlFlow/ExitPoints.cs
  9. 115
      ICSharpCode.Decompiler/IL/ControlFlow/RemoveRedundantReturn.cs
  10. 16
      ICSharpCode.Decompiler/IL/Instructions/InstructionCollection.cs
  11. 6
      ICSharpCode.Decompiler/IL/Instructions/Leave.cs
  12. 3
      ICSharpCode.Decompiler/IL/Transforms/HighLevelLoopTransform.cs
  13. 6
      ICSharpCode.Decompiler/IL/Transforms/ReduceNestingTransform.cs
  14. 2
      ILSpy/LoadedAssembly.cs

22
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExceptionHandling.cs

@ -246,6 +246,28 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
} }
} }
internal void EarlyReturnInTryBlock(bool a, bool b)
{
try
{
if (a)
{
Console.WriteLine("a");
}
else if (b)
{
// #2379: The only goto-free way of representing this code is to use a return statement
return;
}
Console.WriteLine("a || !b");
}
finally
{
Console.WriteLine("finally");
}
}
#if ROSLYN || !OPT #if ROSLYN || !OPT
// TODO Non-Roslyn compilers create a second while loop inside the try, by inverting the if // TODO Non-Roslyn compilers create a second while loop inside the try, by inverting the if
// This is fixed in the non-optimised version by the enabling the RemoveDeadCode flag // This is fixed in the non-optimised version by the enabling the RemoveDeadCode flag

21
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.cs

@ -1009,5 +1009,26 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
} }
Console.WriteLine("end"); Console.WriteLine("end");
} }
public void ForEachInSwitch(int i, IEnumerable<string> args)
{
switch (i)
{
case 1:
Console.WriteLine("one");
break;
case 2:
{
foreach (string arg in args)
{
Console.WriteLine(arg);
}
break;
}
default:
throw new NotImplementedException();
}
}
} }
} }

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

@ -199,7 +199,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Console.WriteLine("case 1"); Console.WriteLine("case 1");
return; return;
} }
if (B(1)) if (B(1))
{ {
Console.WriteLine(1); Console.WriteLine(1);

5
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -93,7 +93,7 @@ namespace ICSharpCode.Decompiler.CSharp
new YieldReturnDecompiler(), // must run after inlining but before loop detection new YieldReturnDecompiler(), // must run after inlining but before loop detection
new AsyncAwaitDecompiler(), // 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 DetectCatchWhenConditionBlocks(), // must run after inlining but before loop detection
new DetectExitPoints(canIntroduceExitForReturn: false), new DetectExitPoints(),
new LdLocaDupInitObjTransform(), new LdLocaDupInitObjTransform(),
new EarlyExpressionTransforms(), new EarlyExpressionTransforms(),
// RemoveDeadVariableInit must run after EarlyExpressionTransforms so that stobj(ldloca V, ...) // RemoveDeadVariableInit must run after EarlyExpressionTransforms so that stobj(ldloca V, ...)
@ -118,7 +118,7 @@ namespace ICSharpCode.Decompiler.CSharp
} }
}, },
// re-run DetectExitPoints after loop detection // re-run DetectExitPoints after loop detection
new DetectExitPoints(canIntroduceExitForReturn: true), new DetectExitPoints(),
new BlockILTransform { // per-block transforms new BlockILTransform { // per-block transforms
PostOrderTransforms = { PostOrderTransforms = {
new ConditionDetection(), new ConditionDetection(),
@ -160,6 +160,7 @@ namespace ICSharpCode.Decompiler.CSharp
new TransformDisplayClassUsage(), new TransformDisplayClassUsage(),
new HighLevelLoopTransform(), new HighLevelLoopTransform(),
new ReduceNestingTransform(), new ReduceNestingTransform(),
new RemoveRedundantReturn(),
new IntroduceDynamicTypeOnLocals(), new IntroduceDynamicTypeOnLocals(),
new IntroduceNativeIntTypeOnLocals(), new IntroduceNativeIntTypeOnLocals(),
new AssignVariableNames(), new AssignVariableNames(),

22
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -632,7 +632,7 @@ namespace ICSharpCode.Decompiler.CSharp
var enumeratorVar = inst.Variable; var enumeratorVar = inst.Variable;
// If there's another BlockContainer nested in this container and it only has one child block, unwrap it. // If there's another BlockContainer nested in this container and it only has one child block, unwrap it.
// If there's an extra leave inside the block, extract it into optionalReturnAfterLoop. // If there's an extra leave inside the block, extract it into optionalReturnAfterLoop.
var loopContainer = UnwrapNestedContainerIfPossible(container, out var optionalReturnAfterLoop); var loopContainer = UnwrapNestedContainerIfPossible(container, out var optionalLeaveAfterLoop);
// Detect whether we're dealing with a while loop with multiple embedded statements. // Detect whether we're dealing with a while loop with multiple embedded statements.
if (loopContainer.Kind != ContainerKind.While) if (loopContainer.Kind != ContainerKind.While)
return null; return null;
@ -771,22 +771,22 @@ namespace ICSharpCode.Decompiler.CSharp
// If there was an optional return statement, return it as well. // If there was an optional return statement, return it as well.
// If there were labels or any other statements in the whileLoopBlock, move them after the foreach // If there were labels or any other statements in the whileLoopBlock, move them after the foreach
// loop. // loop.
if (optionalReturnAfterLoop != null || whileLoopBlock.Statements.Count > 1) if (optionalLeaveAfterLoop != null || whileLoopBlock.Statements.Count > 1)
{ {
var block = new BlockStatement { var block = new BlockStatement {
Statements = { Statements = {
foreachStmt foreachStmt
} }
}; };
if (optionalReturnAfterLoop != null) if (optionalLeaveAfterLoop != null)
{ {
block.Statements.Add(optionalReturnAfterLoop.AcceptVisitor(this)); block.Statements.Add(optionalLeaveAfterLoop.AcceptVisitor(this));
} }
if (whileLoopBlock.Statements.Count > 1) if (whileLoopBlock.Statements.Count > 1)
{ {
block.Statements.AddRange(whileLoopBlock.Statements block.Statements.AddRange(whileLoopBlock.Statements
.Skip(1) .Skip(1)
.SkipWhile(s => s.Annotations.Any(a => a == optionalReturnAfterLoop)) .SkipWhile(s => s.Annotations.Any(a => a == optionalLeaveAfterLoop))
.Select(SyntaxExtensions.Detach)); .Select(SyntaxExtensions.Detach));
} }
return block; return block;
@ -860,10 +860,10 @@ namespace ICSharpCode.Decompiler.CSharp
/// if the value has no side-effects. /// if the value has no side-effects.
/// Otherwise returns the unmodified container. /// Otherwise returns the unmodified container.
/// </summary> /// </summary>
/// <param name="optionalReturnInst">If the leave is a return and has no side-effects, we can move the return out of the using-block and put it after the loop, otherwise returns null.</param> /// <param name="optionalLeaveInst">If the leave is a return/break and has no side-effects, we can move the return out of the using-block and put it after the loop, otherwise returns null.</param>
BlockContainer UnwrapNestedContainerIfPossible(BlockContainer container, out Leave optionalReturnInst) BlockContainer UnwrapNestedContainerIfPossible(BlockContainer container, out Leave optionalLeaveInst)
{ {
optionalReturnInst = null; optionalLeaveInst = null;
// Check block structure: // Check block structure:
if (container.Blocks.Count != 1) if (container.Blocks.Count != 1)
return container; return container;
@ -875,11 +875,11 @@ namespace ICSharpCode.Decompiler.CSharp
// If the leave has no value, just unwrap the BlockContainer. // If the leave has no value, just unwrap the BlockContainer.
if (leave.MatchLeave(container)) if (leave.MatchLeave(container))
return nestedContainer; return nestedContainer;
// If the leave is a return, we can move the return out of the using-block and put it after the loop // If the leave is a return/break, we can move it out of the using-block and put it after the loop
// (but only if the value doesn't have side-effects) // (but only if the value doesn't have side-effects)
if (leave.IsLeavingFunction && SemanticHelper.IsPure(leave.Value.Flags)) if (SemanticHelper.IsPure(leave.Value.Flags))
{ {
optionalReturnInst = leave; optionalLeaveInst = leave;
return nestedContainer; return nestedContainer;
} }
return container; return container;

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -79,6 +79,7 @@
<Compile Include="CSharp\Syntax\VariableDesignation.cs" /> <Compile Include="CSharp\Syntax\VariableDesignation.cs" />
<Compile Include="Humanizer\Vocabularies.cs" /> <Compile Include="Humanizer\Vocabularies.cs" />
<Compile Include="Humanizer\Vocabulary.cs" /> <Compile Include="Humanizer\Vocabulary.cs" />
<Compile Include="IL\ControlFlow\RemoveRedundantReturn.cs" />
<Compile Include="IL\Transforms\DeconstructionTransform.cs" /> <Compile Include="IL\Transforms\DeconstructionTransform.cs" />
<Compile Include="IL\Transforms\FixLoneIsInst.cs" /> <Compile Include="IL\Transforms\FixLoneIsInst.cs" />
<Compile Include="IL\Instructions\DeconstructInstruction.cs" /> <Compile Include="IL\Instructions\DeconstructInstruction.cs" />

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

@ -16,7 +16,6 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@ -366,7 +365,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
Debug.Assert(ifInst.Parent == block); Debug.Assert(ifInst.Parent == block);
//assert then block terminates //assert then block terminates
var trueExitInst = GetExit(ifInst.TrueInst);
var exitInst = GetExit(block); var exitInst = GetExit(block);
context.Step($"InvertIf at IL_{ifInst.StartILOffset:x4}", ifInst); context.Step($"InvertIf at IL_{ifInst.StartILOffset:x4}", ifInst);

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

@ -16,9 +16,11 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; #nullable enable
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Threading; using System.Threading;
using ICSharpCode.Decompiler.IL.Transforms; using ICSharpCode.Decompiler.IL.Transforms;
@ -53,13 +55,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
static readonly Nop ExitNotYetDetermined = new Nop { Comment = "ExitNotYetDetermined" }; static readonly Nop ExitNotYetDetermined = new Nop { Comment = "ExitNotYetDetermined" };
static readonly Nop NoExit = new Nop { Comment = "NoExit" }; static readonly Nop NoExit = new Nop { Comment = "NoExit" };
bool canIntroduceExitForReturn;
public DetectExitPoints(bool canIntroduceExitForReturn)
{
this.canIntroduceExitForReturn = canIntroduceExitForReturn;
}
/// <summary> /// <summary>
/// Gets the next instruction after <paramref name="inst"/> is executed. /// Gets the next instruction after <paramref name="inst"/> is executed.
/// Returns NoExit when the next instruction cannot be identified; /// Returns NoExit when the next instruction cannot be identified;
@ -67,18 +62,20 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// </summary> /// </summary>
internal static ILInstruction GetExit(ILInstruction inst) internal static ILInstruction GetExit(ILInstruction inst)
{ {
SlotInfo slot = inst.SlotInfo; SlotInfo? slot = inst.SlotInfo;
if (slot == Block.InstructionSlot) if (slot == Block.InstructionSlot)
{ {
Block block = (Block)inst.Parent; Block block = (Block)inst.Parent!;
return block.Instructions.ElementAtOrDefault(inst.ChildIndex + 1) ?? ExitNotYetDetermined; return block.Instructions.ElementAtOrDefault(inst.ChildIndex + 1) ?? ExitNotYetDetermined;
} }
else if (slot == TryInstruction.TryBlockSlot else if (slot == TryInstruction.TryBlockSlot
|| slot == TryCatchHandler.BodySlot || slot == TryCatchHandler.BodySlot
|| slot == TryCatch.HandlerSlot || slot == TryCatch.HandlerSlot
|| slot == PinnedRegion.BodySlot) || slot == PinnedRegion.BodySlot
|| slot == UsingInstruction.BodySlot
|| slot == LockInstruction.BodySlot)
{ {
return GetExit(inst.Parent); return GetExit(inst.Parent!);
} }
return NoExit; return NoExit;
} }
@ -106,30 +103,53 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
} }
CancellationToken cancellationToken; class ContainerContext
BlockContainer currentContainer; {
public readonly BlockContainer Container;
/// <summary> /// <summary>
/// The instruction that will be executed next after leaving the currentContainer. /// The instruction that will be executed next after leaving the Container.
/// <c>ExitNotYetDetermined</c> means the container is last in its parent block, and thus does not /// <c>ExitNotYetDetermined</c> means the container is last in its parent block, and thus does not
/// yet have any leave instructions. This means we can move any exit instruction of /// yet have any leave instructions. This means we can move any exit instruction of
/// our choice our of the container and replace it with a leave instruction. /// our choice our of the container and replace it with a leave instruction.
/// </summary> /// </summary>
ILInstruction currentExit; public readonly ILInstruction CurrentExit;
/// <summary> /// <summary>
/// If <c>currentExit==ExitNotYetDetermined</c>, holds the list of potential exit instructions. /// If <c>currentExit==ExitNotYetDetermined</c>, holds the list of potential exit instructions.
/// After the currentContainer was visited completely, one of these will be selected as exit instruction. /// After the currentContainer was visited completely, one of these will be selected as exit instruction.
/// </summary> /// </summary>
List<ILInstruction> potentialExits; public readonly List<ILInstruction>? PotentialExits = null;
public ContainerContext(BlockContainer container, ILInstruction currentExit)
{
this.Container = container;
this.CurrentExit = currentExit;
this.PotentialExits = (currentExit == ExitNotYetDetermined ? new List<ILInstruction>() : null);
}
public void HandleExit(ILInstruction inst)
{
if (this.CurrentExit == ExitNotYetDetermined && this.Container.LeaveCount == 0)
{
this.PotentialExits!.Add(inst);
}
else if (CompatibleExitInstruction(inst, this.CurrentExit))
{
inst.ReplaceWith(new Leave(this.Container).WithILRange(inst));
}
}
}
CancellationToken cancellationToken;
readonly List<Block> blocksPotentiallyMadeUnreachable = new List<Block>(); readonly List<Block> blocksPotentiallyMadeUnreachable = new List<Block>();
readonly Stack<ContainerContext> containerStack = new Stack<ContainerContext>();
public void Run(ILFunction function, ILTransformContext context) public void Run(ILFunction function, ILTransformContext context)
{ {
cancellationToken = context.CancellationToken; cancellationToken = context.CancellationToken;
currentExit = NoExit;
blocksPotentiallyMadeUnreachable.Clear(); blocksPotentiallyMadeUnreachable.Clear();
containerStack.Clear();
function.AcceptVisitor(this); function.AcceptVisitor(this);
// It's possible that there are unreachable code blocks which we only // It's possible that there are unreachable code blocks which we only
// detect as such during exit point detection. // detect as such during exit point detection.
@ -142,6 +162,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
} }
blocksPotentiallyMadeUnreachable.Clear(); blocksPotentiallyMadeUnreachable.Clear();
containerStack.Clear();
} }
static bool IsInfiniteLoop(Block block) static bool IsInfiniteLoop(Block block)
@ -159,30 +180,26 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
protected internal override void VisitBlockContainer(BlockContainer container) protected internal override void VisitBlockContainer(BlockContainer container)
{ {
var oldExit = currentExit;
var oldContainer = currentContainer;
var oldPotentialExits = potentialExits;
var thisExit = GetExit(container); var thisExit = GetExit(container);
currentExit = thisExit; var stackEntry = new ContainerContext(container, thisExit);
currentContainer = container; containerStack.Push(stackEntry);
potentialExits = (thisExit == ExitNotYetDetermined ? new List<ILInstruction>() : null);
base.VisitBlockContainer(container); base.VisitBlockContainer(container);
if (thisExit == ExitNotYetDetermined && potentialExits.Count > 0) if (stackEntry.PotentialExits?.Any(i => i.IsConnected) ?? false)
{ {
// This transform determined an exit point. // This transform determined an exit point.
currentExit = ChooseExit(potentialExits); var newExit = ChooseExit(stackEntry.PotentialExits.Where(i => i.IsConnected));
foreach (var exit in potentialExits) Debug.Assert(!newExit.MatchLeave(container));
foreach (var exit in stackEntry.PotentialExits)
{ {
if (CompatibleExitInstruction(currentExit, exit)) if (exit.IsConnected && CompatibleExitInstruction(newExit, exit))
{ {
exit.ReplaceWith(new Leave(currentContainer).WithILRange(exit)); exit.ReplaceWith(new Leave(container).WithILRange(exit));
} }
} }
Debug.Assert(!currentExit.MatchLeave(currentContainer));
ILInstruction inst = container; ILInstruction inst = container;
// traverse up to the block (we'll always find one because GetExit // traverse up to the block (we'll always find one because GetExit
// only returns ExitNotYetDetermined if there's a block) // only returns ExitNotYetDetermined if there's a block)
while (inst.Parent.OpCode != OpCode.Block) while (inst.Parent!.OpCode != OpCode.Block)
inst = inst.Parent; inst = inst.Parent;
Block block = (Block)inst.Parent; Block block = (Block)inst.Parent;
if (block.HasFlag(InstructionFlags.EndPointUnreachable)) if (block.HasFlag(InstructionFlags.EndPointUnreachable))
@ -191,34 +208,33 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// we still have an unreachable endpoint. // we still have an unreachable endpoint.
// The appended currentExit instruction would not be reachable! // The appended currentExit instruction would not be reachable!
// This happens in test case ExceptionHandling.ThrowInFinally() // This happens in test case ExceptionHandling.ThrowInFinally()
if (currentExit is Branch b) if (newExit is Branch b)
{ {
blocksPotentiallyMadeUnreachable.Add(b.TargetBlock); blocksPotentiallyMadeUnreachable.Add(b.TargetBlock);
} }
} }
else else
{ {
block.Instructions.Add(currentExit); block.Instructions.Add(newExit);
} }
} }
else if (containerStack.Pop() != stackEntry)
{ {
Debug.Assert(thisExit == currentExit); Debug.Fail("containerStack got imbalanced");
} }
currentExit = oldExit;
currentContainer = oldContainer;
potentialExits = oldPotentialExits;
} }
static ILInstruction ChooseExit(List<ILInstruction> potentialExits) static ILInstruction ChooseExit(IEnumerable<ILInstruction> potentialExits)
{ {
ILInstruction first = potentialExits[0]; using var enumerator = potentialExits.GetEnumerator();
if (first is Leave l && l.IsLeavingFunction) enumerator.MoveNext();
ILInstruction first = enumerator.Current;
if (first is Leave { IsLeavingFunction: true })
{ {
for (int i = 1; i < potentialExits.Count; i++) while (enumerator.MoveNext())
{ {
var exit = potentialExits[i]; var exit = enumerator.Current;
if (!(exit is Leave l2 && l2.IsLeavingFunction)) if (!(exit is Leave { IsLeavingFunction: true }))
return exit; return exit;
} }
} }
@ -235,41 +251,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
} }
void HandleExit(ILInstruction inst)
{
if (currentExit == ExitNotYetDetermined && CanIntroduceAsExit(inst))
{
potentialExits.Add(inst);
}
else if (CompatibleExitInstruction(inst, currentExit))
{
inst.ReplaceWith(new Leave(currentContainer).WithILRange(inst));
}
}
private bool CanIntroduceAsExit(ILInstruction inst)
{
if (currentContainer.LeaveCount > 0)
{
// if we're re-running on a block container that already has an exit,
// we can't introduce any additional exits
return false;
}
if (inst is Leave l && l.IsLeavingFunction)
{
return canIntroduceExitForReturn;
}
else
{
return true;
}
}
protected internal override void VisitBranch(Branch inst) protected internal override void VisitBranch(Branch inst)
{ {
if (!inst.TargetBlock.IsDescendantOf(currentContainer)) foreach (var entry in containerStack)
{ {
HandleExit(inst); if (inst.TargetBlock.IsDescendantOf(entry.Container))
break;
entry.HandleExit(inst);
} }
} }
@ -278,7 +266,32 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
base.VisitLeave(inst); base.VisitLeave(inst);
if (!inst.Value.MatchNop()) if (!inst.Value.MatchNop())
return; return;
HandleExit(inst); foreach (var entry in containerStack)
{
if (inst.TargetContainer == entry.Container)
break;
if (inst.IsLeavingFunction || inst.TargetContainer.Kind != ContainerKind.Normal)
{
if (entry.Container.Kind == ContainerKind.Normal)
{
// Don't transform a `return`/`break` into a leave for try-block containers (or similar).
// It's possible that those can be turned into fallthrough later, but
// it might not work out and then we would be left with a `goto`.
// But continue searching the container stack, it might be possible to
// turn the `return` into a `break` instead.
}
else
{
// return; could turn to break;
entry.HandleExit(inst);
break; // but only for the innermost loop/switch
}
}
else
{
entry.HandleExit(inst);
}
}
} }
} }
} }

115
ICSharpCode.Decompiler/IL/ControlFlow/RemoveRedundantReturn.cs

@ -0,0 +1,115 @@
// Copyright (c) Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#nullable enable
using System.Linq;
using ICSharpCode.Decompiler.IL.Transforms;
namespace ICSharpCode.Decompiler.IL.ControlFlow
{
/// <summary>
/// Similar to <see cref="DetectExitPoints"/>, but acts only on <c>leave</c> instructions
/// leaving the whole function (<c>return</c>/<c>yield break</c>) that can be made implicit
/// without using goto.
/// </summary>
class RemoveRedundantReturn : IILTransform
{
public void Run(ILFunction function, ILTransformContext context)
{
foreach (var lambda in function.Descendants.OfType<ILFunction>())
{
if (lambda.Body is BlockContainer c && ((lambda.AsyncReturnType ?? lambda.ReturnType).Kind == TypeSystem.TypeKind.Void || lambda.IsIterator))
{
Block lastBlock = c.Blocks.Last();
if (lastBlock.Instructions.Last() is Leave { IsLeavingFunction: true })
{
ConvertReturnToFallthrough(lastBlock.Instructions.SecondToLastOrDefault());
}
else
{
if (ConvertReturnToFallthrough(lastBlock.Instructions.Last()))
{
lastBlock.Instructions.Add(new Leave(c));
}
}
}
}
}
private static bool ConvertReturnToFallthrough(ILInstruction? inst)
{
bool result = false;
switch (inst)
{
case BlockContainer c when c.Kind == ContainerKind.Normal:
// body of try block, or similar: recurse into last instruction in container
// Note: no need to handle loops/switches here; those already were handled by DetectExitPoints
Block lastBlock = c.Blocks.Last();
if (lastBlock.Instructions.Last() is Leave { IsLeavingFunction: true, Value: Nop } leave)
{
leave.TargetContainer = c;
result = true;
}
else if (ConvertReturnToFallthrough(lastBlock.Instructions.Last()))
{
lastBlock.Instructions.Add(new Leave(c));
result = true;
}
break;
case TryCatch tryCatch:
result |= ConvertReturnToFallthrough(tryCatch.TryBlock);
foreach (var h in tryCatch.Handlers)
{
result |= ConvertReturnToFallthrough(h.Body);
}
break;
case TryFinally tryFinally:
result |= ConvertReturnToFallthrough(tryFinally.TryBlock);
break;
case LockInstruction lockInst:
result |= ConvertReturnToFallthrough(lockInst.Body);
break;
case UsingInstruction usingInst:
result |= ConvertReturnToFallthrough(usingInst.Body);
break;
case PinnedRegion pinnedRegion:
result |= ConvertReturnToFallthrough(pinnedRegion.Body);
break;
case IfInstruction ifInstruction:
result |= ConvertReturnToFallthrough(ifInstruction.TrueInst);
result |= ConvertReturnToFallthrough(ifInstruction.FalseInst);
break;
case Block block when block.Kind == BlockKind.ControlFlow:
{
var lastInst = block.Instructions.LastOrDefault();
if (lastInst is Leave { IsLeavingFunction: true, Value: Nop })
{
block.Instructions.RemoveAt(block.Instructions.Count - 1);
result = true;
lastInst = block.Instructions.LastOrDefault();
}
result |= ConvertReturnToFallthrough(lastInst);
break;
}
}
return result;
}
}
}

16
ICSharpCode.Decompiler/IL/Instructions/InstructionCollection.cs

@ -16,6 +16,8 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@ -73,7 +75,7 @@ namespace ICSharpCode.Decompiler.IL
public struct Enumerator : IEnumerator<T> public struct Enumerator : IEnumerator<T>
{ {
#if DEBUG #if DEBUG
ILInstruction parentInstruction; ILInstruction? parentInstruction;
#endif #endif
readonly List<T> list; readonly List<T> list;
int pos; int pos;
@ -140,7 +142,7 @@ namespace ICSharpCode.Decompiler.IL
/// Runs in O(1) if the item can be found using the Parent/ChildIndex properties. /// Runs in O(1) if the item can be found using the Parent/ChildIndex properties.
/// Otherwise, runs in O(N). /// Otherwise, runs in O(N).
/// </remarks> /// </remarks>
public int IndexOf(T item) public int IndexOf(T? item)
{ {
if (item == null) if (item == null)
{ {
@ -163,7 +165,7 @@ namespace ICSharpCode.Decompiler.IL
/// This method searches the list. /// This method searches the list.
/// Usually it's more efficient to test item.Parent instead! /// Usually it's more efficient to test item.Parent instead!
/// </remarks> /// </remarks>
public bool Contains(T item) public bool Contains(T? item)
{ {
return IndexOf(item) >= 0; return IndexOf(item) >= 0;
} }
@ -395,7 +397,7 @@ namespace ICSharpCode.Decompiler.IL
return list[0]; return list[0];
} }
public T FirstOrDefault() public T? FirstOrDefault()
{ {
return list.Count > 0 ? list[0] : null; return list.Count > 0 ? list[0] : null;
} }
@ -405,17 +407,17 @@ namespace ICSharpCode.Decompiler.IL
return list[list.Count - 1]; return list[list.Count - 1];
} }
public T LastOrDefault() public T? LastOrDefault()
{ {
return list.Count > 0 ? list[list.Count - 1] : null; return list.Count > 0 ? list[list.Count - 1] : null;
} }
public T SecondToLastOrDefault() public T? SecondToLastOrDefault()
{ {
return list.Count > 1 ? list[list.Count - 2] : null; return list.Count > 1 ? list[list.Count - 2] : null;
} }
public T ElementAtOrDefault(int index) public T? ElementAtOrDefault(int index)
{ {
if (index >= 0 && index < list.Count) if (index >= 0 && index < list.Count)
return list[index]; return list[index];

6
ICSharpCode.Decompiler/IL/Instructions/Leave.cs

@ -83,11 +83,15 @@ namespace ICSharpCode.Decompiler.IL
} }
/// <summary> /// <summary>
/// Gets whether the leave instruction is leaving the whole ILFunction. /// Gets whether the leave instruction is directly leaving the whole ILFunction.
/// (TargetContainer == main container of the function). /// (TargetContainer == main container of the function).
/// ///
/// This is only valid for functions returning void (representing value-less "return;"), /// This is only valid for functions returning void (representing value-less "return;"),
/// and for iterators (representing "yield break;"). /// and for iterators (representing "yield break;").
///
/// Note: returns false for leave instructions that indirectly leave the function
/// (e.g. leaving a try block, and the try-finally construct is immediately followed
/// by another leave instruction)
/// </summary> /// </summary>
public bool IsLeavingFunction { public bool IsLeavingFunction {
get { return targetContainer?.Parent is ILFunction; } get { return targetContainer?.Parent is ILFunction; }

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

@ -20,7 +20,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Text;
using ICSharpCode.Decompiler.IL.ControlFlow; using ICSharpCode.Decompiler.IL.ControlFlow;
using ICSharpCode.Decompiler.Util; using ICSharpCode.Decompiler.Util;
@ -39,7 +38,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
this.context = context; this.context = context;
foreach (var loop in function.Descendants.OfType<BlockContainer>()) foreach (BlockContainer loop in function.Descendants.OfType<BlockContainer>())
{ {
if (loop.Kind != ContainerKind.Loop) if (loop.Kind != ContainerKind.Loop)
continue; continue;

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

@ -95,7 +95,7 @@ namespace ICSharpCode.Decompiler.IL
var inst = block.Instructions[i]; var inst = block.Instructions[i];
// the next instruction to be executed. Transformations will change the next instruction, so this is a method instead of a variable // the next instruction to be executed. Transformations will change the next instruction, so this is a method instead of a variable
ILInstruction NextInsn() => i + 1 < block.Instructions.Count ? block.Instructions[i + 1] : nextInstruction; ILInstruction NextInsn() => block.Instructions.ElementAtOrDefault(i + 1) ?? nextInstruction;
switch (inst) switch (inst)
{ {
@ -105,8 +105,8 @@ namespace ICSharpCode.Decompiler.IL
// reduce nesting in switch blocks // reduce nesting in switch blocks
if (container.Kind == ContainerKind.Switch && if (container.Kind == ContainerKind.Switch &&
CanDuplicateExit(NextInsn(), continueTarget, out var keywordExit1) && CanDuplicateExit(NextInsn(), continueTarget, out var keywordExit1) &&
ReduceSwitchNesting(block, container, keywordExit1)) ReduceSwitchNesting(block, container, keywordExit1))
{ {
RemoveRedundantExit(block, nextInstruction); RemoveRedundantExit(block, nextInstruction);
} }

2
ILSpy/LoadedAssembly.cs

@ -473,7 +473,7 @@ namespace ICSharpCode.ILSpy
return module; return module;
} }
string file = parent.GetUniversalResolver().FindAssemblyFile(reference); string? file = parent.GetUniversalResolver().FindAssemblyFile(reference);
if (file != null) if (file != null)
{ {

Loading…
Cancel
Save