Browse Source

Fix #2652: AwaitInFinallyTransform: ILAst must form a tree

pull/2639/head
Siegfried Pammer 3 years ago
parent
commit
84c15bb24f
  1. 220
      ICSharpCode.Decompiler/IL/ControlFlow/AwaitInFinallyTransform.cs
  2. 18
      ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs

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

@ -16,7 +16,9 @@ @@ -16,7 +16,9 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.Decompiler.FlowAnalysis;
@ -111,132 +113,64 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -111,132 +113,64 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (beforeExceptionCaptureBlock == null)
continue;
var (afterFinallyBlock, capturePatternStart, objectVariableCopy) = FindBlockAfterFinally(context, beforeExceptionCaptureBlock, objectVariable);
if (afterFinallyBlock == null || capturePatternStart == null)
var (noThrowBlock, exceptionCaptureBlock, objectVariableCopy) = FindBlockAfterFinally(context, beforeExceptionCaptureBlock, objectVariable);
if (noThrowBlock == null || exceptionCaptureBlock == null)
continue;
var initOfIdentifierVariable = tryCatch.Parent.Children.ElementAtOrDefault(tryCatch.ChildIndex - 1) as StLoc;
if (initOfIdentifierVariable == null || !initOfIdentifierVariable.Value.MatchLdcI4(0))
var initOfStateVariable = tryCatch.Parent.Children.ElementAtOrDefault(tryCatch.ChildIndex - 1) as StLoc;
if (initOfStateVariable == null || !initOfStateVariable.Value.MatchLdcI4(0))
continue;
var identifierVariable = initOfIdentifierVariable.Variable;
var stateVariable = initOfStateVariable.Variable;
if (!ValidateStateVariable(stateVariable, initOfStateVariable, tryCatch, entryPointOfFinally))
continue;
StateRangeAnalysis sra = new StateRangeAnalysis(StateRangeAnalysisMode.AwaitInFinally, null, stateVariable);
sra.AssignStateRanges(noThrowBlock, Util.LongSet.Universe);
var mapping = sra.GetBlockStateSetMapping((BlockContainer)noThrowBlock.Parent);
context.StepStartGroup("Inline finally block with await", tryCatch.Handlers[0]);
var cfg = new ControlFlowGraph(container, context.CancellationToken);
changedContainers.Add(container);
context.StepStartGroup("Move blocks to state assignments");
Dictionary<int, Block> identifierValueTargets = new Dictionary<int, Block>();
foreach (var load in identifierVariable.LoadInstructions.ToArray())
{
var statement = LocalFunctionDecompiler.GetStatement(load);
var block = (Block)statement.Parent;
if (!statement.MatchIfInstruction(out var cond, out var branchToTarget))
{
block.Instructions.RemoveAt(statement.ChildIndex);
continue;
}
if (block.Instructions.LastOrDefault() is not Branch otherBranch)
{
block.Instructions.RemoveAt(statement.ChildIndex);
continue;
}
var finallyContainer = new BlockContainer().WithILRange(catchBlockContainer);
tryCatch.ReplaceWith(new TryFinally(tryCatch.TryBlock, finallyContainer).WithILRange(tryCatch.TryBlock));
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))
{
identifierValueTargets.Add(value, otherBranch.TargetBlock);
block.Instructions.RemoveAt(statement.ChildIndex + 1);
statement.ReplaceWith(branchToTarget);
}
else
{
block.Instructions.RemoveAt(statement.ChildIndex);
}
}
context.Step("Move blocks into finally", finallyContainer);
MoveDominatedBlocksToContainer(entryPointOfFinally, beforeExceptionCaptureBlock, cfg, finallyContainer);
var removedBlocks = new List<Block>();
SimplifyEndOfFinally(context, objectVariable, beforeExceptionCaptureBlock, objectVariableCopy, finallyContainer);
foreach (var store in identifierVariable.StoreInstructions.OfType<StLoc>().ToArray())
if (noThrowBlock.Instructions[0].MatchLdLoc(stateVariable))
{
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);
noThrowBlock.Instructions.RemoveAt(0);
}
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)
foreach (var branch in tryCatch.TryBlock.Descendants.OfType<Branch>())
{
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 (branch.TargetBlock == entryPointOfFinally)
{
if (cond.MatchCompEqualsNull(out var arg) && arg.MatchLdLoc(objectVariableCopy))
if (!(branch.Parent is Block block && branch.ChildIndex > 0
&& block.Instructions[branch.ChildIndex - 1].MatchStLoc(stateVariable, out var v)
&& v.MatchLdcI4(out int value)))
{
context.Step("Simplify end of finally", beforeExceptionCaptureBlock);
beforeExceptionCaptureBlock.Instructions.RemoveRange(beforeExceptionCaptureBlock.Instructions.Count - 3, 2);
branch.ReplaceWith(new Leave(finallyContainer).WithILRange(branch));
value = 0;
}
else if (cond.MatchCompNotEqualsNull(out arg) && arg.MatchLdLoc(objectVariableCopy))
if (mapping.TryGetValue(value, out Block targetBlock))
{
context.Step("Simplify end of finally", beforeExceptionCaptureBlock);
beforeExceptionCaptureBlock.Instructions.RemoveRange(beforeExceptionCaptureBlock.Instructions.Count - 3, 2);
branch.ReplaceWith(new Leave(finallyContainer).WithILRange(branch));
context.Step($"branch to finally with state {value} => branch to state target " + targetBlock.Label, branch);
branch.TargetBlock = targetBlock;
}
}
}
foreach (var branch in container.Descendants.OfType<Branch>())
{
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))
else
{
context.Step("branch out of finally container => leave finally container", branch);
branch.ReplaceWith(new Leave(finallyContainer).WithILRange(branch));
context.Step("branch to finally => branch after finally", branch);
branch.TargetBlock = noThrowBlock;
}
}
else
{
Debug.Assert(branch.TargetBlock.IsDescendantOf(tryCatch.TryBlock));
}
}
context.StepEndGroup(keepIfEmpty: true);
@ -251,7 +185,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -251,7 +185,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
((BlockContainer)function.Body).SortBlocks(deleteUnreachableBlocks: true);
void MoveDominatedBlocksToContainer(Block newEntryPoint, Block endBlock, ControlFlowGraph graph,
BlockContainer targetContainer, List<Block> removedBlocks)
BlockContainer targetContainer)
{
var node = graph.GetNode(newEntryPoint);
var endNode = endBlock == null ? null : graph.GetNode(endBlock);
@ -270,10 +204,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -270,10 +204,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (block.Parent == targetContainer)
continue;
if (!removedBlocks.Contains(block))
{
MoveBlock(block, targetContainer);
}
MoveBlock(block, targetContainer);
}
}
}
@ -284,10 +215,70 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -284,10 +215,70 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
block.Remove();
target.Blocks.Add(block);
}
static void SimplifyEndOfFinally(ILTransformContext context, ILVariable objectVariable, Block beforeExceptionCaptureBlock, ILVariable objectVariableCopy, BlockContainer finallyContainer)
{
if (beforeExceptionCaptureBlock.Instructions.Count >= 3
&& 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));
}
else
{
Debug.Fail("Broken beforeExceptionCaptureBlock");
}
}
else
{
Debug.Fail("Broken beforeExceptionCaptureBlock");
}
}
}
private static bool ValidateStateVariable(ILVariable stateVariable, StLoc initializer, TryCatch tryCatch, Block entryPointOfFinally)
{
if (stateVariable.AddressCount > 0)
return false;
foreach (var store in stateVariable.StoreInstructions)
{
if (store == initializer)
continue;
if (store is not StLoc stloc)
return false;
if (!stloc.Value.MatchLdcI4(out _))
return false;
if (!stloc.IsDescendantOf(tryCatch))
return false;
if (stloc.Parent is not Block block)
return false;
if (block.Instructions.ElementAtOrDefault(stloc.ChildIndex + 1)?.MatchBranch(entryPointOfFinally) != true)
return false;
}
return true;
}
static (Block, Block, ILVariable) FindBlockAfterFinally(ILTransformContext context, Block block, ILVariable objectVariable)
{
// Block IL_0327 (incoming: 2) {
// stloc V_7(ldloc I_0)
// if (comp.o(ldloc V_7 == ldnull)) br IL_034a
// br IL_0333
// }
int count = block.Instructions.Count;
if (count < 3)
return default;
@ -298,10 +289,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -298,10 +289,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (!value.MatchLdLoc(objectVariable))
return default;
if (!block.Instructions[count - 2].MatchIfInstruction(out var cond, out var afterExceptionCaptureBlockBranch))
if (!block.Instructions[count - 2].MatchIfInstruction(out var cond, out var noThrowBlockBranch))
return default;
if (!afterExceptionCaptureBlockBranch.MatchBranch(out var afterExceptionCaptureBlock))
if (!noThrowBlockBranch.MatchBranch(out var noThrowBlock))
return default;
if (!block.Instructions[count - 1].MatchBranch(out var exceptionCaptureBlock))
@ -316,7 +307,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -316,7 +307,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
{
if (!arg.MatchLdLoc(objectVariableCopy))
return default;
(afterExceptionCaptureBlock, exceptionCaptureBlock) = (exceptionCaptureBlock, afterExceptionCaptureBlock);
(noThrowBlock, exceptionCaptureBlock) = (exceptionCaptureBlock, noThrowBlock);
}
else
{
@ -324,17 +315,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -324,17 +315,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
if (!AwaitInCatchTransform.MatchExceptionCaptureBlock(context, exceptionCaptureBlock,
ref objectVariableCopy, out var store, out _, out _))
ref objectVariableCopy, out _, out _, out _))
{
return default;
}
var exceptionCaptureBlockStart = LocalFunctionDecompiler.GetStatement(store);
if (exceptionCaptureBlockStart == null)
return default;
return (afterExceptionCaptureBlock, (Block)exceptionCaptureBlockStart.Parent, objectVariableCopy);
return (noThrowBlock, exceptionCaptureBlock, objectVariableCopy);
}
}
}

18
ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs

@ -16,9 +16,12 @@ @@ -16,9 +16,12 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading;
@ -33,7 +36,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -33,7 +36,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
{
IteratorMoveNext,
IteratorDispose,
AsyncMoveNext
AsyncMoveNext,
AwaitInFinally
}
/// <summary>
@ -53,16 +57,16 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -53,16 +57,16 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
{
public CancellationToken CancellationToken;
readonly StateRangeAnalysisMode mode;
readonly IField stateField;
readonly IField? stateField;
readonly SymbolicEvaluationContext evalContext;
readonly Dictionary<Block, LongSet> ranges = new Dictionary<Block, LongSet>();
readonly internal Dictionary<IMethod, LongSet> finallyMethodToStateRange; // used only for IteratorDispose
readonly internal Dictionary<IMethod, LongSet>? finallyMethodToStateRange; // used only for IteratorDispose
internal ILVariable doFinallyBodies;
internal ILVariable skipFinallyBodies;
internal ILVariable? doFinallyBodies;
internal ILVariable? skipFinallyBodies;
public StateRangeAnalysis(StateRangeAnalysisMode mode, IField stateField, ILVariable cachedStateVar = null)
public StateRangeAnalysis(StateRangeAnalysisMode mode, IField? stateField, ILVariable? cachedStateVar = null)
{
this.mode = mode;
this.stateField = stateField;
@ -195,7 +199,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -195,7 +199,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// Call to finally method.
// Usually these are in finally blocks, but sometimes (e.g. foreach over array),
// the C# compiler puts the call to a finally method outside the try-finally block.
finallyMethodToStateRange.Add((IMethod)call.Method.MemberDefinition, stateRange);
finallyMethodToStateRange!.Add((IMethod)call.Method.MemberDefinition, stateRange);
return LongSet.Empty; // return Empty since we executed user code (the finally method)
case StObj stobj when mode == StateRangeAnalysisMode.IteratorMoveNext:
{

Loading…
Cancel
Save