Skip to content

Commit

Permalink
Set state of enumerator object to "after" during disposal (#76090)
Browse files Browse the repository at this point in the history
  • Loading branch information
jcouv authored Dec 2, 2024
1 parent a8af111 commit c4598d2
Show file tree
Hide file tree
Showing 12 changed files with 2,821 additions and 751 deletions.
29 changes: 29 additions & 0 deletions docs/compilers/CSharp/Compiler Breaking Changes - DotNet 10.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,32 @@ class C
}
}
```

## Set state of enumerator object to "after" during disposal

***Introduced in Visual Studio 2022 version 17.13***

The state machine for enumerators incorrectly allowed resuming execution after the enumerator was disposed.
Now, `MoveNext()` on a disposed enumerator properly returns `false` without executing any more user code.

```csharp
var enumerator = C.GetEnumerator();

Console.Write(enumerator.MoveNext()); // prints True
Console.Write(enumerator.Current); // prints 1
enumerator.Dispose();

Console.Write(enumerator.MoveNext()); // now prints False
class C
{
public static IEnumerator<int> GetEnumerator()
{
yield return 1;
Console.Write("not executed after disposal")
yield return 2;
}
}
```

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Breaking changes in Roslyn after .NET 9.0.100 through .NET 10.0.100

This document lists known breaking changes in Roslyn after .NET 9 general release (.NET SDK version 9.0.100) through .NET 10 general release (.NET SDK version 10.0.100).

## Set state of enumerator object to "after" during disposal

***Introduced in Visual Studio 2022 version 17.13***

The state machine for enumerators incorrectly allowed resuming execution after the enumerator was disposed.
Now, `MoveNext()` on a disposed enumerator properly returns `false` without executing any more user code.

```vb
Imports System
Imports System.Collections.Generic

Module Program
Sub Main()
Dim enumerator = C.GetEnumerator()

Console.Write(enumerator.MoveNext()) ' prints True
Console.Write(enumerator.Current) ' prints 1

enumerator.Dispose()

Console.Write(enumerator.MoveNext()) ' now prints False
End Sub
End Module

Class C
Public Shared Iterator Function GetEnumerator() As IEnumerator(Of Integer)
Yield 1
Console.Write("not executed after disposal")
Yield 2
End Function
End Class
```

Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ internal void GenerateMoveNextAndDispose(BoundStatement body, SynthesizedImpleme
// nothing to finalize
var disposeBody = F.Block(
GenerateAllHoistedLocalsCleanup(),
F.Assignment(F.Field(F.This(), stateField), F.Literal(StateMachineState.FinishedState)),
F.Return());

F.CloseMethod(disposeBody);
Expand All @@ -177,6 +178,7 @@ internal void GenerateMoveNextAndDispose(BoundStatement body, SynthesizedImpleme
F.Assignment(F.Local(stateLocal), F.Field(F.This(), stateField)),
EmitFinallyFrame(rootFrame, state),
GenerateAllHoistedLocalsCleanup(),
F.Assignment(F.Field(F.This(), stateField), F.Literal(StateMachineState.FinishedState)),
F.Return());

F.CloseMethod(disposeBody);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10531,5 +10531,31 @@ public static async System.Collections.Generic.IAsyncEnumerable<S2> M(S2 p)
comp = CreateCompilation(source, options: TestOptions.UnsafeDebugDll, references: [libS2, missingLibS1], targetFramework: TargetFramework.Net80);
comp.VerifyEmitDiagnostics();
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/76078")]
public void StateAfterMoveNext_YieldReturn()
{
string src = """
var enumerator = C.Produce();
System.Console.Write(await enumerator.MoveNextAsync());
System.Console.Write(enumerator.Current);
await enumerator.DisposeAsync();
System.Console.Write(await enumerator.MoveNextAsync());
System.Console.Write(enumerator.Current is null ? " null" : throw null);
class C
{
public static async System.Collections.Generic.IAsyncEnumerator<string> Produce()
{
await System.Threading.Tasks.Task.CompletedTask;
yield return " one ";
yield return " two ";
}
}
""";
CompileAndVerify(src, expectedOutput: ExpectedOutput("True one False null"), verify: Verification.Skipped, targetFramework: TargetFramework.Net80).VerifyDiagnostics();
}
}
}
Loading

0 comments on commit c4598d2

Please sign in to comment.