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

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

@ -28,6 +28,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{ {
private int memberField; private int memberField;
private static bool True()
{
return true;
}
public async void SimpleVoidMethod() public async void SimpleVoidMethod()
{ {
Console.WriteLine("Before"); Console.WriteLine("Before");
@ -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 #endif
public static async Task<int> GetIntegerSumAsync(IEnumerable<int> items) 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
int sum = 0; int sum = 0;
await foreach (int item in items.WithCancellation(token)) await foreach (int item in items.WithCancellation(token))
{ {
if (token.IsCancellationRequested) if (!token.IsCancellationRequested)
{ {
break;
}
sum += item; sum += item;
continue;
}
break;
} }
return sum; return sum;
} }

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

@ -448,6 +448,32 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Console.WriteLine("{0} {1}", val, val.ToString()); 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 #endif
} }
} }

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

@ -17,7 +17,7 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
//using System.Threading.Tasks; using System.Threading.Tasks;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty 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
Use(disposable2); Use(disposable2);
} }
//public async Task SimpleUsingVarAsync(IAsyncDisposable other) public async Task SimpleUsingVarAsync()
//{ {
// Console.WriteLine("before using"); Console.WriteLine("before using");
// await using IAsyncDisposable asyncDisposable = GetAsyncDisposable(); await using IAsyncDisposable asyncDisposable = GetAsyncDisposable();
// Console.WriteLine("inside using"); Console.WriteLine("inside using");
// Use(asyncDisposable); Use(asyncDisposable);
//} }
} }
} }

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

@ -18,19 +18,43 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.IL.Transforms; using ICSharpCode.Decompiler.IL.Transforms;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL.ControlFlow namespace ICSharpCode.Decompiler.IL.ControlFlow
{ {
class AwaitInCatchTransform 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) public static void Run(ILFunction function, ILTransformContext context)
{ {
if (!context.Settings.AwaitInCatchFinally) if (!context.Settings.AwaitInCatchFinally)
return; return;
HashSet<BlockContainer> changedContainers = new HashSet<BlockContainer>(); HashSet<BlockContainer> changedContainers = new HashSet<BlockContainer>();
HashSet<Block> removedBlocks = new HashSet<Block>();
// analyze all try-catch statements in the function // analyze all try-catch statements in the function
foreach (var tryCatch in function.Descendants.OfType<TryCatch>().ToArray()) foreach (var tryCatch in function.Descendants.OfType<TryCatch>().ToArray())
@ -44,12 +68,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
changedContainers.Add(container); changedContainers.Add(container);
foreach (var result in transformableCatchBlocks) foreach (var result in transformableCatchBlocks)
{ {
removedBlocks.Clear();
var node = cfg.GetNode(result.RealCatchBlockEntryPoint); 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. // Remove the IfInstruction from the jump table and eliminate all branches to the block.
var jumpTableBlock = (Block)result.JumpTableEntry.Parent; if (result.JumpTableEntry is IfInstruction jumpTableEntry)
{
var jumpTableBlock = (Block)jumpTableEntry.Parent;
context.Step("Remove jump-table entry", result.JumpTableEntry); context.Step("Remove jump-table entry", result.JumpTableEntry);
jumpTableBlock.Instructions.RemoveAt(result.JumpTableEntry.ChildIndex); jumpTableBlock.Instructions.RemoveAt(result.JumpTableEntry.ChildIndex);
@ -69,6 +96,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
} }
} }
} }
}
// Add the real catch block entry-point to the block container // Add the real catch block entry-point to the block container
var catchBlockHead = ((BlockContainer)result.Handler.Body).Blocks.Last(); var catchBlockHead = ((BlockContainer)result.Handler.Body).Blocks.Last();
@ -79,14 +107,25 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// Remove the generated catch block // Remove the generated catch block
catchBlockHead.Remove(); catchBlockHead.Remove();
TransformAsyncThrowToThrow(context, removedBlocks, result.RealCatchBlockEntryPoint);
// Inline all blocks that are dominated by the entrypoint of the real catch block // Inline all blocks that are dominated by the entrypoint of the real catch block
foreach (var n in cfg.cfg) foreach (var n in cfg.cfg)
{ {
if (((Block)n.UserData).Parent == result.Handler.Body) Block block = (Block)n.UserData;
continue;
if (node.Dominates(n)) 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
} }
// Remove all assignments to the common object variable that stores the exception object. // 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) if (!load.IsDescendantOf(result.Handler))
cc.ReplaceWith(new LdLoc(result.Handler.Variable)); 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 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
container.SortBlocks(deleteUnreachableBlocks: true); 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) static void MoveBlock(Block block, BlockContainer target)
{ {
block.Remove(); block.Remove();
@ -140,66 +203,141 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// <summary> /// <summary>
/// Analyzes all catch handlers and returns every handler that follows the await catch handler pattern. /// Analyzes all catch handlers and returns every handler that follows the await catch handler pattern.
/// </summary> /// </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; catchHandlerIdentifier = null;
foreach (var handler in handlers) 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; continue;
}
if (id < 1 || (catchHandlerIdentifier != null && identifierVariable != catchHandlerIdentifier)) if (id < 1 || (catchHandlerIdentifier != null && identifierVariable != catchHandlerIdentifier))
{
continue; continue;
}
catchHandlerIdentifier = identifierVariable; 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; return transformableCatchBlocks.Count > 0;
} }
/// <summary> /// <summary>
/// Matches the await catch handler pattern: /// 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_6(ldloc V_3) - store exception in 'global' object variable
/// stloc V_5(ldc.i4 2) - store id of catch block in 'identifierVariable' /// 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 /// br IL_0075 - jump out of catch block to the head of the catch-handler jump table
/// </summary> /// </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; identifierVariable = null;
realEntryPoint = null; realEntryPoint = null;
jumpTableEntry = null; jumpTableEntry = null;
objectVariableStore = null; objectVariable = null;
nextBlockOrExitContainer = null; nextBlockOrExitContainer = null;
var catchBlock = container.EntryPoint; var exceptionVariable = handler.Variable;
if (catchBlock.Instructions.Count < 2 || catchBlock.Instructions.Count > 4) var catchBlock = ((BlockContainer)handler.Body).EntryPoint;
ILInstruction value;
switch (catchBlock.Instructions.Count)
{
case 3:
if (!catchBlock.Instructions[0].MatchStLoc(out objectVariable, out value))
return false; return false;
if (!catchBlock.Instructions.Last().MatchBranch(out var jumpTableStartBlock)) if (!value.MatchLdLoc(exceptionVariable))
return false; return false;
if (catchBlock.Instructions.Count > 2 && catchBlock.Instructions[catchBlock.Instructions.Count - 3] is StLoc stloc) break;
{ case 4:
objectVariableStore = stloc; 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(); 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; return false;
// analyze jump table: // analyze jump table:
// [stloc identifierVariableCopy(identifierVariable)] switch (jumpTableStartBlock.Instructions.Count)
{
case 3:
// stloc identifierVariableCopy(identifierVariable)
// if (comp(identifierVariable == id)) br realEntryPoint // if (comp(identifierVariable == id)) br realEntryPoint
// br jumpTableEntryBlock // br jumpTableEntryBlock
ILVariable identifierVariableCopy; if (!jumpTableStartBlock.Instructions[0].MatchStLoc(out var identifierVariableCopy, out var identifierVariableLoad)
if (jumpTableStartBlock.Instructions.Count == 3) || !identifierVariableLoad.MatchLdLoc(identifierVariable))
{ {
if (!jumpTableStartBlock.Instructions[0].MatchStLoc(out identifierVariableCopy, out var identifierVariableLoad) || !identifierVariableLoad.MatchLdLoc(identifierVariable))
return false; return false;
} }
else if (jumpTableStartBlock.Instructions.Count == 2) 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)
{ {
identifierVariableCopy = identifierVariable; return false;
} }
else
return ParseSwitchJumpTable(id, switchInst, identifierVariable, out realEntryPoint, out nextBlockOrExitContainer, out jumpTableEntry);
default:
return false;
}
bool ParseSwitchJumpTable(int id, SwitchInstruction jumpTable, ILVariable identifierVariable, out Block realEntryPoint, out ILInstruction nextBlockOrExitContainer, out ILInstruction jumpTableEntry)
{
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; return false;
var jumpTableEntryBlock = jumpTableStartBlock; 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;
}
bool ParseIfJumpTable(int id, Block jumpTableEntryBlock, ILVariable identifierVariable, out Block realEntryPoint, out ILInstruction nextBlockOrExitContainer, out ILInstruction jumpTableEntry)
{
realEntryPoint = null;
nextBlockOrExitContainer = null;
jumpTableEntry = null;
do do
{ {
if (!(jumpTableEntryBlock.Instructions.SecondToLastOrDefault() is IfInstruction ifInst)) if (!(jumpTableEntryBlock.Instructions.SecondToLastOrDefault() is IfInstruction ifInst))
@ -229,7 +367,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
{ {
return false; return false;
} }
if (!left.MatchLdLoc(identifierVariableCopy)) if (!left.MatchLdLoc(identifierVariable))
return false; return false;
if (right.MatchLdcI4(id)) if (right.MatchLdcI4(id))
{ {
@ -241,4 +379,106 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
return false; 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;
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 (callVirt.Arguments[0] is not Call call || call.Arguments.Count != 1
|| !call.Arguments[0].MatchLdLoc(typedExceptionVariableStLoc.Variable))
{
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
{ {
if (!(tryCatch.Parent?.Parent is BlockContainer container)) if (!(tryCatch.Parent?.Parent is BlockContainer container))
continue; 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 // 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; continue;
// and consists of an assignment to a temporary that is used outside the catch block if (!exceptionVariable.Type.IsKnownType(KnownTypeCode.Object))
// 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))
continue; 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; 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; continue;
// globalCopyVar should only be used once, at the end of the finally-block // 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; 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; 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; continue;
var identifierVariable = initOfIdentifierVariable.Variable;
context.StepStartGroup("Inline finally block with await", tryCatch.Handlers[0]);
var cfg = new ControlFlowGraph(container, context.CancellationToken); var cfg = new ControlFlowGraph(container, context.CancellationToken);
var exitOfFinallyNode = cfg.GetNode(exitOfFinally); changedContainers.Add(container);
var entryPointOfFinallyNode = cfg.GetNode(entryPointOfFinally);
var additionalBlocksInFinally = new HashSet<Block>(); context.StepStartGroup("Move blocks to state assignments");
var invalidExits = new List<ControlFlowNode>(); Dictionary<int, Block> identifierValueTargets = new Dictionary<int, Block>();
TraverseDominatorTree(entryPointOfFinallyNode); foreach (var load in identifierVariable.LoadInstructions.ToArray())
{
var statement = LocalFunctionDecompiler.GetStatement(load);
var block = (Block)statement.Parent;
void TraverseDominatorTree(ControlFlowNode node) if (!statement.MatchIfInstruction(out var cond, out var branchToTarget))
{ {
if (entryPointOfFinallyNode != node) block.Instructions.RemoveAt(statement.ChildIndex);
continue;
}
if (block.Instructions.LastOrDefault() is not Branch otherBranch)
{
block.Instructions.RemoveAt(statement.ChildIndex);
continue;
}
if (cond.MatchCompEquals(out var left, out var right)
&& left == load && right.MatchLdcI4(out int value)
&& branchToTarget.MatchBranch(out var targetBlock))
{
identifierValueTargets.Add(value, targetBlock);
block.Instructions.RemoveAt(statement.ChildIndex);
}
else if (cond.MatchCompNotEquals(out left, out right)
&& left == load && right.MatchLdcI4(out value))
{ {
if (entryPointOfFinallyNode.Dominates(node)) identifierValueTargets.Add(value, otherBranch.TargetBlock);
additionalBlocksInFinally.Add((Block)node.UserData); block.Instructions.RemoveAt(statement.ChildIndex + 1);
statement.ReplaceWith(otherBranch);
}
else else
invalidExits.Add(node); {
block.Instructions.RemoveAt(statement.ChildIndex);
}
} }
if (node == exitOfFinallyNode) var removedBlocks = new List<Block>();
return;
foreach (var child in node.DominatorTreeChildren) foreach (var store in identifierVariable.StoreInstructions.OfType<StLoc>().ToArray())
{
if (!store.Value.MatchLdcI4(out int value))
continue;
var statement = LocalFunctionDecompiler.GetStatement(store);
var parent = (Block)statement.Parent;
if (value <= 0)
{ {
TraverseDominatorTree(child); 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);
} }
if (invalidExits.Any())
continue;
context.Step("Inline finally block with await", tryCatch.Handlers[0]); context.StepEndGroup(keepIfEmpty: true);
foreach (var blockToRemove in blocksToRemove) 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 (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))
{ {
blockToRemove.Remove(); 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));
} }
var finallyContainer = new BlockContainer(); else if (cond.MatchCompNotEqualsNull(out arg) && arg.MatchLdLoc(objectVariableCopy))
entryPointOfFinally.Remove(); {
if (removeFirstInstructionInAfterFinally) context.Step("Simplify end of finally", beforeExceptionCaptureBlock);
afterFinally.Instructions.RemoveAt(0); beforeExceptionCaptureBlock.Instructions.RemoveRange(beforeExceptionCaptureBlock.Instructions.Count - 3, 2);
changedContainers.Add(container); branch.ReplaceWith(new Leave(finallyContainer).WithILRange(branch));
var outer = BlockContainer.FindClosestContainer(container.Parent); }
if (outer != null) }
changedContainers.Add(outer); }
finallyContainer.Blocks.Add(entryPointOfFinally);
finallyContainer.AddILRange(entryPointOfFinally); foreach (var branch in container.Descendants.OfType<Branch>())
exitOfFinally.Instructions.RemoveRange(tempStore.ChildIndex, 3);
exitOfFinally.Instructions.Add(new Leave(finallyContainer));
foreach (var branchToFinally in container.Descendants.OfType<Branch>())
{ {
if (branchToFinally.TargetBlock == entryPointOfFinally) if (branch.TargetBlock == entryPointOfFinally && branch.IsDescendantOf(tryCatch.TryBlock))
branchToFinally.ReplaceWith(new Branch(afterFinally)); {
context.Step("branch to finally => branch after finally", branch);
branch.ReplaceWith(new Branch(afterFinallyBlock).WithILRange(branch));
} }
foreach (var newBlock in additionalBlocksInFinally) else if (branch.TargetBlock == capturePatternStart)
{
if (branch.IsDescendantOf(finallyContainer))
{ {
newBlock.Remove(); context.Step("branch out of finally container => leave finally container", branch);
finallyContainer.Blocks.Add(newBlock); branch.ReplaceWith(new Leave(finallyContainer).WithILRange(branch));
finallyContainer.AddILRange(newBlock);
} }
tryCatch.ReplaceWith(new TryFinally(tryCatch.TryBlock, finallyContainer).WithILRange(tryCatch.TryBlock));
} }
}
context.StepEndGroup(keepIfEmpty: true);
}
context.Step("Clean up", function);
// clean up all modified containers // clean up all modified containers
foreach (var container in changedContainers) foreach (var container in changedContainers)
container.SortBlocks(deleteUnreachableBlocks: true); container.SortBlocks(deleteUnreachableBlocks: true);
((BlockContainer)function.Body).SortBlocks(deleteUnreachableBlocks: true);
void MoveDominatedBlocksToContainer(Block newEntryPoint, Block endBlock, ControlFlowGraph graph,
BlockContainer targetContainer, List<Block> removedBlocks)
{
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 (endNode != null && endNode != n && endNode.Dominates(n))
continue;
if (block.Parent == targetContainer)
continue;
if (!removedBlocks.Contains(block))
{
MoveBlock(block, targetContainer);
}
}
}
} }
/// <summary> void MoveBlock(Block block, BlockContainer target)
/// Block finallyHead (incoming: 2) { {
/// [body of finally] context.Step($"Move {block.Label} to container at IL_{target.StartILOffset:x4}", target);
/// stloc V_4(ldloc V_1) block.Remove();
/// if (comp(ldloc V_4 == ldnull)) br afterFinally target.Blocks.Add(block);
/// br typeCheckBlock }
/// } }
///
/// Block typeCheckBlock (incoming: 1) { static (Block, Block, ILVariable) FindBlockAfterFinally(ILTransformContext context, Block block, ILVariable objectVariable)
/// stloc S_110(isinst System.Exception(ldloc V_4)) {
/// if (comp(ldloc S_110 != ldnull)) br captureBlock int count = block.Instructions.Count;
/// br throwBlock if (count < 3)
/// } return default;
///
/// Block throwBlock (incoming: 1) { if (!block.Instructions[count - 3].MatchStLoc(out var objectVariableCopy, out var value))
/// throw(ldloc V_4) return default;
/// }
/// if (!value.MatchLdLoc(objectVariable))
/// Block captureBlock (incoming: 1) { return default;
/// callvirt Throw(call Capture(ldloc S_110))
/// br afterFinally if (!block.Instructions[count - 2].MatchIfInstruction(out var cond, out var afterExceptionCaptureBlockBranch))
/// } return default;
///
/// Block afterFinally (incoming: 2) { if (!afterExceptionCaptureBlockBranch.MatchBranch(out var afterExceptionCaptureBlock))
/// stloc V_1(ldnull) return default;
/// [after finally]
/// } if (!block.Instructions[count - 1].MatchBranch(out var exceptionCaptureBlock))
/// </summary> return default;
static bool MatchExceptionCaptureBlock(StLoc tempStore, out Block endOfFinally, out Block afterFinally, out List<Block> blocksToRemove)
{ if (cond.MatchCompEqualsNull(out var arg))
afterFinally = null; {
endOfFinally = (Block)tempStore.Parent; if (!arg.MatchLdLoc(objectVariableCopy))
blocksToRemove = new List<Block>(); return default;
int count = endOfFinally.Instructions.Count; }
if (tempStore.ChildIndex != count - 3) else if (cond.MatchCompNotEqualsNull(out arg))
return false; {
if (!(endOfFinally.Instructions[count - 2] is IfInstruction ifInst)) if (!arg.MatchLdLoc(objectVariableCopy))
return false; return default;
if (!endOfFinally.Instructions.Last().MatchBranch(out var typeCheckBlock)) (afterExceptionCaptureBlock, exceptionCaptureBlock) = (exceptionCaptureBlock, afterExceptionCaptureBlock);
return false; }
if (!ifInst.TrueInst.MatchBranch(out afterFinally)) else
return false; {
// match typeCheckBlock return default;
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;
}
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])
{
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))
{
if (!afterFinally.Instructions[1].MatchBranch(out var targetBlock))
return false;
blocksToRemove.Add(afterFinally);
afterFinally = targetBlock;
return true;
}
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;
} }
if (globalCopyVarSplitted.StoreCount != 1 || globalCopyVarSplitted.LoadCount != 0)
return false; if (!AwaitInCatchTransform.MatchExceptionCaptureBlock(context, exceptionCaptureBlock,
return true; 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 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
@ -52,6 +54,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// stloc temp(isinst exceptionType(ldloc exceptionVar)) // stloc temp(isinst exceptionType(ldloc exceptionVar))
// if (comp(ldloc temp != ldnull)) br whenConditionBlock // if (comp(ldloc temp != ldnull)) br whenConditionBlock
// br falseBlock // br falseBlock
context.Step($"Detected catch-when for {catchBlock.Variable.Name} (extra store)", instructions[0]);
((StLoc)instructions[0]).Value = exceptionSlot; ((StLoc)instructions[0]).Value = exceptionSlot;
instructions[1].ReplaceWith(new Branch(whenConditionBlock)); instructions[1].ReplaceWith(new Branch(whenConditionBlock));
instructions.RemoveAt(2); instructions.RemoveAt(2);
@ -61,12 +64,95 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
// if (comp(isinst exceptionType(ldloc exceptionVar) != ldnull)) br whenConditionBlock // if (comp(isinst exceptionType(ldloc exceptionVar) != ldnull)) br whenConditionBlock
// br falseBlock // br falseBlock
context.Step($"Detected catch-when for {catchBlock.Variable.Name}", instructions[0]);
instructions[0].ReplaceWith(new Branch(whenConditionBlock)); instructions[0].ReplaceWith(new Branch(whenConditionBlock));
instructions.RemoveAt(1); instructions.RemoveAt(1);
container.SortBlocks(deleteUnreachableBlocks: true); 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> /// <summary>

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

@ -892,7 +892,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
void TransformCatchVariable(TryCatchHandler handler, Block entryPoint, bool isCatchBlock) void TransformCatchVariable(TryCatchHandler handler, Block entryPoint, bool isCatchBlock)
{ {
if (!handler.Variable.IsSingleDefinition || handler.Variable.LoadCount != 1) 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)) if (!entryPoint.Instructions[0].MatchStLoc(out var exceptionVar, out var exceptionSlotLoad))
{ {
// Not the pattern with a second exceptionVar. // Not the pattern with a second exceptionVar.

Loading…
Cancel
Save