Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clear hoisted locals when disposing iterator and async-iterator state machines #75908

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

jcouv
Copy link
Member

@jcouv jcouv commented Nov 14, 2024

Fixes #75666

Background

We already have logic to clear unmanaged hoisted locals when we exit scopes. But that logic has a number of limitations:

  1. we don't clear top-level locals on normal exit
  2. we don't clear any locals on yield break
  3. we don't clear any locals on early exit (from yield return, ie. foreach (var i in coll) { break; })
  4. we don't clear any locals on throw
static IEnumerable<int> Produce()
{
    {
        string hoistedNestedLocal = "";
        // existing clearing logic doesn't work for:
        // `yield break`
        // `yield return` and the caller breaks `foreach (var i in coll) { break; }`
        // `throw`
    }

    string hoistedTopLevelLocal = "";
    // existing clearing local doesn't work for:
    // normal exit (because a `yield break` is injected, see `FlowAnalysisPass.AppendImplicitReturn`, so the cleanup is added after a `yield break`)
    // `yield break`
    // `yield return` and the caller breaks `foreach (var i in coll) { break; }`
    // `throw`
}

So the current design only works for nested locals on normal exit from their scope.

Change

This PR clears the hoisted fields for all the locals (top-level and nested) in the part of the code that handles disposal: in Dispose method for iterators and at the end of MoveNext for async-iterators.
There is no need to add such clearing for async state machines, as it is non-trivial for a user to get a direct reference to those.

The proposed design fixes scenarios 1, 2, 3, and most of 4 (a throw during disposal still results in uncleared state).
This remains "best effort", as we don't want to add the overhead of an additional try/finally to handle that last scenario.

Look at the diff between commit 1 (baseline) and commit 2 (code change) to see the expectedOutput and IL changes.

@jcouv jcouv self-assigned this Nov 14, 2024
@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged Issues and PRs which have not yet been triaged by a lead label Nov 14, 2024
@jcouv jcouv force-pushed the clear-dispose branch 3 times, most recently from 3826321 to 9bb50d5 Compare November 14, 2024 20:28
@jcouv jcouv marked this pull request as ready for review November 14, 2024 22:00
@jcouv jcouv requested a review from a team as a code owner November 14, 2024 22:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Consider clearing state of iterator state machine when disposal completes
1 participant