Browse Source

Fix #1749, fix #2339, fix #2353: Add support for rethrow in async exception handlers, fix await catch/finally patterns for complex methods.

pull/2350/head
Siegfried Pammer 4 years ago
parent
commit
76227af89d
  1. 17
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/Async.cs
  2. 154
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Async.cs
  3. 7
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncForeach.cs
  4. 26
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExceptionHandling.cs
  5. 18
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/UsingVariables.cs
  6. 392
      ICSharpCode.Decompiler/IL/ControlFlow/AwaitInCatchTransform.cs
  7. 427
      ICSharpCode.Decompiler/IL/ControlFlow/AwaitInFinallyTransform.cs
  8. 86
      ICSharpCode.Decompiler/IL/Transforms/DetectCatchWhenConditionBlocks.cs
  9. 2
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

17
ICSharpCode.Decompiler.Tests/TestCases/Correctness/Async.cs

@ -34,18 +34,28 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -34,18 +34,28 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
public async Task Run()
{
Console.WriteLine("SimpleBoolTaskMethod:");
await SimpleBoolTaskMethod();
Console.WriteLine("StreamCopyTo:");
StreamCopyTo(new MemoryStream(new byte[1024]), 16);
Console.WriteLine("StreamCopyToWithConfigureAwait:");
StreamCopyToWithConfigureAwait(new MemoryStream(new byte[1024]), 16);
Console.WriteLine("AwaitInForEach:");
await AwaitInForEach(Enumerable.Range(0, 100).Select(i => Task.FromResult(i)));
Console.WriteLine("TaskMethodWithoutAwaitButWithExceptionHandling:");
await TaskMethodWithoutAwaitButWithExceptionHandling();
#if CS60
Console.WriteLine($"{nameof(AwaitCatch)}:");
await AwaitCatch(Task.FromResult(1));
Console.WriteLine($"{nameof(AwaitMultipleCatchBlocks)}:");
await AwaitMultipleCatchBlocks(Task.FromResult(1));
Console.WriteLine($"{nameof(AwaitMultipleCatchBlocks2)}:");
await AwaitMultipleCatchBlocks2(Task.FromResult(1));
Console.WriteLine($"{nameof(AwaitInComplexFinally)}:");
Console.WriteLine(await AwaitInComplexFinally());
try
{
Console.WriteLine($"{nameof(AwaitFinally)}:");
await AwaitFinally(Task.FromResult(2));
}
catch (Exception ex)
@ -53,13 +63,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -53,13 +63,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
Console.WriteLine(ex + " caught!");
}
#endif
Console.WriteLine("NestedAwait:");
await NestedAwait(Task.FromResult(Task.FromResult(5)));
Console.WriteLine("AwaitWithStack:");
await AwaitWithStack(Task.FromResult(3));
Console.WriteLine("AwaitWithStack2:");
await AwaitWithStack2(Task.FromResult(4));
#if CS60
Console.WriteLine($"{nameof(AwaitInCatch)}:");
await AwaitInCatch(Task.FromResult(1), Task.FromResult(2));
Console.WriteLine($"{nameof(AwaitInFinally)}:");
await AwaitInFinally(Task.FromResult(2), Task.FromResult(4));
Console.WriteLine($"{nameof(AwaitInCatchAndFinally)}:");
await AwaitInCatchAndFinally(Task.FromResult(3), Task.FromResult(6), Task.FromResult(9));
Console.WriteLine($"{nameof(AwaitInFinallyInUsing)}:");
Console.WriteLine(await AwaitInFinallyInUsing(Task.FromResult<IDisposable>(new StringWriter()), Task.FromResult(6), Task.FromResult(9)));
#endif
}

154
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Async.cs

@ -28,6 +28,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -28,6 +28,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
private int memberField;
private static bool True()
{
return true;
}
public async void SimpleVoidMethod()
{
Console.WriteLine("Before");
@ -152,6 +157,155 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -152,6 +157,155 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
}
}
public async Task AnonymousThrow()
{
try
{
await Task.Delay(0);
}
catch
{
await Task.Delay(0);
throw;
}
}
public async Task DeclaredException()
{
try
{
await Task.Delay(0);
}
catch (Exception)
{
await Task.Delay(0);
throw;
}
}
public async Task RethrowDeclared()
{
try
{
await Task.Delay(0);
}
catch (Exception ex)
{
await Task.Delay(0);
throw ex;
}
}
public async Task RethrowDeclaredWithFilter()
{
try
{
await Task.Delay(0);
}
catch (Exception ex) when (ex.GetType().FullName.Contains("asdf"))
{
await Task.Delay(0);
throw;
}
}
public async Task ComplexCatchBlock()
{
try
{
await Task.Delay(0);
}
catch (Exception ex)
{
if (ex.GetHashCode() != 0)
{
throw;
}
await Task.Delay(0);
}
}
public async Task ComplexCatchBlockWithFilter()
{
try
{
await Task.Delay(0);
}
catch (Exception ex) when (ex.GetType().FullName.Contains("asdf"))
{
if (ex.GetHashCode() != 0)
{
throw;
}
await Task.Delay(0);
}
}
public async Task LoadsToCatch(int i)
{
try
{
throw null;
}
catch (Exception ex2) when (i == 0)
{
Console.WriteLine("First!");
if (i == 1)
{
throw;
}
await Task.Yield();
Console.WriteLine(ex2.StackTrace);
}
catch (Exception ex3) when (True())
{
Console.WriteLine("Second!");
if (i == 1)
{
throw;
}
await Task.Yield();
Console.WriteLine(ex3.StackTrace);
}
catch (Exception ex)
{
Console.WriteLine("Third!");
if (i == 1)
{
throw;
}
await Task.Yield();
Console.WriteLine(ex.StackTrace);
}
catch when (i == 0)
{
Console.WriteLine("Fourth!");
if (i == 1)
{
throw;
}
await Task.Yield();
}
catch when (True())
{
Console.WriteLine("Fifth!");
if (i == 1)
{
throw;
}
await Task.Yield();
}
catch
{
Console.WriteLine("Sixth!");
if (i == 1)
{
throw;
}
await Task.Yield();
}
}
#endif
public static async Task<int> GetIntegerSumAsync(IEnumerable<int> items)

7
ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncForeach.cs

@ -29,11 +29,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -29,11 +29,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
int sum = 0;
await foreach (int item in items.WithCancellation(token))
{
if (token.IsCancellationRequested)
if (!token.IsCancellationRequested)
{
break;
sum += item;
continue;
}
sum += item;
break;
}
return sum;
}

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

@ -448,6 +448,32 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -448,6 +448,32 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Console.WriteLine("{0} {1}", val, val.ToString());
}
}
public void XXX1()
{
try
{
Console.WriteLine();
}
catch (Exception ex) when (ex.Data.IsFixedSize)
{
Console.WriteLine(ex.ToString());
throw ex;
}
}
public void XXX2()
{
try
{
Console.WriteLine();
}
catch (Exception ex) when (ex is InternalBufferOverflowException)
{
Console.WriteLine(ex.ToString());
throw ex;
}
}
#endif
}
}

18
ICSharpCode.Decompiler.Tests/TestCases/Pretty/UsingVariables.cs

@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
// DEALINGS IN THE SOFTWARE.
using System;
//using System.Threading.Tasks;
using System.Threading.Tasks;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
@ -38,7 +38,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -38,7 +38,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
private void Use(IAsyncDisposable disposable)
private void Use(IAsyncDisposable asyncDisposable)
{
}
@ -84,12 +84,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -84,12 +84,12 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Use(disposable2);
}
//public async Task SimpleUsingVarAsync(IAsyncDisposable other)
//{
// Console.WriteLine("before using");
// await using IAsyncDisposable asyncDisposable = GetAsyncDisposable();
// Console.WriteLine("inside using");
// Use(asyncDisposable);
//}
public async Task SimpleUsingVarAsync()
{
Console.WriteLine("before using");
await using IAsyncDisposable asyncDisposable = GetAsyncDisposable();
Console.WriteLine("inside using");
Use(asyncDisposable);
}
}
}

392
ICSharpCode.Decompiler/IL/ControlFlow/AwaitInCatchTransform.cs

@ -18,19 +18,43 @@ @@ -18,19 +18,43 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.Decompiler.IL.Transforms;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL.ControlFlow
{
class AwaitInCatchTransform
{
readonly struct CatchBlockInfo
{
public readonly int Id;
public readonly TryCatchHandler Handler;
public readonly Block RealCatchBlockEntryPoint;
public readonly ILInstruction NextBlockOrExitContainer;
public readonly ILInstruction JumpTableEntry;
public readonly ILVariable ObjectVariable;
public CatchBlockInfo(int id, TryCatchHandler handler, Block realCatchBlockEntryPoint,
ILInstruction nextBlockOrExitContainer, ILInstruction jumpTableEntry, ILVariable objectVariable)
{
Id = id;
Handler = handler;
RealCatchBlockEntryPoint = realCatchBlockEntryPoint;
NextBlockOrExitContainer = nextBlockOrExitContainer;
JumpTableEntry = jumpTableEntry;
ObjectVariable = objectVariable;
}
}
public static void Run(ILFunction function, ILTransformContext context)
{
if (!context.Settings.AwaitInCatchFinally)
return;
HashSet<BlockContainer> changedContainers = new HashSet<BlockContainer>();
HashSet<Block> removedBlocks = new HashSet<Block>();
// analyze all try-catch statements in the function
foreach (var tryCatch in function.Descendants.OfType<TryCatch>().ToArray())
@ -44,28 +68,32 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -44,28 +68,32 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
changedContainers.Add(container);
foreach (var result in transformableCatchBlocks)
{
removedBlocks.Clear();
var node = cfg.GetNode(result.RealCatchBlockEntryPoint);
context.StepStartGroup("Inline catch block with await", result.Handler);
context.StepStartGroup($"Inline catch block with await (at {result.Handler.Variable.Name})", result.Handler);
// Remove the IfInstruction from the jump table and eliminate all branches to the block.
var jumpTableBlock = (Block)result.JumpTableEntry.Parent;
context.Step("Remove jump-table entry", result.JumpTableEntry);
jumpTableBlock.Instructions.RemoveAt(result.JumpTableEntry.ChildIndex);
foreach (var branch in tryCatch.Descendants.OfType<Branch>())
if (result.JumpTableEntry is IfInstruction jumpTableEntry)
{
if (branch.TargetBlock == jumpTableBlock)
var jumpTableBlock = (Block)jumpTableEntry.Parent;
context.Step("Remove jump-table entry", result.JumpTableEntry);
jumpTableBlock.Instructions.RemoveAt(result.JumpTableEntry.ChildIndex);
foreach (var branch in tryCatch.Descendants.OfType<Branch>())
{
if (result.NextBlockOrExitContainer is BlockContainer exitContainer)
if (branch.TargetBlock == jumpTableBlock)
{
context.Step("branch jumpTableBlock => leave exitContainer", branch);
branch.ReplaceWith(new Leave(exitContainer));
}
else
{
context.Step("branch jumpTableBlock => branch nextBlock", branch);
branch.ReplaceWith(new Branch((Block)result.NextBlockOrExitContainer));
if (result.NextBlockOrExitContainer is BlockContainer exitContainer)
{
context.Step("branch jumpTableBlock => leave exitContainer", branch);
branch.ReplaceWith(new Leave(exitContainer));
}
else
{
context.Step("branch jumpTableBlock => branch nextBlock", branch);
branch.ReplaceWith(new Branch((Block)result.NextBlockOrExitContainer));
}
}
}
}
@ -79,14 +107,25 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -79,14 +107,25 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// Remove the generated catch block
catchBlockHead.Remove();
TransformAsyncThrowToThrow(context, removedBlocks, result.RealCatchBlockEntryPoint);
// Inline all blocks that are dominated by the entrypoint of the real catch block
foreach (var n in cfg.cfg)
{
if (((Block)n.UserData).Parent == result.Handler.Body)
continue;
Block block = (Block)n.UserData;
if (node.Dominates(n))
{
MoveBlock((Block)n.UserData, (BlockContainer)result.Handler.Body);
TransformAsyncThrowToThrow(context, removedBlocks, block);
if (block.Parent == result.Handler.Body)
continue;
if (!removedBlocks.Contains(block))
{
context.Step("Move block", result.Handler.Body);
MoveBlock(block, (BlockContainer)result.Handler.Body);
}
}
}
@ -111,14 +150,21 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -111,14 +150,21 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
// Remove all assignments to the common object variable that stores the exception object.
if (result.ObjectVariableStore != null)
if (result.ObjectVariable != result.Handler.Variable)
{
foreach (var load in result.ObjectVariableStore.Variable.LoadInstructions.ToArray())
foreach (var load in result.ObjectVariable.LoadInstructions.ToArray())
{
if (load.Parent is CastClass cc && cc.Type == result.Handler.Variable.Type)
cc.ReplaceWith(new LdLoc(result.Handler.Variable));
if (!load.IsDescendantOf(result.Handler))
continue;
if (load.Parent is CastClass cc && cc.Type.Equals(result.Handler.Variable.Type))
{
cc.ReplaceWith(new LdLoc(result.Handler.Variable).WithILRange(cc).WithILRange(load));
}
else
load.ReplaceWith(new LdLoc(result.Handler.Variable));
{
load.ReplaceWith(new LdLoc(result.Handler.Variable).WithILRange(load));
}
}
}
@ -131,6 +177,23 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -131,6 +177,23 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
container.SortBlocks(deleteUnreachableBlocks: true);
}
private static void TransformAsyncThrowToThrow(ILTransformContext context, HashSet<Block> removedBlocks, Block block)
{
ILVariable v = null;
if (MatchExceptionCaptureBlock(context, block,
ref v, out StLoc typedExceptionVariableStore,
out Block captureBlock, out Block throwBlock))
{
context.Step($"ExceptionDispatchInfo.Capture({v.Name}).Throw() => throw;", typedExceptionVariableStore);
block.Instructions.RemoveRange(typedExceptionVariableStore.ChildIndex + 1, 2);
captureBlock.Remove();
throwBlock.Remove();
removedBlocks.Add(captureBlock);
removedBlocks.Add(throwBlock);
typedExceptionVariableStore.ReplaceWith(new Rethrow());
}
}
static void MoveBlock(Block block, BlockContainer target)
{
block.Remove();
@ -140,105 +203,282 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -140,105 +203,282 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// <summary>
/// Analyzes all catch handlers and returns every handler that follows the await catch handler pattern.
/// </summary>
static bool AnalyzeHandlers(InstructionCollection<TryCatchHandler> handlers, out ILVariable catchHandlerIdentifier, out List<(int Id, TryCatchHandler Handler, Block RealCatchBlockEntryPoint, ILInstruction NextBlockOrExitContainer, IfInstruction JumpTableEntry, StLoc ObjectVariableStore)> transformableCatchBlocks)
static bool AnalyzeHandlers(InstructionCollection<TryCatchHandler> handlers, out ILVariable catchHandlerIdentifier,
out List<CatchBlockInfo> transformableCatchBlocks)
{
transformableCatchBlocks = new List<(int Id, TryCatchHandler Handler, Block RealCatchBlockEntryPoint, ILInstruction NextBlockOrExitContainer, IfInstruction JumpTableEntry, StLoc ObjectVariableStore)>();
transformableCatchBlocks = new List<CatchBlockInfo>();
catchHandlerIdentifier = null;
foreach (var handler in handlers)
{
if (!MatchAwaitCatchHandler((BlockContainer)handler.Body, out int id, out var identifierVariable, out var realEntryPoint, out var nextBlockOrExitContainer, out var jumpTableEntry, out var objectVariableStore))
if (!MatchAwaitCatchHandler(handler, out int id, out var identifierVariable,
out var realEntryPoint, out var nextBlockOrExitContainer, out var jumpTableEntry,
out var objectVariable))
{
continue;
}
if (id < 1 || (catchHandlerIdentifier != null && identifierVariable != catchHandlerIdentifier))
{
continue;
}
catchHandlerIdentifier = identifierVariable;
transformableCatchBlocks.Add((id, handler, realEntryPoint, nextBlockOrExitContainer, jumpTableEntry, objectVariableStore));
transformableCatchBlocks.Add(new(id, handler, realEntryPoint, nextBlockOrExitContainer, jumpTableEntry, objectVariable ?? handler.Variable));
}
return transformableCatchBlocks.Count > 0;
}
/// <summary>
/// Matches the await catch handler pattern:
/// stloc V_3(ldloc E_100) - copy exception variable to a temporary
/// [stloc V_3(ldloc E_100) - copy exception variable to a temporary]
/// stloc V_6(ldloc V_3) - store exception in 'global' object variable
/// stloc V_5(ldc.i4 2) - store id of catch block in 'identifierVariable'
/// br IL_0075 - jump out of catch block to the head of the catch-handler jump table
/// </summary>
static bool MatchAwaitCatchHandler(BlockContainer container, out int id, out ILVariable identifierVariable, out Block realEntryPoint, out ILInstruction nextBlockOrExitContainer, out IfInstruction jumpTableEntry, out StLoc objectVariableStore)
static bool MatchAwaitCatchHandler(TryCatchHandler handler, out int id, out ILVariable identifierVariable,
out Block realEntryPoint, out ILInstruction nextBlockOrExitContainer,
out ILInstruction jumpTableEntry, out ILVariable objectVariable)
{
id = default(int);
id = 0;
identifierVariable = null;
realEntryPoint = null;
jumpTableEntry = null;
objectVariableStore = null;
objectVariable = null;
nextBlockOrExitContainer = null;
var catchBlock = container.EntryPoint;
if (catchBlock.Instructions.Count < 2 || catchBlock.Instructions.Count > 4)
return false;
if (!catchBlock.Instructions.Last().MatchBranch(out var jumpTableStartBlock))
return false;
if (catchBlock.Instructions.Count > 2 && catchBlock.Instructions[catchBlock.Instructions.Count - 3] is StLoc stloc)
var exceptionVariable = handler.Variable;
var catchBlock = ((BlockContainer)handler.Body).EntryPoint;
ILInstruction value;
switch (catchBlock.Instructions.Count)
{
objectVariableStore = stloc;
case 3:
if (!catchBlock.Instructions[0].MatchStLoc(out objectVariable, out value))
return false;
if (!value.MatchLdLoc(exceptionVariable))
return false;
break;
case 4:
if (!catchBlock.Instructions[0].MatchStLoc(out var temporaryVariable, out value))
return false;
if (!value.MatchLdLoc(exceptionVariable))
return false;
if (!catchBlock.Instructions[1].MatchStLoc(out objectVariable, out value))
return false;
if (!value.MatchLdLoc(temporaryVariable))
return false;
break;
default:
// if the exception variable is not used at all (e.g., catch (Exception))
// the "exception-variable-assignment" is omitted completely.
// This can happen in optimized code.
break;
}
if (!catchBlock.Instructions.Last().MatchBranch(out var jumpTableStartBlock))
return false;
var identifierVariableAssignment = catchBlock.Instructions.SecondToLastOrDefault();
if (!identifierVariableAssignment.MatchStLoc(out identifierVariable, out var value) || !value.MatchLdcI4(out id))
if (!identifierVariableAssignment.MatchStLoc(out identifierVariable, out value) || !value.MatchLdcI4(out id))
return false;
// analyze jump table:
// [stloc identifierVariableCopy(identifierVariable)]
// if (comp(identifierVariable == id)) br realEntryPoint
// br jumpTableEntryBlock
ILVariable identifierVariableCopy;
if (jumpTableStartBlock.Instructions.Count == 3)
switch (jumpTableStartBlock.Instructions.Count)
{
if (!jumpTableStartBlock.Instructions[0].MatchStLoc(out identifierVariableCopy, out var identifierVariableLoad) || !identifierVariableLoad.MatchLdLoc(identifierVariable))
case 3:
// stloc identifierVariableCopy(identifierVariable)
// if (comp(identifierVariable == id)) br realEntryPoint
// br jumpTableEntryBlock
if (!jumpTableStartBlock.Instructions[0].MatchStLoc(out var identifierVariableCopy, out var identifierVariableLoad)
|| !identifierVariableLoad.MatchLdLoc(identifierVariable))
{
return false;
}
return ParseIfJumpTable(id, jumpTableStartBlock, identifierVariableCopy, out realEntryPoint, out nextBlockOrExitContainer, out jumpTableEntry);
case 2:
// if (comp(identifierVariable == id)) br realEntryPoint
// br jumpTableEntryBlock
return ParseIfJumpTable(id, jumpTableStartBlock, identifierVariable, out realEntryPoint, out nextBlockOrExitContainer, out jumpTableEntry);
case 1:
if (jumpTableStartBlock.Instructions[0] is not SwitchInstruction switchInst)
{
return false;
}
return ParseSwitchJumpTable(id, switchInst, identifierVariable, out realEntryPoint, out nextBlockOrExitContainer, out jumpTableEntry);
default:
return false;
}
else if (jumpTableStartBlock.Instructions.Count == 2)
bool ParseSwitchJumpTable(int id, SwitchInstruction jumpTable, ILVariable identifierVariable, out Block realEntryPoint, out ILInstruction nextBlockOrExitContainer, out ILInstruction jumpTableEntry)
{
identifierVariableCopy = identifierVariable;
}
else
realEntryPoint = null;
nextBlockOrExitContainer = null;
jumpTableEntry = null;
if (!jumpTable.Value.MatchLdLoc(identifierVariable))
return false;
var defaultSection = jumpTable.GetDefaultSection();
foreach (var section in jumpTable.Sections)
{
if (!section.Labels.Contains(id))
continue;
if (!section.Body.MatchBranch(out realEntryPoint))
return false;
if (defaultSection.Body.MatchBranch(out var t))
nextBlockOrExitContainer = t;
else if (defaultSection.Body.MatchLeave(out var t2))
nextBlockOrExitContainer = t2;
jumpTableEntry = section;
return true;
}
return false;
var jumpTableEntryBlock = jumpTableStartBlock;
do
}
bool ParseIfJumpTable(int id, Block jumpTableEntryBlock, ILVariable identifierVariable, out Block realEntryPoint, out ILInstruction nextBlockOrExitContainer, out ILInstruction jumpTableEntry)
{
if (!(jumpTableEntryBlock.Instructions.SecondToLastOrDefault() is IfInstruction ifInst))
return false;
ILInstruction lastInst = jumpTableEntryBlock.Instructions.Last();
if (ifInst.Condition.MatchCompEquals(out var left, out var right))
realEntryPoint = null;
nextBlockOrExitContainer = null;
jumpTableEntry = null;
do
{
if (!ifInst.TrueInst.MatchBranch(out realEntryPoint))
if (!(jumpTableEntryBlock.Instructions.SecondToLastOrDefault() is IfInstruction ifInst))
return false;
if (!lastInst.MatchBranch(out jumpTableEntryBlock))
ILInstruction lastInst = jumpTableEntryBlock.Instructions.Last();
if (ifInst.Condition.MatchCompEquals(out var left, out var right))
{
if (!lastInst.MatchLeave((BlockContainer)lastInst.Parent.Parent))
if (!ifInst.TrueInst.MatchBranch(out realEntryPoint))
return false;
if (!lastInst.MatchBranch(out jumpTableEntryBlock))
{
if (!lastInst.MatchLeave((BlockContainer)lastInst.Parent.Parent))
return false;
}
}
}
else if (ifInst.Condition.MatchCompNotEquals(out left, out right))
{
if (!lastInst.MatchBranch(out realEntryPoint))
return false;
if (!ifInst.TrueInst.MatchBranch(out jumpTableEntryBlock))
else if (ifInst.Condition.MatchCompNotEquals(out left, out right))
{
if (!ifInst.TrueInst.MatchLeave((BlockContainer)lastInst.Parent.Parent))
if (!lastInst.MatchBranch(out realEntryPoint))
return false;
if (!ifInst.TrueInst.MatchBranch(out jumpTableEntryBlock))
{
if (!ifInst.TrueInst.MatchLeave((BlockContainer)lastInst.Parent.Parent))
return false;
}
}
}
else
{
else
{
return false;
}
if (!left.MatchLdLoc(identifierVariable))
return false;
if (right.MatchLdcI4(id))
{
nextBlockOrExitContainer = jumpTableEntryBlock ?? lastInst.Parent.Parent;
jumpTableEntry = ifInst;
return true;
}
} while (jumpTableEntryBlock?.Instructions.Count == 2);
return false;
}
}
// Block beforeThrowBlock {
// [before throw]
// stloc typedExceptionVariable(isinst System.Exception(ldloc objectVariable))
// if (comp.o(ldloc typedExceptionVariable != ldnull)) br captureBlock
// br throwBlock
// }
//
// Block throwBlock {
// throw(ldloc objectVariable)
// }
//
// Block captureBlock {
// callvirt Throw(call Capture(ldloc typedExceptionVariable))
// br nextBlock
// }
// =>
// throw(ldloc result.Handler.Variable)
internal static bool MatchExceptionCaptureBlock(ILTransformContext context, Block block,
ref ILVariable objectVariable, out StLoc typedExceptionVariableStore, out Block captureBlock, out Block throwBlock)
{
bool DerivesFromException(IType t) => t.GetAllBaseTypes().Any(ty => ty.IsKnownType(KnownTypeCode.Exception));
captureBlock = null;
throwBlock = null;
typedExceptionVariableStore = null;
var typedExceptionVariableStLoc = block.Instructions.ElementAtOrDefault(block.Instructions.Count - 3) as StLoc;
if (typedExceptionVariableStLoc == null
|| !typedExceptionVariableStLoc.Value.MatchIsInst(out var arg, out var type)
|| !DerivesFromException(type)
|| !arg.MatchLdLoc(out var v))
{
return false;
}
if (objectVariable == null)
{
objectVariable = v;
}
else if (!objectVariable.Equals(v))
{
return false;
}
typedExceptionVariableStore = typedExceptionVariableStLoc;
if (!block.Instructions[block.Instructions.Count - 2].MatchIfInstruction(out var condition, out var trueInst))
return false;
ILInstruction lastInstr = block.Instructions.Last();
if (!lastInstr.MatchBranch(out throwBlock))
return false;
if (condition.MatchCompNotEqualsNull(out arg)
&& trueInst is Branch branchToCapture)
{
if (!arg.MatchLdLoc(typedExceptionVariableStore.Variable))
return false;
}
if (!left.MatchLdLoc(identifierVariableCopy))
captureBlock = branchToCapture.TargetBlock;
}
else
{
return false;
}
if (throwBlock.IncomingEdgeCount != 1
|| throwBlock.Instructions.Count != 1
|| !(throwBlock.Instructions[0].MatchThrow(out var ov) && ov.MatchLdLoc(objectVariable)))
{
return false;
}
if (captureBlock.IncomingEdgeCount != 1
|| captureBlock.Instructions.Count != 2
|| !MatchCaptureThrowCalls(captureBlock.Instructions[0]))
{
return false;
}
return true;
bool MatchCaptureThrowCalls(ILInstruction inst)
{
var exceptionDispatchInfoType = context.TypeSystem.FindType(typeof(System.Runtime.ExceptionServices.ExceptionDispatchInfo));
if (inst is not CallVirt callVirt || callVirt.Arguments.Count != 1)
return false;
if (right.MatchLdcI4(id))
if (callVirt.Arguments[0] is not Call call || call.Arguments.Count != 1
|| !call.Arguments[0].MatchLdLoc(typedExceptionVariableStLoc.Variable))
{
nextBlockOrExitContainer = jumpTableEntryBlock ?? lastInst.Parent.Parent;
jumpTableEntry = ifInst;
return true;
return false;
}
} while (jumpTableEntryBlock?.Instructions.Count == 2);
return false;
return callVirt.Method.Name == "Throw"
&& callVirt.Method.DeclaringType.Equals(exceptionDispatchInfoType)
&& call.Method.Name == "Capture"
&& call.Method.DeclaringType.Equals(exceptionDispatchInfoType);
}
}
}
}

427
ICSharpCode.Decompiler/IL/ControlFlow/AwaitInFinallyTransform.cs

@ -38,218 +38,303 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -38,218 +38,303 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
{
if (!(tryCatch.Parent?.Parent is BlockContainer container))
continue;
// } catch exceptionVariable : 02000078 System.Object when (ldc.i4 1) BlockContainer {
// Block IL_004a (incoming: 1) {
// stloc objectVariable(ldloc exceptionVariable)
// br finallyBlock
// }
//
// }
// }
//
// Block finallyBlock (incoming: 2) {
// if (comp.o(ldloc b == ldnull)) br afterFinallyBlock
// br finallyBlockContinuation
// }
//
// Block finallyBlockContinuation (incoming: 1) {
// await(addressof System.Threading.Tasks.ValueTask(callvirt DisposeAsync(ldloc b)))
// br afterFinallyBlock
// }
//
// Block afterFinallyBlock (incoming: 2) {
// stloc V_1(ldloc objectVariable)
// if (comp.o(ldloc V_1 == ldnull)) br IL_00ea
// br IL_00cf
// }
// await in finally uses a single catch block with catch-type object
if (tryCatch.Handlers.Count != 1 || !(tryCatch.Handlers[0].Body is BlockContainer catchBlockContainer) || !tryCatch.Handlers[0].Variable.Type.IsKnownType(KnownTypeCode.Object))
if (tryCatch.Handlers.Count != 1)
{
continue;
}
var handler = tryCatch.Handlers[0];
var exceptionVariable = handler.Variable;
if (handler.Body is not BlockContainer catchBlockContainer)
continue;
// and consists of an assignment to a temporary that is used outside the catch block
// and a jump to the finally block
var block = catchBlockContainer.EntryPoint;
if (block.Instructions.Count < 2 || !block.Instructions[0].MatchStLoc(out var globalCopyVar, out var value) || !value.MatchLdLoc(tryCatch.Handlers[0].Variable))
if (!exceptionVariable.Type.IsKnownType(KnownTypeCode.Object))
continue;
if (block.Instructions.Count == 3)
// Matches the await finally pattern:
// [stloc V_3(ldloc E_100) - copy exception variable to a temporary]
// stloc V_6(ldloc V_3) - store exception in 'global' object variable
// br IL_0075 - jump out of catch block to the head of the finallyBlock
var catchBlockEntry = catchBlockContainer.EntryPoint;
ILVariable objectVariable;
switch (catchBlockEntry.Instructions.Count)
{
if (!block.Instructions[1].MatchStLoc(out var globalCopyVarTemp, out value) || !value.MatchLdLoc(globalCopyVar))
case 2:
if (!catchBlockEntry.Instructions[0].MatchStLoc(out objectVariable, out var value))
continue;
if (!value.MatchLdLoc(exceptionVariable))
continue;
break;
case 3:
if (!catchBlockEntry.Instructions[0].MatchStLoc(out var temporaryVariable, out value))
continue;
if (!value.MatchLdLoc(exceptionVariable))
continue;
if (!catchBlockEntry.Instructions[1].MatchStLoc(out objectVariable, out value))
continue;
if (!value.MatchLdLoc(temporaryVariable))
continue;
break;
default:
continue;
globalCopyVar = globalCopyVarTemp;
}
if (!block.Instructions[block.Instructions.Count - 1].MatchBranch(out var entryPointOfFinally))
if (!catchBlockEntry.Instructions[catchBlockEntry.Instructions.Count - 1].MatchBranch(out var entryPointOfFinally))
continue;
// globalCopyVar should only be used once, at the end of the finally-block
if (globalCopyVar.LoadCount != 1 || globalCopyVar.StoreCount > 2)
if (objectVariable.LoadCount != 1 || objectVariable.StoreCount > 2)
continue;
var tempStore = globalCopyVar.LoadInstructions[0].Parent as StLoc;
if (tempStore == null || !MatchExceptionCaptureBlock(tempStore, out var exitOfFinally, out var afterFinally, out var blocksToRemove))
var beforeExceptionCaptureBlock = (Block)LocalFunctionDecompiler.GetStatement(objectVariable.LoadInstructions[0])?.Parent;
if (beforeExceptionCaptureBlock == null)
continue;
if (!MatchAfterFinallyBlock(ref afterFinally, blocksToRemove, out bool removeFirstInstructionInAfterFinally))
var (afterFinallyBlock, capturePatternStart, objectVariableCopy) = FindBlockAfterFinally(context, beforeExceptionCaptureBlock, objectVariable);
if (afterFinallyBlock == null || capturePatternStart == null)
continue;
var initOfIdentifierVariable = tryCatch.Parent.Children.ElementAtOrDefault(tryCatch.ChildIndex - 1) as StLoc;
if (initOfIdentifierVariable == null || !initOfIdentifierVariable.Value.MatchLdcI4(0))
continue;
var cfg = new ControlFlowGraph(container, context.CancellationToken);
var exitOfFinallyNode = cfg.GetNode(exitOfFinally);
var entryPointOfFinallyNode = cfg.GetNode(entryPointOfFinally);
var additionalBlocksInFinally = new HashSet<Block>();
var invalidExits = new List<ControlFlowNode>();
var identifierVariable = initOfIdentifierVariable.Variable;
context.StepStartGroup("Inline finally block with await", tryCatch.Handlers[0]);
var cfg = new ControlFlowGraph(container, context.CancellationToken);
changedContainers.Add(container);
TraverseDominatorTree(entryPointOfFinallyNode);
context.StepStartGroup("Move blocks to state assignments");
Dictionary<int, Block> identifierValueTargets = new Dictionary<int, Block>();
void TraverseDominatorTree(ControlFlowNode node)
foreach (var load in identifierVariable.LoadInstructions.ToArray())
{
if (entryPointOfFinallyNode != node)
var statement = LocalFunctionDecompiler.GetStatement(load);
var block = (Block)statement.Parent;
if (!statement.MatchIfInstruction(out var cond, out var branchToTarget))
{
if (entryPointOfFinallyNode.Dominates(node))
additionalBlocksInFinally.Add((Block)node.UserData);
else
invalidExits.Add(node);
block.Instructions.RemoveAt(statement.ChildIndex);
continue;
}
if (node == exitOfFinallyNode)
return;
if (block.Instructions.LastOrDefault() is not Branch otherBranch)
{
block.Instructions.RemoveAt(statement.ChildIndex);
continue;
}
foreach (var child in node.DominatorTreeChildren)
if (cond.MatchCompEquals(out var left, out var right)
&& left == load && right.MatchLdcI4(out int value)
&& branchToTarget.MatchBranch(out var targetBlock))
{
TraverseDominatorTree(child);
identifierValueTargets.Add(value, targetBlock);
block.Instructions.RemoveAt(statement.ChildIndex);
}
else if (cond.MatchCompNotEquals(out left, out right)
&& left == load && right.MatchLdcI4(out value))
{
identifierValueTargets.Add(value, otherBranch.TargetBlock);
block.Instructions.RemoveAt(statement.ChildIndex + 1);
statement.ReplaceWith(otherBranch);
}
else
{
block.Instructions.RemoveAt(statement.ChildIndex);
}
}
if (invalidExits.Any())
continue;
var removedBlocks = new List<Block>();
context.Step("Inline finally block with await", tryCatch.Handlers[0]);
foreach (var blockToRemove in blocksToRemove)
foreach (var store in identifierVariable.StoreInstructions.OfType<StLoc>().ToArray())
{
blockToRemove.Remove();
if (!store.Value.MatchLdcI4(out int value))
continue;
var statement = LocalFunctionDecompiler.GetStatement(store);
var parent = (Block)statement.Parent;
if (value <= 0)
{
parent.Instructions.RemoveAt(statement.ChildIndex);
continue;
}
if (!identifierValueTargets.TryGetValue(value, out var targetBlock))
{
store.ReplaceWith(new Nop() { Comment = $"Could not find matching block for id {value}" });
continue;
}
var targetContainer = BlockContainer.FindClosestContainer(statement);
context.Step($"Move block with id={value} {targetBlock.Label} to IL_{store.StartILOffset}", statement);
parent.Instructions.RemoveAt(statement.ChildIndex + 1);
store.ReplaceWith(new Branch(targetBlock));
MoveDominatedBlocksToContainer(targetBlock, null, cfg, targetContainer, removedBlocks);
}
var finallyContainer = new BlockContainer();
entryPointOfFinally.Remove();
if (removeFirstInstructionInAfterFinally)
afterFinally.Instructions.RemoveAt(0);
changedContainers.Add(container);
var outer = BlockContainer.FindClosestContainer(container.Parent);
if (outer != null)
changedContainers.Add(outer);
finallyContainer.Blocks.Add(entryPointOfFinally);
finallyContainer.AddILRange(entryPointOfFinally);
exitOfFinally.Instructions.RemoveRange(tempStore.ChildIndex, 3);
exitOfFinally.Instructions.Add(new Leave(finallyContainer));
foreach (var branchToFinally in container.Descendants.OfType<Branch>())
context.StepEndGroup(keepIfEmpty: true);
var finallyContainer = new BlockContainer().WithILRange(catchBlockContainer);
tryCatch.ReplaceWith(new TryFinally(tryCatch.TryBlock, finallyContainer).WithILRange(tryCatch.TryBlock));
context.Step("Move blocks into finally", finallyContainer);
MoveDominatedBlocksToContainer(entryPointOfFinally, beforeExceptionCaptureBlock, cfg, finallyContainer, removedBlocks);
if (beforeExceptionCaptureBlock.Instructions.Count >= 3)
{
if (branchToFinally.TargetBlock == entryPointOfFinally)
branchToFinally.ReplaceWith(new Branch(afterFinally));
if (beforeExceptionCaptureBlock.Instructions.SecondToLastOrDefault().MatchIfInstruction(out var cond, out var brInst)
&& beforeExceptionCaptureBlock.Instructions.LastOrDefault() is Branch branch
&& beforeExceptionCaptureBlock.Instructions[beforeExceptionCaptureBlock.Instructions.Count - 3].MatchStLoc(objectVariableCopy, out var value)
&& value.MatchLdLoc(objectVariable))
{
if (cond.MatchCompEqualsNull(out var arg) && arg.MatchLdLoc(objectVariableCopy))
{
context.Step("Simplify end of finally", beforeExceptionCaptureBlock);
beforeExceptionCaptureBlock.Instructions.RemoveRange(beforeExceptionCaptureBlock.Instructions.Count - 3, 2);
branch.ReplaceWith(new Leave(finallyContainer).WithILRange(branch));
}
else if (cond.MatchCompNotEqualsNull(out arg) && arg.MatchLdLoc(objectVariableCopy))
{
context.Step("Simplify end of finally", beforeExceptionCaptureBlock);
beforeExceptionCaptureBlock.Instructions.RemoveRange(beforeExceptionCaptureBlock.Instructions.Count - 3, 2);
branch.ReplaceWith(new Leave(finallyContainer).WithILRange(branch));
}
}
}
foreach (var newBlock in additionalBlocksInFinally)
foreach (var branch in container.Descendants.OfType<Branch>())
{
newBlock.Remove();
finallyContainer.Blocks.Add(newBlock);
finallyContainer.AddILRange(newBlock);
if (branch.TargetBlock == entryPointOfFinally && branch.IsDescendantOf(tryCatch.TryBlock))
{
context.Step("branch to finally => branch after finally", branch);
branch.ReplaceWith(new Branch(afterFinallyBlock).WithILRange(branch));
}
else if (branch.TargetBlock == capturePatternStart)
{
if (branch.IsDescendantOf(finallyContainer))
{
context.Step("branch out of finally container => leave finally container", branch);
branch.ReplaceWith(new Leave(finallyContainer).WithILRange(branch));
}
}
}
tryCatch.ReplaceWith(new TryFinally(tryCatch.TryBlock, finallyContainer).WithILRange(tryCatch.TryBlock));
context.StepEndGroup(keepIfEmpty: true);
}
context.Step("Clean up", function);
// clean up all modified containers
foreach (var container in changedContainers)
container.SortBlocks(deleteUnreachableBlocks: true);
}
/// <summary>
/// Block finallyHead (incoming: 2) {
/// [body of finally]
/// stloc V_4(ldloc V_1)
/// if (comp(ldloc V_4 == ldnull)) br afterFinally
/// br typeCheckBlock
/// }
///
/// Block typeCheckBlock (incoming: 1) {
/// stloc S_110(isinst System.Exception(ldloc V_4))
/// if (comp(ldloc S_110 != ldnull)) br captureBlock
/// br throwBlock
/// }
///
/// Block throwBlock (incoming: 1) {
/// throw(ldloc V_4)
/// }
///
/// Block captureBlock (incoming: 1) {
/// callvirt Throw(call Capture(ldloc S_110))
/// br afterFinally
/// }
///
/// Block afterFinally (incoming: 2) {
/// stloc V_1(ldnull)
/// [after finally]
/// }
/// </summary>
static bool MatchExceptionCaptureBlock(StLoc tempStore, out Block endOfFinally, out Block afterFinally, out List<Block> blocksToRemove)
{
afterFinally = null;
endOfFinally = (Block)tempStore.Parent;
blocksToRemove = new List<Block>();
int count = endOfFinally.Instructions.Count;
if (tempStore.ChildIndex != count - 3)
return false;
if (!(endOfFinally.Instructions[count - 2] is IfInstruction ifInst))
return false;
if (!endOfFinally.Instructions.Last().MatchBranch(out var typeCheckBlock))
return false;
if (!ifInst.TrueInst.MatchBranch(out afterFinally))
return false;
// match typeCheckBlock
if (typeCheckBlock.Instructions.Count != 3)
return false;
if (!typeCheckBlock.Instructions[0].MatchStLoc(out var castStore, out var cast)
|| !cast.MatchIsInst(out var arg, out var type) || !type.IsKnownType(KnownTypeCode.Exception) || !arg.MatchLdLoc(tempStore.Variable))
return false;
if (!typeCheckBlock.Instructions[1].MatchIfInstruction(out var cond, out var jumpToCaptureBlock))
return false;
if (!cond.MatchCompNotEqualsNull(out arg) || !arg.MatchLdLoc(castStore))
return false;
if (!typeCheckBlock.Instructions[2].MatchBranch(out var throwBlock))
return false;
if (!jumpToCaptureBlock.MatchBranch(out var captureBlock))
return false;
// match throwBlock
if (throwBlock.Instructions.Count != 1 || !throwBlock.Instructions[0].MatchThrow(out arg) || !arg.MatchLdLoc(tempStore.Variable))
return false;
// match captureBlock
if (captureBlock.Instructions.Count != 2)
return false;
if (!captureBlock.Instructions[1].MatchBranch(afterFinally))
return false;
if (!(captureBlock.Instructions[0] is CallVirt callVirt) || callVirt.Method.FullName != "System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw" || callVirt.Arguments.Count != 1)
return false;
if (!(callVirt.Arguments[0] is Call call) || call.Method.FullName != "System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture" || call.Arguments.Count != 1)
return false;
if (!call.Arguments[0].MatchLdLoc(castStore))
return false;
blocksToRemove.Add(typeCheckBlock);
blocksToRemove.Add(throwBlock);
blocksToRemove.Add(captureBlock);
return true;
}
((BlockContainer)function.Body).SortBlocks(deleteUnreachableBlocks: true);
static bool MatchAfterFinallyBlock(ref Block afterFinally, List<Block> blocksToRemove, out bool removeFirstInstructionInAfterFinally)
{
removeFirstInstructionInAfterFinally = false;
if (afterFinally.Instructions.Count < 2)
return false;
ILVariable globalCopyVarSplitted;
switch (afterFinally.Instructions[0])
void MoveDominatedBlocksToContainer(Block newEntryPoint, Block endBlock, ControlFlowGraph graph,
BlockContainer targetContainer, List<Block> removedBlocks)
{
case IfInstruction ifInst:
if (ifInst.Condition.MatchCompEquals(out var load, out var ldone) && ldone.MatchLdcI4(1) && load.MatchLdLoc(out var variable))
{
if (!ifInst.TrueInst.MatchBranch(out var targetBlock))
return false;
blocksToRemove.Add(afterFinally);
afterFinally = targetBlock;
return true;
}
else if (ifInst.Condition.MatchCompNotEquals(out load, out ldone) && ldone.MatchLdcI4(1) && load.MatchLdLoc(out variable))
var node = graph.GetNode(newEntryPoint);
var endNode = endBlock == null ? null : graph.GetNode(endBlock);
MoveBlock(newEntryPoint, targetContainer);
foreach (var n in graph.cfg)
{
Block block = (Block)n.UserData;
if (node.Dominates(n))
{
if (!afterFinally.Instructions[1].MatchBranch(out var targetBlock))
return false;
blocksToRemove.Add(afterFinally);
afterFinally = targetBlock;
return true;
if (endNode != null && endNode != n && endNode.Dominates(n))
continue;
if (block.Parent == targetContainer)
continue;
if (!removedBlocks.Contains(block))
{
MoveBlock(block, targetContainer);
}
}
return false;
case LdLoc ldLoc:
if (ldLoc.Variable.LoadCount != 1 || ldLoc.Variable.StoreCount != 1)
return false;
if (!afterFinally.Instructions[1].MatchStLoc(out globalCopyVarSplitted, out var ldnull) || !ldnull.MatchLdNull())
return false;
removeFirstInstructionInAfterFinally = true;
break;
case StLoc stloc:
globalCopyVarSplitted = stloc.Variable;
if (!stloc.Value.MatchLdNull())
return false;
break;
default:
return false;
}
}
void MoveBlock(Block block, BlockContainer target)
{
context.Step($"Move {block.Label} to container at IL_{target.StartILOffset:x4}", target);
block.Remove();
target.Blocks.Add(block);
}
if (globalCopyVarSplitted.StoreCount != 1 || globalCopyVarSplitted.LoadCount != 0)
return false;
return true;
}
static (Block, Block, ILVariable) FindBlockAfterFinally(ILTransformContext context, Block block, ILVariable objectVariable)
{
int count = block.Instructions.Count;
if (count < 3)
return default;
if (!block.Instructions[count - 3].MatchStLoc(out var objectVariableCopy, out var value))
return default;
if (!value.MatchLdLoc(objectVariable))
return default;
if (!block.Instructions[count - 2].MatchIfInstruction(out var cond, out var afterExceptionCaptureBlockBranch))
return default;
if (!afterExceptionCaptureBlockBranch.MatchBranch(out var afterExceptionCaptureBlock))
return default;
if (!block.Instructions[count - 1].MatchBranch(out var exceptionCaptureBlock))
return default;
if (cond.MatchCompEqualsNull(out var arg))
{
if (!arg.MatchLdLoc(objectVariableCopy))
return default;
}
else if (cond.MatchCompNotEqualsNull(out arg))
{
if (!arg.MatchLdLoc(objectVariableCopy))
return default;
(afterExceptionCaptureBlock, exceptionCaptureBlock) = (exceptionCaptureBlock, afterExceptionCaptureBlock);
}
else
{
return default;
}
if (!AwaitInCatchTransform.MatchExceptionCaptureBlock(context, exceptionCaptureBlock,
ref objectVariableCopy, out var store, out _, out _))
{
return default;
}
var exceptionCaptureBlockStart = LocalFunctionDecompiler.GetStatement(store);
if (exceptionCaptureBlockStart == null)
return default;
return (afterExceptionCaptureBlock, (Block)exceptionCaptureBlockStart.Parent, objectVariableCopy);
}
}
}

86
ICSharpCode.Decompiler/IL/Transforms/DetectCatchWhenConditionBlocks.cs

@ -17,6 +17,8 @@ @@ -17,6 +17,8 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.Decompiler.TypeSystem;
@ -52,6 +54,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -52,6 +54,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// stloc temp(isinst exceptionType(ldloc exceptionVar))
// if (comp(ldloc temp != ldnull)) br whenConditionBlock
// br falseBlock
context.Step($"Detected catch-when for {catchBlock.Variable.Name} (extra store)", instructions[0]);
((StLoc)instructions[0]).Value = exceptionSlot;
instructions[1].ReplaceWith(new Branch(whenConditionBlock));
instructions.RemoveAt(2);
@ -61,12 +64,95 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -61,12 +64,95 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
// if (comp(isinst exceptionType(ldloc exceptionVar) != ldnull)) br whenConditionBlock
// br falseBlock
context.Step($"Detected catch-when for {catchBlock.Variable.Name}", instructions[0]);
instructions[0].ReplaceWith(new Branch(whenConditionBlock));
instructions.RemoveAt(1);
container.SortBlocks(deleteUnreachableBlocks: true);
}
PropagateExceptionVariable(context, catchBlock);
}
}
}
/// <summary>
/// catch E_189 : 0200007C System.Exception when (BlockContainer {
/// Block IL_0079 (incoming: 1) {
/// stloc S_30(ldloc E_189)
/// br IL_0085
/// }
///
/// Block IL_0085 (incoming: 1) {
/// stloc I_1(ldloc S_30)
/// where S_30 and I_1 are single definition
/// =>
/// copy-propagate E_189 to replace all uses of S_30 and I_1
/// </summary>
static void PropagateExceptionVariable(ILTransformContext context, TryCatchHandler handler)
{
var exceptionVariable = handler.Variable;
if (!exceptionVariable.IsSingleDefinition)
return;
context.StepStartGroup(nameof(PropagateExceptionVariable));
int i = 0;
while (i < exceptionVariable.LoadInstructions.Count)
{
var load = exceptionVariable.LoadInstructions[i];
if (!load.IsDescendantOf(handler))
{
i++;
continue;
}
// We are only interested in store "statements" copying the exception variable
// without modifying it.
var statement = LocalFunctionDecompiler.GetStatement(load);
if (!(statement is StLoc stloc))
{
i++;
continue;
}
// simple copy case:
// stloc b(ldloc a)
if (stloc.Value == load)
{
PropagateExceptionInstance(stloc);
}
// if the type of the cast-class instruction matches the exceptionType,
// this cast can be removed without losing any side-effects.
// Note: this would also hold true iff exceptionType were an exception derived
// from cc.Type, however, we are more restrictive to match the pattern exactly.
// stloc b(castclass exceptionType(ldloc a))
else if (stloc.Value is CastClass cc && cc.Type.Equals(exceptionVariable.Type) && cc.Argument == load)
{
stloc.Value = load;
PropagateExceptionInstance(stloc);
}
else
{
i++;
}
void PropagateExceptionInstance(StLoc store)
{
foreach (var load in store.Variable.LoadInstructions.ToArray())
{
if (!load.IsDescendantOf(handler))
continue;
load.ReplaceWith(new LdLoc(exceptionVariable).WithILRange(load));
}
if (store.Variable.LoadCount == 0 && store.Parent is Block block)
{
block.Instructions.RemoveAt(store.ChildIndex);
}
else
{
i++;
}
}
}
context.StepEndGroup(keepIfEmpty: false);
}
/// <summary>

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

@ -892,7 +892,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -892,7 +892,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
void TransformCatchVariable(TryCatchHandler handler, Block entryPoint, bool isCatchBlock)
{
if (!handler.Variable.IsSingleDefinition || handler.Variable.LoadCount != 1)
return; // handle.Variable already has non-trivial uses
return; // handler.Variable already has non-trivial uses
if (!entryPoint.Instructions[0].MatchStLoc(out var exceptionVar, out var exceptionSlotLoad))
{
// Not the pattern with a second exceptionVar.

Loading…
Cancel
Save