Browse Source

Fix #3825: handle hoisted-local cleanup before combined-tokens disposal

For an async iterator with an [EnumeratorCancellation] cancellation token, the
hoisted-local cleanup (stfld <>u__N(this, null)) can be emitted before the
combined CancellationTokenSource disposal in the set-result and catch blocks.
CheckSetResultReturnBlock and ValidateCatchBlock only consumed that cleanup after
the disposal, so the `pos + 2 == count` test missed the dispose pattern and the
analysis failed, leaving the raw state machine (catch (object), goto case, ...).

Allow the cleanup to appear before the combined-tokens disposal as well.

Assisted-by: Copilot:claude-opus-4.8:GitHub Copilot CLI
pull/3831/head
Sebastien Lebreton 2 weeks ago
parent
commit
d9ce405354
  1. 14
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncStreams.cs
  2. 11
      ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs

14
ICSharpCode.Decompiler.Tests/TestCases/Pretty/AsyncStreams.cs

@ -71,6 +71,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -71,6 +71,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
await Task.Delay(10).ConfigureAwait(continueOnCapturedContext: false);
}
}
public static async IAsyncEnumerable<int> AwaitInFinallyWithCancellation([EnumeratorCancellation] CancellationToken cancellationToken)
{
await Task.Yield();
try
{
yield return 1;
await Task.Delay(10, cancellationToken);
}
finally
{
await Task.Yield();
}
}
}
public struct TestStruct

11
ICSharpCode.Decompiler/IL/ControlFlow/AsyncAwaitDecompiler.cs

@ -911,6 +911,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -911,6 +911,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
finalStateKnown = true;
pos++;
// [optional] stfld <>u__N(ldloc this, ldnull)
// The hoisted-local cleanup may appear either before or after the combined-tokens disposal
// (the latter is present for async iterators with an [EnumeratorCancellation] token).
MatchHoistedLocalCleanup(block, ref pos);
if (pos + 2 == block.Instructions.Count && block.MatchIfAtEndOfBlock(out var condition, out var trueInst, out var falseInst))
{
if (MatchDisposeCombinedTokens(blockContainer, condition, trueInst, falseInst, blocksAnalyzed, out var setResultAndExitBlock))
@ -1088,6 +1093,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -1088,6 +1093,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
finalState = newState;
finalStateKnown = true;
}
// [optional] stfld <>u__N(ldloc this, ldnull)
// The hoisted-local cleanup may appear either before or after the combined-tokens disposal
// (the latter is present for async iterators with an [EnumeratorCancellation] token).
MatchHoistedLocalCleanup(catchBlock, ref pos);
if (pos + 2 == catchBlock.Instructions.Count && catchBlock.MatchIfAtEndOfBlock(out var condition, out var trueInst, out var falseInst))
{
if (MatchDisposeCombinedTokens(handlerContainer, condition, trueInst, falseInst, blocksAnalyzed, out var setResultAndExitBlock))

Loading…
Cancel
Save