From 9105bf7d2e0f5bff891f0a84c3ce3eeb60c91efb Mon Sep 17 00:00:00 2001 From: Henrique Ruschel Date: Thu, 27 Nov 2025 17:39:14 -0300 Subject: [PATCH 1/5] Fix for async iterator #81467 --- docs/features/async-streams.md | 2 +- .../AsyncRewriter.AsyncIteratorRewriter.cs | 21 +- .../StateMachineRewriter.cs | 15 +- .../Emit/CodeGen/CodeGenAsyncIteratorTests.cs | 292 +++++++++--------- .../Test/Emit3/Semantics/ExtensionTests.cs | 12 +- 5 files changed, 175 insertions(+), 167 deletions(-) diff --git a/docs/features/async-streams.md b/docs/features/async-streams.md index 94451737be0d6..9f9b064c316f3 100644 --- a/docs/features/async-streams.md +++ b/docs/features/async-streams.md @@ -201,7 +201,7 @@ else if (token.Equals(this.parameterProxy) || token.Equals(default)) else { result.combinedTokens = CancellationTokenSource.CreateLinkedTokenSource(this.parameterProxy, token); - result.parameter = combinedTokens.Token; + result.parameter = result.combinedTokens.Token; } ``` For a discussion of the threadID check, see https://github.com/dotnet/corefx/issues/3481 diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs index c305d9afe130f..09387aac28dff 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncRewriter.AsyncIteratorRewriter.cs @@ -207,10 +207,10 @@ protected override void InitializeStateMachine(ArrayBuilder body F.New(stateMachineType.Constructor.AsMember(frameType), F.Literal(initialState)))); } - protected override BoundStatement InitializeParameterField(MethodSymbol getEnumeratorMethod, ParameterSymbol parameter, BoundExpression resultParameter, BoundExpression parameterProxy) + protected override BoundStatement InitializeParameterField(MethodSymbol getEnumeratorMethod, ParameterSymbol parameter, BoundExpression result, BoundExpression resultParameter, BoundExpression parameterProxy) { - BoundStatement result; - if (_combinedTokensField is object && + BoundStatement initializeParameterFieldResult; + if (_combinedTokensField is not null && !parameter.IsExtensionParameterImplementation() && parameter.HasEnumeratorCancellationAttribute && parameter.Type.Equals(F.Compilation.GetWellKnownType(WellKnownType.System_Threading_CancellationToken), TypeCompareKind.ConsiderEverything)) @@ -227,12 +227,13 @@ protected override BoundStatement InitializeParameterField(MethodSymbol getEnume // else // { // result.combinedTokens = CancellationTokenSource.CreateLinkedTokenSource(this.parameterProxy, token); - // result.parameter = combinedTokens.Token; + // result.parameter = result.combinedTokens.Token; // } BoundParameter tokenParameter = F.Parameter(getEnumeratorMethod.Parameters[0]); - BoundFieldAccess combinedTokens = F.Field(F.This(), _combinedTokensField); - result = F.If( + BoundFieldAccess resultCombinedTokens = F.Field(result, _combinedTokensField); + + initializeParameterFieldResult = F.If( // if (this.parameterProxy.Equals(default)) F.Call(parameterProxy, WellKnownMember.System_Threading_CancellationToken__Equals, F.Default(parameterProxy.Type)), // result.parameter = token; @@ -246,18 +247,18 @@ protected override BoundStatement InitializeParameterField(MethodSymbol getEnume thenClause: F.Assignment(resultParameter, parameterProxy), elseClauseOpt: F.Block( // result.combinedTokens = CancellationTokenSource.CreateLinkedTokenSource(this.parameterProxy, token); - F.Assignment(combinedTokens, F.StaticCall(WellKnownMember.System_Threading_CancellationTokenSource__CreateLinkedTokenSource, parameterProxy, tokenParameter)), + F.Assignment(resultCombinedTokens, F.StaticCall(WellKnownMember.System_Threading_CancellationTokenSource__CreateLinkedTokenSource, parameterProxy, tokenParameter)), // result.parameter = result.combinedTokens.Token; - F.Assignment(resultParameter, F.Property(combinedTokens, WellKnownMember.System_Threading_CancellationTokenSource__Token))))); + F.Assignment(resultParameter, F.Property(resultCombinedTokens, WellKnownMember.System_Threading_CancellationTokenSource__Token))))); } else { // For parameters that don't have [EnumeratorCancellation], initialize their parameter fields // result.parameter = this.parameterProxy; - result = F.Assignment(resultParameter, parameterProxy); + initializeParameterFieldResult = F.Assignment(resultParameter, parameterProxy); } - return result; + return initializeParameterFieldResult; } protected override BoundStatement GenerateStateMachineCreation(LocalSymbol stateMachineVariable, NamedTypeSymbol frameType, IReadOnlyDictionary proxies) diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs index 6c5fc05a07aff..8a8091bd87d7c 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs @@ -430,7 +430,7 @@ protected SynthesizedImplementationMethod GenerateIteratorGetEnumerator(MethodSy var thisInitialized = F.GenerateLabel("thisInitialized"); - if ((object)initialThreadIdField != null) + if (initialThreadIdField is not null) { managedThreadId = MakeCurrentThreadId(); @@ -487,6 +487,9 @@ protected SynthesizedImplementationMethod GenerateIteratorGetEnumerator(MethodSy CapturedSymbolReplacement proxy; if (copyDest.TryGetValue(parameter, out proxy)) { + // result + BoundExpression result = F.Local(resultVariable); + // result.parameter BoundExpression resultParameter = proxy.Replacement( F.Syntax, @@ -494,8 +497,12 @@ protected SynthesizedImplementationMethod GenerateIteratorGetEnumerator(MethodSy (F, resultVariable)); // this.parameterProxy - BoundExpression parameterProxy = copySrc[parameter].Replacement(F.Syntax, static (stateMachineType, F) => F.This(), F); - BoundStatement copy = InitializeParameterField(getEnumeratorMethod, parameter, resultParameter, parameterProxy); + BoundExpression parameterProxy = copySrc[parameter].Replacement( + F.Syntax, + static (stateMachineType, F) => F.This(), + F + ); + BoundStatement copy = InitializeParameterField(getEnumeratorMethod, parameter, result, resultParameter, parameterProxy); bodyBuilder.Add(copy); } @@ -516,7 +523,7 @@ protected virtual void GenerateResetInstance(ArrayBuilder builde F.Assignment(F.Field(F.This(), stateField), F.Literal(initialState))); } - protected virtual BoundStatement InitializeParameterField(MethodSymbol getEnumeratorMethod, ParameterSymbol parameter, BoundExpression resultParameter, BoundExpression parameterProxy) + protected virtual BoundStatement InitializeParameterField(MethodSymbol getEnumeratorMethod, ParameterSymbol parameter, BoundExpression result, BoundExpression resultParameter, BoundExpression parameterProxy) { Debug.Assert(!method.IsIterator || !method.IsAsync); // an override handles async-iterators diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs index ee2351156ea27..6d65a66dfdcb5 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs @@ -3241,14 +3241,14 @@ .locals init (C.d__0 V_0, IL_007f: ldfld ""System.Threading.CancellationToken C.d__0.<>3__token"" IL_0084: stfld ""System.Threading.CancellationToken C.d__0.token"" IL_0089: br.s IL_00ae - IL_008b: ldarg.0 + IL_008b: ldloc.0 IL_008c: ldarg.0 IL_008d: ldfld ""System.Threading.CancellationToken C.d__0.<>3__token"" IL_0092: ldarg.1 IL_0093: call ""System.Threading.CancellationTokenSource System.Threading.CancellationTokenSource.CreateLinkedTokenSource(System.Threading.CancellationToken, System.Threading.CancellationToken)"" IL_0098: stfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" IL_009d: ldloc.0 - IL_009e: ldarg.0 + IL_009e: ldloc.0 IL_009f: ldfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" IL_00a4: callvirt ""System.Threading.CancellationToken System.Threading.CancellationTokenSource.Token.get"" IL_00a9: stfld ""System.Threading.CancellationToken C.d__0.token"" @@ -3455,73 +3455,73 @@ public static async partial System.Collections.Generic.IAsyncEnumerable M(" // we generate initialization logic for the token parameter verifier.VerifyIL("C.d__0.System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator(System.Threading.CancellationToken)", @" { - // Code size 176 (0xb0) - .maxstack 3 - .locals init (C.d__0 V_0, + // Code size 176 (0xb0) + .maxstack 3 + .locals init (C.d__0 V_0, System.Threading.CancellationToken V_1) - IL_0000: ldarg.0 - IL_0001: ldfld ""int C.d__0.<>1__state"" - IL_0006: ldc.i4.s -2 - IL_0008: bne.un.s IL_0035 - IL_000a: ldarg.0 - IL_000b: ldfld ""int C.d__0.<>l__initialThreadId"" - IL_0010: call ""int System.Environment.CurrentManagedThreadId.get"" - IL_0015: bne.un.s IL_0035 - IL_0017: ldarg.0 - IL_0018: ldc.i4.s -3 - IL_001a: stfld ""int C.d__0.<>1__state"" - IL_001f: ldarg.0 - IL_0020: call ""System.Runtime.CompilerServices.AsyncIteratorMethodBuilder System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.Create()"" - IL_0025: stfld ""System.Runtime.CompilerServices.AsyncIteratorMethodBuilder C.d__0.<>t__builder"" - IL_002a: ldarg.0 - IL_002b: ldc.i4.0 - IL_002c: stfld ""bool C.d__0.<>w__disposeMode"" - IL_0031: ldarg.0 - IL_0032: stloc.0 - IL_0033: br.s IL_003d - IL_0035: ldc.i4.s -3 - IL_0037: newobj ""C.d__0..ctor(int)"" - IL_003c: stloc.0 - IL_003d: ldarg.0 - IL_003e: ldflda ""System.Threading.CancellationToken C.d__0.<>3__token"" - IL_0043: ldloca.s V_1 - IL_0045: initobj ""System.Threading.CancellationToken"" - IL_004b: ldloc.1 - IL_004c: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" - IL_0051: brfalse.s IL_005c - IL_0053: ldloc.0 - IL_0054: ldarg.1 - IL_0055: stfld ""System.Threading.CancellationToken C.d__0.token"" - IL_005a: br.s IL_00ae - IL_005c: ldarga.s V_1 - IL_005e: ldarg.0 - IL_005f: ldfld ""System.Threading.CancellationToken C.d__0.<>3__token"" - IL_0064: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" - IL_0069: brtrue.s IL_007d - IL_006b: ldarga.s V_1 - IL_006d: ldloca.s V_1 - IL_006f: initobj ""System.Threading.CancellationToken"" - IL_0075: ldloc.1 - IL_0076: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" - IL_007b: brfalse.s IL_008b - IL_007d: ldloc.0 - IL_007e: ldarg.0 - IL_007f: ldfld ""System.Threading.CancellationToken C.d__0.<>3__token"" - IL_0084: stfld ""System.Threading.CancellationToken C.d__0.token"" - IL_0089: br.s IL_00ae - IL_008b: ldarg.0 - IL_008c: ldarg.0 - IL_008d: ldfld ""System.Threading.CancellationToken C.d__0.<>3__token"" - IL_0092: ldarg.1 - IL_0093: call ""System.Threading.CancellationTokenSource System.Threading.CancellationTokenSource.CreateLinkedTokenSource(System.Threading.CancellationToken, System.Threading.CancellationToken)"" - IL_0098: stfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" - IL_009d: ldloc.0 - IL_009e: ldarg.0 - IL_009f: ldfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" - IL_00a4: callvirt ""System.Threading.CancellationToken System.Threading.CancellationTokenSource.Token.get"" - IL_00a9: stfld ""System.Threading.CancellationToken C.d__0.token"" - IL_00ae: ldloc.0 - IL_00af: ret + IL_0000: ldarg.0 + IL_0001: ldfld ""int C.d__0.<>1__state"" + IL_0006: ldc.i4.s -2 + IL_0008: bne.un.s IL_0035 + IL_000a: ldarg.0 + IL_000b: ldfld ""int C.d__0.<>l__initialThreadId"" + IL_0010: call ""int System.Environment.CurrentManagedThreadId.get"" + IL_0015: bne.un.s IL_0035 + IL_0017: ldarg.0 + IL_0018: ldc.i4.s -3 + IL_001a: stfld ""int C.d__0.<>1__state"" + IL_001f: ldarg.0 + IL_0020: call ""System.Runtime.CompilerServices.AsyncIteratorMethodBuilder System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.Create()"" + IL_0025: stfld ""System.Runtime.CompilerServices.AsyncIteratorMethodBuilder C.d__0.<>t__builder"" + IL_002a: ldarg.0 + IL_002b: ldc.i4.0 + IL_002c: stfld ""bool C.d__0.<>w__disposeMode"" + IL_0031: ldarg.0 + IL_0032: stloc.0 + IL_0033: br.s IL_003d + IL_0035: ldc.i4.s -3 + IL_0037: newobj ""C.d__0..ctor(int)"" + IL_003c: stloc.0 + IL_003d: ldarg.0 + IL_003e: ldflda ""System.Threading.CancellationToken C.d__0.<>3__token"" + IL_0043: ldloca.s V_1 + IL_0045: initobj ""System.Threading.CancellationToken"" + IL_004b: ldloc.1 + IL_004c: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" + IL_0051: brfalse.s IL_005c + IL_0053: ldloc.0 + IL_0054: ldarg.1 + IL_0055: stfld ""System.Threading.CancellationToken C.d__0.token"" + IL_005a: br.s IL_00ae + IL_005c: ldarga.s V_1 + IL_005e: ldarg.0 + IL_005f: ldfld ""System.Threading.CancellationToken C.d__0.<>3__token"" + IL_0064: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" + IL_0069: brtrue.s IL_007d + IL_006b: ldarga.s V_1 + IL_006d: ldloca.s V_1 + IL_006f: initobj ""System.Threading.CancellationToken"" + IL_0075: ldloc.1 + IL_0076: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" + IL_007b: brfalse.s IL_008b + IL_007d: ldloc.0 + IL_007e: ldarg.0 + IL_007f: ldfld ""System.Threading.CancellationToken C.d__0.<>3__token"" + IL_0084: stfld ""System.Threading.CancellationToken C.d__0.token"" + IL_0089: br.s IL_00ae + IL_008b: ldloc.0 + IL_008c: ldarg.0 + IL_008d: ldfld ""System.Threading.CancellationToken C.d__0.<>3__token"" + IL_0092: ldarg.1 + IL_0093: call ""System.Threading.CancellationTokenSource System.Threading.CancellationTokenSource.CreateLinkedTokenSource(System.Threading.CancellationToken, System.Threading.CancellationToken)"" + IL_0098: stfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" + IL_009d: ldloc.0 + IL_009e: ldloc.0 + IL_009f: ldfld ""System.Threading.CancellationTokenSource C.d__0.<>x__combinedTokens"" + IL_00a4: callvirt ""System.Threading.CancellationToken System.Threading.CancellationTokenSource.Token.get"" + IL_00a9: stfld ""System.Threading.CancellationToken C.d__0.token"" + IL_00ae: ldloc.0 + IL_00af: ret }"); // we generate disposal logic for the combinedTokens field @@ -3778,14 +3778,14 @@ .locals init (C.<g__local|0_0>d V_0, IL_007f: ldfld ""System.Threading.CancellationToken C.<g__local|0_0>d.<>3__token"" IL_0084: stfld ""System.Threading.CancellationToken C.<g__local|0_0>d.token"" IL_0089: br.s IL_00ae - IL_008b: ldarg.0 + IL_008b: ldloc.0 IL_008c: ldarg.0 IL_008d: ldfld ""System.Threading.CancellationToken C.<g__local|0_0>d.<>3__token"" IL_0092: ldarg.1 IL_0093: call ""System.Threading.CancellationTokenSource System.Threading.CancellationTokenSource.CreateLinkedTokenSource(System.Threading.CancellationToken, System.Threading.CancellationToken)"" IL_0098: stfld ""System.Threading.CancellationTokenSource C.<g__local|0_0>d.<>x__combinedTokens"" IL_009d: ldloc.0 - IL_009e: ldarg.0 + IL_009e: ldloc.0 IL_009f: ldfld ""System.Threading.CancellationTokenSource C.<g__local|0_0>d.<>x__combinedTokens"" IL_00a4: callvirt ""System.Threading.CancellationToken System.Threading.CancellationTokenSource.Token.get"" IL_00a9: stfld ""System.Threading.CancellationToken C.<g__local|0_0>d.token"" @@ -7681,14 +7681,14 @@ .locals init (C.d__1 V_0, IL_008b: ldfld ""System.Threading.CancellationToken C.d__1.<>3__token1"" IL_0090: stfld ""System.Threading.CancellationToken C.d__1.token1"" IL_0095: br.s IL_00ba - IL_0097: ldarg.0 + IL_0097: ldloc.0 IL_0098: ldarg.0 IL_0099: ldfld ""System.Threading.CancellationToken C.d__1.<>3__token1"" IL_009e: ldarg.1 IL_009f: call ""System.Threading.CancellationTokenSource System.Threading.CancellationTokenSource.CreateLinkedTokenSource(System.Threading.CancellationToken, System.Threading.CancellationToken)"" IL_00a4: stfld ""System.Threading.CancellationTokenSource C.d__1.<>x__combinedTokens"" IL_00a9: ldloc.0 - IL_00aa: ldarg.0 + IL_00aa: ldloc.0 IL_00ab: ldfld ""System.Threading.CancellationTokenSource C.d__1.<>x__combinedTokens"" IL_00b0: callvirt ""System.Threading.CancellationToken System.Threading.CancellationTokenSource.Token.get"" IL_00b5: stfld ""System.Threading.CancellationToken C.d__1.token1"" @@ -7756,81 +7756,81 @@ static async System.Collections.Generic.IAsyncEnumerable Iter(int value, [E // GetAsyncEnumerator's token parameter is used directly, since the argument token is default verifier.VerifyIL("C.<
g__Iter|0_0>d.System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator(System.Threading.CancellationToken)", @" { - // Code size 200 (0xc8) - .maxstack 3 - .locals init (C.<
g__Iter|0_0>d V_0, + // Code size 200 (0xc8) + .maxstack 3 + .locals init (C.<
g__Iter|0_0>d V_0, System.Threading.CancellationToken V_1) - IL_0000: ldarg.0 - IL_0001: ldfld ""int C.<
g__Iter|0_0>d.<>1__state"" - IL_0006: ldc.i4.s -2 - IL_0008: bne.un.s IL_0035 - IL_000a: ldarg.0 - IL_000b: ldfld ""int C.<
g__Iter|0_0>d.<>l__initialThreadId"" - IL_0010: call ""int System.Environment.CurrentManagedThreadId.get"" - IL_0015: bne.un.s IL_0035 - IL_0017: ldarg.0 - IL_0018: ldc.i4.s -3 - IL_001a: stfld ""int C.<
g__Iter|0_0>d.<>1__state"" - IL_001f: ldarg.0 - IL_0020: call ""System.Runtime.CompilerServices.AsyncIteratorMethodBuilder System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.Create()"" - IL_0025: stfld ""System.Runtime.CompilerServices.AsyncIteratorMethodBuilder C.<
g__Iter|0_0>d.<>t__builder"" - IL_002a: ldarg.0 - IL_002b: ldc.i4.0 - IL_002c: stfld ""bool C.<
g__Iter|0_0>d.<>w__disposeMode"" - IL_0031: ldarg.0 - IL_0032: stloc.0 - IL_0033: br.s IL_003d - IL_0035: ldc.i4.s -3 - IL_0037: newobj ""C.<
g__Iter|0_0>d..ctor(int)"" - IL_003c: stloc.0 - IL_003d: ldloc.0 - IL_003e: ldarg.0 - IL_003f: ldfld ""int C.<
g__Iter|0_0>d.<>3__value"" - IL_0044: stfld ""int C.<
g__Iter|0_0>d.value"" - IL_0049: ldarg.0 - IL_004a: ldflda ""System.Threading.CancellationToken C.<
g__Iter|0_0>d.<>3__token1"" - IL_004f: ldloca.s V_1 - IL_0051: initobj ""System.Threading.CancellationToken"" - IL_0057: ldloc.1 - IL_0058: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" - IL_005d: brfalse.s IL_0068 - IL_005f: ldloc.0 - IL_0060: ldarg.1 - IL_0061: stfld ""System.Threading.CancellationToken C.<
g__Iter|0_0>d.token1"" - IL_0066: br.s IL_00ba - IL_0068: ldarga.s V_1 - IL_006a: ldarg.0 - IL_006b: ldfld ""System.Threading.CancellationToken C.<
g__Iter|0_0>d.<>3__token1"" - IL_0070: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" - IL_0075: brtrue.s IL_0089 - IL_0077: ldarga.s V_1 - IL_0079: ldloca.s V_1 - IL_007b: initobj ""System.Threading.CancellationToken"" - IL_0081: ldloc.1 - IL_0082: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" - IL_0087: brfalse.s IL_0097 - IL_0089: ldloc.0 - IL_008a: ldarg.0 - IL_008b: ldfld ""System.Threading.CancellationToken C.<
g__Iter|0_0>d.<>3__token1"" - IL_0090: stfld ""System.Threading.CancellationToken C.<
g__Iter|0_0>d.token1"" - IL_0095: br.s IL_00ba - IL_0097: ldarg.0 - IL_0098: ldarg.0 - IL_0099: ldfld ""System.Threading.CancellationToken C.<
g__Iter|0_0>d.<>3__token1"" - IL_009e: ldarg.1 - IL_009f: call ""System.Threading.CancellationTokenSource System.Threading.CancellationTokenSource.CreateLinkedTokenSource(System.Threading.CancellationToken, System.Threading.CancellationToken)"" - IL_00a4: stfld ""System.Threading.CancellationTokenSource C.<
g__Iter|0_0>d.<>x__combinedTokens"" - IL_00a9: ldloc.0 - IL_00aa: ldarg.0 - IL_00ab: ldfld ""System.Threading.CancellationTokenSource C.<
g__Iter|0_0>d.<>x__combinedTokens"" - IL_00b0: callvirt ""System.Threading.CancellationToken System.Threading.CancellationTokenSource.Token.get"" - IL_00b5: stfld ""System.Threading.CancellationToken C.<
g__Iter|0_0>d.token1"" - IL_00ba: ldloc.0 - IL_00bb: ldarg.0 - IL_00bc: ldfld ""System.Threading.CancellationToken C.<
g__Iter|0_0>d.<>3__origToken"" - IL_00c1: stfld ""System.Threading.CancellationToken C.<
g__Iter|0_0>d.origToken"" - IL_00c6: ldloc.0 - IL_00c7: ret + IL_0000: ldarg.0 + IL_0001: ldfld ""int C.<
g__Iter|0_0>d.<>1__state"" + IL_0006: ldc.i4.s -2 + IL_0008: bne.un.s IL_0035 + IL_000a: ldarg.0 + IL_000b: ldfld ""int C.<
g__Iter|0_0>d.<>l__initialThreadId"" + IL_0010: call ""int System.Environment.CurrentManagedThreadId.get"" + IL_0015: bne.un.s IL_0035 + IL_0017: ldarg.0 + IL_0018: ldc.i4.s -3 + IL_001a: stfld ""int C.<
g__Iter|0_0>d.<>1__state"" + IL_001f: ldarg.0 + IL_0020: call ""System.Runtime.CompilerServices.AsyncIteratorMethodBuilder System.Runtime.CompilerServices.AsyncIteratorMethodBuilder.Create()"" + IL_0025: stfld ""System.Runtime.CompilerServices.AsyncIteratorMethodBuilder C.<
g__Iter|0_0>d.<>t__builder"" + IL_002a: ldarg.0 + IL_002b: ldc.i4.0 + IL_002c: stfld ""bool C.<
g__Iter|0_0>d.<>w__disposeMode"" + IL_0031: ldarg.0 + IL_0032: stloc.0 + IL_0033: br.s IL_003d + IL_0035: ldc.i4.s -3 + IL_0037: newobj ""C.<
g__Iter|0_0>d..ctor(int)"" + IL_003c: stloc.0 + IL_003d: ldloc.0 + IL_003e: ldarg.0 + IL_003f: ldfld ""int C.<
g__Iter|0_0>d.<>3__value"" + IL_0044: stfld ""int C.<
g__Iter|0_0>d.value"" + IL_0049: ldarg.0 + IL_004a: ldflda ""System.Threading.CancellationToken C.<
g__Iter|0_0>d.<>3__token1"" + IL_004f: ldloca.s V_1 + IL_0051: initobj ""System.Threading.CancellationToken"" + IL_0057: ldloc.1 + IL_0058: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" + IL_005d: brfalse.s IL_0068 + IL_005f: ldloc.0 + IL_0060: ldarg.1 + IL_0061: stfld ""System.Threading.CancellationToken C.<
g__Iter|0_0>d.token1"" + IL_0066: br.s IL_00ba + IL_0068: ldarga.s V_1 + IL_006a: ldarg.0 + IL_006b: ldfld ""System.Threading.CancellationToken C.<
g__Iter|0_0>d.<>3__token1"" + IL_0070: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" + IL_0075: brtrue.s IL_0089 + IL_0077: ldarga.s V_1 + IL_0079: ldloca.s V_1 + IL_007b: initobj ""System.Threading.CancellationToken"" + IL_0081: ldloc.1 + IL_0082: call ""bool System.Threading.CancellationToken.Equals(System.Threading.CancellationToken)"" + IL_0087: brfalse.s IL_0097 + IL_0089: ldloc.0 + IL_008a: ldarg.0 + IL_008b: ldfld ""System.Threading.CancellationToken C.<
g__Iter|0_0>d.<>3__token1"" + IL_0090: stfld ""System.Threading.CancellationToken C.<
g__Iter|0_0>d.token1"" + IL_0095: br.s IL_00ba + IL_0097: ldloc.0 + IL_0098: ldarg.0 + IL_0099: ldfld ""System.Threading.CancellationToken C.<
g__Iter|0_0>d.<>3__token1"" + IL_009e: ldarg.1 + IL_009f: call ""System.Threading.CancellationTokenSource System.Threading.CancellationTokenSource.CreateLinkedTokenSource(System.Threading.CancellationToken, System.Threading.CancellationToken)"" + IL_00a4: stfld ""System.Threading.CancellationTokenSource C.<
g__Iter|0_0>d.<>x__combinedTokens"" + IL_00a9: ldloc.0 + IL_00aa: ldloc.0 + IL_00ab: ldfld ""System.Threading.CancellationTokenSource C.<
g__Iter|0_0>d.<>x__combinedTokens"" + IL_00b0: callvirt ""System.Threading.CancellationToken System.Threading.CancellationTokenSource.Token.get"" + IL_00b5: stfld ""System.Threading.CancellationToken C.<
g__Iter|0_0>d.token1"" + IL_00ba: ldloc.0 + IL_00bb: ldarg.0 + IL_00bc: ldfld ""System.Threading.CancellationToken C.<
g__Iter|0_0>d.<>3__origToken"" + IL_00c1: stfld ""System.Threading.CancellationToken C.<
g__Iter|0_0>d.origToken"" + IL_00c6: ldloc.0 + IL_00c7: ret } "); } diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs index 9f93bdc4afcb7..9d7a1f50cf577 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs @@ -42465,14 +42465,14 @@ .locals init (C.d__1 V_0, IL_008b: ldfld "System.Threading.CancellationToken C.d__1.<>3__token2" IL_0090: stfld "System.Threading.CancellationToken C.d__1.token2" IL_0095: br.s IL_00ba - IL_0097: ldarg.0 + IL_0097: ldloc.0 IL_0098: ldarg.0 IL_0099: ldfld "System.Threading.CancellationToken C.d__1.<>3__token2" IL_009e: ldarg.1 IL_009f: call "System.Threading.CancellationTokenSource System.Threading.CancellationTokenSource.CreateLinkedTokenSource(System.Threading.CancellationToken, System.Threading.CancellationToken)" IL_00a4: stfld "System.Threading.CancellationTokenSource C.d__1.<>x__combinedTokens" IL_00a9: ldloc.0 - IL_00aa: ldarg.0 + IL_00aa: ldloc.0 IL_00ab: ldfld "System.Threading.CancellationTokenSource C.d__1.<>x__combinedTokens" IL_00b0: callvirt "System.Threading.CancellationToken System.Threading.CancellationTokenSource.Token.get" IL_00b5: stfld "System.Threading.CancellationToken C.d__1.token2" @@ -42563,14 +42563,14 @@ .locals init (C.d__1 V_0, IL_007f: ldfld "System.Threading.CancellationToken C.d__1.<>3__token" IL_0084: stfld "System.Threading.CancellationToken C.d__1.token" IL_0089: br.s IL_00ae - IL_008b: ldarg.0 + IL_008b: ldloc.0 IL_008c: ldarg.0 IL_008d: ldfld "System.Threading.CancellationToken C.d__1.<>3__token" IL_0092: ldarg.1 IL_0093: call "System.Threading.CancellationTokenSource System.Threading.CancellationTokenSource.CreateLinkedTokenSource(System.Threading.CancellationToken, System.Threading.CancellationToken)" IL_0098: stfld "System.Threading.CancellationTokenSource C.d__1.<>x__combinedTokens" IL_009d: ldloc.0 - IL_009e: ldarg.0 + IL_009e: ldloc.0 IL_009f: ldfld "System.Threading.CancellationTokenSource C.d__1.<>x__combinedTokens" IL_00a4: callvirt "System.Threading.CancellationToken System.Threading.CancellationTokenSource.Token.get" IL_00a9: stfld "System.Threading.CancellationToken C.d__1.token" @@ -42743,14 +42743,14 @@ .locals init (C.d__1 V_0, IL_0097: ldfld "System.Threading.CancellationToken C.d__1.<>3__token2" IL_009c: stfld "System.Threading.CancellationToken C.d__1.token2" IL_00a1: br.s IL_00c6 - IL_00a3: ldarg.0 + IL_00a3: ldloc.0 IL_00a4: ldarg.0 IL_00a5: ldfld "System.Threading.CancellationToken C.d__1.<>3__token2" IL_00aa: ldarg.1 IL_00ab: call "System.Threading.CancellationTokenSource System.Threading.CancellationTokenSource.CreateLinkedTokenSource(System.Threading.CancellationToken, System.Threading.CancellationToken)" IL_00b0: stfld "System.Threading.CancellationTokenSource C.d__1.<>x__combinedTokens" IL_00b5: ldloc.0 - IL_00b6: ldarg.0 + IL_00b6: ldloc.0 IL_00b7: ldfld "System.Threading.CancellationTokenSource C.d__1.<>x__combinedTokens" IL_00bc: callvirt "System.Threading.CancellationToken System.Threading.CancellationTokenSource.Token.get" IL_00c1: stfld "System.Threading.CancellationToken C.d__1.token2" From aa2790fd325af4071924eccf62507efab56efcfc Mon Sep 17 00:00:00 2001 From: Henrique Ruschel Date: Fri, 28 Nov 2025 19:23:48 -0300 Subject: [PATCH 2/5] Reuse result and test --- .../StateMachineRewriter.cs | 4 +- .../Emit/CodeGen/CodeGenAsyncIteratorTests.cs | 109 ++++++++++++++++++ 2 files changed, 111 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs index 8a8091bd87d7c..4ffae3cc9e6d9 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.cs @@ -493,8 +493,8 @@ protected SynthesizedImplementationMethod GenerateIteratorGetEnumerator(MethodSy // result.parameter BoundExpression resultParameter = proxy.Replacement( F.Syntax, - static (stateMachineType, arg) => arg.F.Local(arg.resultVariable), - (F, resultVariable)); + static (stateMachineType, result) => result, + result); // this.parameterProxy BoundExpression parameterProxy = copySrc[parameter].Replacement( diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs index 6d65a66dfdcb5..b0e59f51c273c 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs @@ -11930,5 +11930,114 @@ .locals init (int V_0, } """); } + + [Fact] + [WorkItem(81467, "https://github.com/dotnet/roslyn/issues/81467")] + public void Multiple_GetAsyncEnumerator_Calls_Should_Dispose_Linked_Tokens() + { + string source = @" +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using static System.Console; + +class C +{ + static async IAsyncEnumerable Create([EnumeratorCancellation] CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + yield return default!; + + cancellationToken.ThrowIfCancellationRequested(); + yield return default!; + } + + static async Task Main() + { + using var cts = new CancellationTokenSource(); + + var currentIterator = Create(cts.Token); + + var currentIteratorType = currentIterator.GetType(); + var combinedTokensField = currentIteratorType.GetField(""<>x__combinedTokens"", BindingFlags.NonPublic | BindingFlags.Instance)!; + + var currentIteratorBug = await GenerateNonDisposedBug(currentIterator, () => + { + var combinedTokens = (CancellationTokenSource)combinedTokensField.GetValue(currentIterator)!; + + return combinedTokens; + }); + + var disposedValue = GetDisposedValue(currentIteratorBug); + + var disposedCount = 0; + for (int i = 0; i < disposedValue.Count; i++) + { + if (disposedValue[i]) + { + disposedCount++; + } + } + + Write(disposedCount); + } + + static async Task> GenerateNonDisposedBug(IAsyncEnumerable iterator, Func getCancellationTokenSource) + { + var result = new List(); + + using var cts1 = new CancellationTokenSource(); + using var cts2 = new CancellationTokenSource(); + using var cts3 = new CancellationTokenSource(); + + var it1 = iterator.GetAsyncEnumerator(cts1.Token); + result.Add(getCancellationTokenSource()); + + var it2 = iterator.GetAsyncEnumerator(cts2.Token); + result.Add(getCancellationTokenSource()); + + var it3 = iterator.GetAsyncEnumerator(cts3.Token); + result.Add(getCancellationTokenSource()); + + await it1.DisposeAsync(); + await it2.DisposeAsync(); + await it3.DisposeAsync(); + + return result; + } + + static List GetDisposedValue(List sources) + { + var result = new List(); + + for (int i = 0; i < sources.Count; i++) + { + result.Add(IsDisposed(sources[i])); + } + + return result; + } + + static bool IsDisposed(CancellationTokenSource cts) + { + try + { + var _ = cts.Token; + return false; + } + catch (ObjectDisposedException) + { + return true; + } + } +}"; + + var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "3"); + } } } From a62b3537188cb6b280092498e1b2ca1703a47ea8 Mon Sep 17 00:00:00 2001 From: Henrique Ruschel Date: Sat, 29 Nov 2025 23:48:15 -0300 Subject: [PATCH 3/5] Changed @" to """ --- .../CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs index b0e59f51c273c..4e8f7e93101a3 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs @@ -11935,7 +11935,7 @@ .locals init (int V_0, [WorkItem(81467, "https://github.com/dotnet/roslyn/issues/81467")] public void Multiple_GetAsyncEnumerator_Calls_Should_Dispose_Linked_Tokens() { - string source = @" + string source = """ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -12033,7 +12033,8 @@ static bool IsDisposed(CancellationTokenSource cts) return true; } } -}"; +} +"""; var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe); comp.VerifyDiagnostics(); From f86351c1f249e6933cfa8e53e8b9c11d25ca9f83 Mon Sep 17 00:00:00 2001 From: Henrique Ruschel Date: Sun, 30 Nov 2025 01:00:54 -0300 Subject: [PATCH 4/5] Forgot to remove "" inside --- .../CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs index 4e8f7e93101a3..45b050d504ef4 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs @@ -11962,7 +11962,7 @@ static async Task Main() var currentIterator = Create(cts.Token); var currentIteratorType = currentIterator.GetType(); - var combinedTokensField = currentIteratorType.GetField(""<>x__combinedTokens"", BindingFlags.NonPublic | BindingFlags.Instance)!; + var combinedTokensField = currentIteratorType.GetField("<>x__combinedTokens", BindingFlags.NonPublic | BindingFlags.Instance)!; var currentIteratorBug = await GenerateNonDisposedBug(currentIterator, () => { From 18a7646cab369f51b1064564f53f543cb9c6d356 Mon Sep 17 00:00:00 2001 From: Henrique Ruschel Date: Mon, 1 Dec 2025 21:23:46 -0300 Subject: [PATCH 5/5] Test Without using reflection --- .../Emit/CodeGen/CodeGenAsyncIteratorTests.cs | 106 ++++++------------ 1 file changed, 36 insertions(+), 70 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs index 45b050d504ef4..f40df15977f14 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs @@ -11935,110 +11935,76 @@ .locals init (int V_0, [WorkItem(81467, "https://github.com/dotnet/roslyn/issues/81467")] public void Multiple_GetAsyncEnumerator_Calls_Should_Dispose_Linked_Tokens() { + string testUtilsSource = """ +#pragma warning disable CS0436 // Type conflicts with imported type + +public static class TestUtils +{ + public static System.Threading.CancellationTokenSource CreateOriginalCancellationTokenSource() + { + return new System.Threading.CancellationTokenSource(); + } +} +"""; + var testUtilsComp = CreateCompilation(testUtilsSource, options: TestOptions.ReleaseDll); + var testUtilsRef = testUtilsComp.EmitToImageReference(aliases: ["TestUtils"]); + string source = """ -using System; +#pragma warning disable CS0436 // Type conflicts with imported type + +extern alias TestUtils; + using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Reflection; using System.Threading; using System.Threading.Tasks; -using static System.Console; class C { static async IAsyncEnumerable Create([EnumeratorCancellation] CancellationToken cancellationToken) { - cancellationToken.ThrowIfCancellationRequested(); yield return default!; - - cancellationToken.ThrowIfCancellationRequested(); yield return default!; } static async Task Main() { - using var cts = new CancellationTokenSource(); - - var currentIterator = Create(cts.Token); - - var currentIteratorType = currentIterator.GetType(); - var combinedTokensField = currentIteratorType.GetField("<>x__combinedTokens", BindingFlags.NonPublic | BindingFlags.Instance)!; - - var currentIteratorBug = await GenerateNonDisposedBug(currentIterator, () => - { - var combinedTokens = (CancellationTokenSource)combinedTokensField.GetValue(currentIterator)!; - - return combinedTokens; - }); + var cts = new CancellationTokenSource(); - var disposedValue = GetDisposedValue(currentIteratorBug); + var iterator = Create(cts.Token); - var disposedCount = 0; - for (int i = 0; i < disposedValue.Count; i++) - { - if (disposedValue[i]) - { - disposedCount++; - } - } - - Write(disposedCount); - } - - static async Task> GenerateNonDisposedBug(IAsyncEnumerable iterator, Func getCancellationTokenSource) - { - var result = new List(); - - using var cts1 = new CancellationTokenSource(); - using var cts2 = new CancellationTokenSource(); - using var cts3 = new CancellationTokenSource(); + var cts1 = new CancellationTokenSource(); + var cts2 = new CancellationTokenSource(); + var cts3 = new CancellationTokenSource(); var it1 = iterator.GetAsyncEnumerator(cts1.Token); - result.Add(getCancellationTokenSource()); - var it2 = iterator.GetAsyncEnumerator(cts2.Token); - result.Add(getCancellationTokenSource()); - var it3 = iterator.GetAsyncEnumerator(cts3.Token); - result.Add(getCancellationTokenSource()); await it1.DisposeAsync(); await it2.DisposeAsync(); await it3.DisposeAsync(); - - return result; - } - - static List GetDisposedValue(List sources) - { - var result = new List(); - - for (int i = 0; i < sources.Count; i++) - { - result.Add(IsDisposed(sources[i])); - } - - return result; } +} - static bool IsDisposed(CancellationTokenSource cts) +namespace System.Threading +{ + public class CancellationTokenSource { - try - { - var _ = cts.Token; - return false; - } - catch (ObjectDisposedException) - { - return true; - } + // This ensures that the tokens are always different (CancellationToken.Equals(CancellationToken) == false) to force the tokens to be linked + public CancellationToken Token => TestUtils::TestUtils.CreateOriginalCancellationTokenSource().Token; + public void Dispose() { System.Console.Write('d'); } + public void Cancel() { } + public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2) => new(); } } """; - var comp = CreateCompilationWithAsyncIterator(new[] { source, EnumeratorCancellationAttributeType }, options: TestOptions.DebugExe); + var comp = CreateCompilationWithTasksExtensions(new[] { source, EnumeratorCancellationAttributeType, AsyncStreamsTypes }, options: TestOptions.DebugExe, references: [testUtilsRef]); + comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "3"); + + CompileAndVerify(comp, expectedOutput: "ddd"); } } }