Repository navigation
Conversation
Calling a function's __annotate__ can run arbitrary code that replaces __annotate__ or __annotations__, either directly or, on the free-threaded build, from another thread while the critical section is suspended. The callable was used through a borrowed reference for the duration of the call, and the compiler-generated annotations tuple was read through a borrowed reference while building the dict. Hold strong references to both. If __annotations__ was set while __annotate__ was running, keep that value instead of overwriting it with the result of the call.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Calling a function's
__annotate__can run arbitrary code that replaces__annotate__or__annotations__, either directly or, on the free-threaded build, from another thread while the critical section is suspended. The callable was used through a borrowed reference for the duration of the call, and the compiler-generated annotations tuple was read through a borrowed reference while building the dict. Hold strong references to both.If
__annotations__was set while__annotate__was running, keep that value instead of overwriting it with the result of the call.Note that this does not try to fix the "stale value" issue as reported in the bug. E.g. if
__annotations__get replaced when__annotate__runs, we don't re-run__annotate__again. I can't think of a legitimate reason someone would need that. Also, re-running could result infunc_get_annotation_dict()potentially looping forever.Why keep the existing value for
__annotations__? On the free-threaded build, two threads can readf.__annotations__for the first time concurrently. If the first thread's critical section is suspended while it runs__annotate__, the second thread can compute, cache and return its own dict. Overwriting that dict when the first thread resumes would lose any in-place changes the second thread's caller made (e.g. a decorator doingf.__annotations__["return"] = X). It would also free a dict that aPyFunction_GetAnnotations()caller may still hold as a borrowed reference. Keeping the existing value means that once a dict has been handed out, it stays cached until it is explicitly replaced.Creation of this PR was assisted by Claude Opus 5.5.
__annotations__can cache a stale result from__annotate__#159434