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. 157
      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. 2
      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 @@ -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
// 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

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

@ -1009,5 +1009,26 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -1009,5 +1009,26 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
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 @@ -199,7 +199,6 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Console.WriteLine("case 1");
return;
}
if (B(1))
{
Console.WriteLine(1);

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

22
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -632,7 +632,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -632,7 +632,7 @@ namespace ICSharpCode.Decompiler.CSharp
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 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.
if (loopContainer.Kind != ContainerKind.While)
return null;
@ -771,22 +771,22 @@ namespace ICSharpCode.Decompiler.CSharp @@ -771,22 +771,22 @@ namespace ICSharpCode.Decompiler.CSharp
// 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
// loop.
if (optionalReturnAfterLoop != null || whileLoopBlock.Statements.Count > 1)
if (optionalLeaveAfterLoop != null || whileLoopBlock.Statements.Count > 1)
{
var block = new BlockStatement {
Statements = {
foreachStmt
}
};
if (optionalReturnAfterLoop != null)
if (optionalLeaveAfterLoop != null)
{
block.Statements.Add(optionalReturnAfterLoop.AcceptVisitor(this));
block.Statements.Add(optionalLeaveAfterLoop.AcceptVisitor(this));
}
if (whileLoopBlock.Statements.Count > 1)
{
block.Statements.AddRange(whileLoopBlock.Statements
.Skip(1)
.SkipWhile(s => s.Annotations.Any(a => a == optionalReturnAfterLoop))
.SkipWhile(s => s.Annotations.Any(a => a == optionalLeaveAfterLoop))
.Select(SyntaxExtensions.Detach));
}
return block;
@ -860,10 +860,10 @@ namespace ICSharpCode.Decompiler.CSharp @@ -860,10 +860,10 @@ namespace ICSharpCode.Decompiler.CSharp
/// if the value has no side-effects.
/// Otherwise returns the unmodified container.
/// </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>
BlockContainer UnwrapNestedContainerIfPossible(BlockContainer container, out Leave optionalReturnInst)
/// <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 optionalLeaveInst)
{
optionalReturnInst = null;
optionalLeaveInst = null;
// Check block structure:
if (container.Blocks.Count != 1)
return container;
@ -875,11 +875,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -875,11 +875,11 @@ namespace ICSharpCode.Decompiler.CSharp
// If the leave has no value, just unwrap the BlockContainer.
if (leave.MatchLeave(container))
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)
if (leave.IsLeavingFunction && SemanticHelper.IsPure(leave.Value.Flags))
if (SemanticHelper.IsPure(leave.Value.Flags))
{
optionalReturnInst = leave;
optionalLeaveInst = leave;
return nestedContainer;
}
return container;

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

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

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

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

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

@ -16,9 +16,11 @@ @@ -16,9 +16,11 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
#nullable enable
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using ICSharpCode.Decompiler.IL.Transforms;
@ -53,13 +55,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -53,13 +55,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;
@ -67,18 +62,20 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -67,18 +62,20 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// </summary>
internal static ILInstruction GetExit(ILInstruction inst)
{
SlotInfo slot = inst.SlotInfo;
SlotInfo? slot = inst.SlotInfo;
if (slot == Block.InstructionSlot)
{
Block block = (Block)inst.Parent;
Block block = (Block)inst.Parent!;
return block.Instructions.ElementAtOrDefault(inst.ChildIndex + 1) ?? ExitNotYetDetermined;
}
else if (slot == TryInstruction.TryBlockSlot
|| slot == TryCatchHandler.BodySlot
|| 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;
}
@ -106,30 +103,53 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -106,30 +103,53 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
}
CancellationToken cancellationToken;
BlockContainer currentContainer;
class ContainerContext
{
public readonly BlockContainer Container;
/// <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
/// 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.
/// </summary>
ILInstruction currentExit;
public readonly ILInstruction CurrentExit;
/// <summary>
/// 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.
/// </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 Stack<ContainerContext> containerStack = new Stack<ContainerContext>();
public void Run(ILFunction function, ILTransformContext context)
{
cancellationToken = context.CancellationToken;
currentExit = NoExit;
blocksPotentiallyMadeUnreachable.Clear();
containerStack.Clear();
function.AcceptVisitor(this);
// It's possible that there are unreachable code blocks which we only
// detect as such during exit point detection.
@ -142,6 +162,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -142,6 +162,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
}
blocksPotentiallyMadeUnreachable.Clear();
containerStack.Clear();
}
static bool IsInfiniteLoop(Block block)
@ -159,30 +180,26 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -159,30 +180,26 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
protected internal override void VisitBlockContainer(BlockContainer container)
{
var oldExit = currentExit;
var oldContainer = currentContainer;
var oldPotentialExits = potentialExits;
var thisExit = GetExit(container);
currentExit = thisExit;
currentContainer = container;
potentialExits = (thisExit == ExitNotYetDetermined ? new List<ILInstruction>() : null);
var stackEntry = new ContainerContext(container, thisExit);
containerStack.Push(stackEntry);
base.VisitBlockContainer(container);
if (thisExit == ExitNotYetDetermined && potentialExits.Count > 0)
if (stackEntry.PotentialExits?.Any(i => i.IsConnected) ?? false)
{
// This transform determined an exit point.
currentExit = ChooseExit(potentialExits);
foreach (var exit in potentialExits)
var newExit = ChooseExit(stackEntry.PotentialExits.Where(i => i.IsConnected));
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;
// traverse up to the block (we'll always find one because GetExit
// only returns ExitNotYetDetermined if there's a block)
while (inst.Parent.OpCode != OpCode.Block)
while (inst.Parent!.OpCode != OpCode.Block)
inst = inst.Parent;
Block block = (Block)inst.Parent;
if (block.HasFlag(InstructionFlags.EndPointUnreachable))
@ -191,34 +208,33 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -191,34 +208,33 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// we still have an unreachable endpoint.
// The appended currentExit instruction would not be reachable!
// This happens in test case ExceptionHandling.ThrowInFinally()
if (currentExit is Branch b)
if (newExit is Branch b)
{
blocksPotentiallyMadeUnreachable.Add(b.TargetBlock);
}
}
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];
if (first is Leave l && l.IsLeavingFunction)
using var enumerator = potentialExits.GetEnumerator();
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];
if (!(exit is Leave l2 && l2.IsLeavingFunction))
var exit = enumerator.Current;
if (!(exit is Leave { IsLeavingFunction: true }))
return exit;
}
}
@ -235,50 +251,47 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -235,50 +251,47 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
}
void HandleExit(ILInstruction inst)
{
if (currentExit == ExitNotYetDetermined && CanIntroduceAsExit(inst))
protected internal override void VisitBranch(Branch inst)
{
potentialExits.Add(inst);
}
else if (CompatibleExitInstruction(inst, currentExit))
foreach (var entry in containerStack)
{
inst.ReplaceWith(new Leave(currentContainer).WithILRange(inst));
if (inst.TargetBlock.IsDescendantOf(entry.Container))
break;
entry.HandleExit(inst);
}
}
private bool CanIntroduceAsExit(ILInstruction inst)
protected internal override void VisitLeave(Leave inst)
{
base.VisitLeave(inst);
if (!inst.Value.MatchNop())
return;
foreach (var entry in containerStack)
{
if (currentContainer.LeaveCount > 0)
if (inst.TargetContainer == entry.Container)
break;
if (inst.IsLeavingFunction || inst.TargetContainer.Kind != ContainerKind.Normal)
{
// 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)
if (entry.Container.Kind == ContainerKind.Normal)
{
return canIntroduceExitForReturn;
// 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 true;
// return; could turn to break;
entry.HandleExit(inst);
break; // but only for the innermost loop/switch
}
}
protected internal override void VisitBranch(Branch inst)
{
if (!inst.TargetBlock.IsDescendantOf(currentContainer))
else
{
HandleExit(inst);
entry.HandleExit(inst);
}
}
protected internal override void VisitLeave(Leave inst)
{
base.VisitLeave(inst);
if (!inst.Value.MatchNop())
return;
HandleExit(inst);
}
}
}

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

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

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

@ -83,11 +83,15 @@ namespace ICSharpCode.Decompiler.IL @@ -83,11 +83,15 @@ namespace ICSharpCode.Decompiler.IL
}
/// <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).
///
/// This is only valid for functions returning void (representing value-less "return;"),
/// 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>
public bool IsLeavingFunction {
get { return targetContainer?.Parent is ILFunction; }

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

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

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

@ -95,7 +95,7 @@ namespace ICSharpCode.Decompiler.IL @@ -95,7 +95,7 @@ namespace ICSharpCode.Decompiler.IL
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
ILInstruction NextInsn() => i + 1 < block.Instructions.Count ? block.Instructions[i + 1] : nextInstruction;
ILInstruction NextInsn() => block.Instructions.ElementAtOrDefault(i + 1) ?? nextInstruction;
switch (inst)
{

2
ILSpy/LoadedAssembly.cs

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

Loading…
Cancel
Save