|
|
|
@ -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); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|