Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 35.2k
gh-131527: Stackref debug borrow checker#140599
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
Merged
mpage
merged 13 commits into
python:main
from
efimov-mikhail:issue-131527-stackref_debug_borrow_checkerNov 5, 2025
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
e615d9f
MakeHeapSafe rework, _Py_stackref_set_borrowed_from added (NOP)
efimov-mikhail 849369d
Format fixes, changes in entry struct
efimov-mikhail 3458162
ErrorFormat improvements, checks for borrows > 0
efimov-mikhail c2bcde8
_Py_stackref_set_borrowed_from implementation
efimov-mikhail 983affc
NEWS entry
efimov-mikhail 83aef9f
Add dummy _Py_stackref_copy_borrowed_from
efimov-mikhail 1f2ce89
Introduce _Py_stackref_get_borrowed_from
efimov-mikhail 9124366
Include fix
efimov-mikhail 7b4f82e
Refactor _Py_stackref_get_borrowed_from
efimov-mikhail 8c28c68
Error message wording
efimov-mikhail bd6ba53
Assert in _Py_stackref_set_borrowed_from
efimov-mikhail b0a2bd9
Merge branch 'main' into issue-131527-stackref_debug_borrow_checker
efimov-mikhail ce5c87f
Merge branch 'main' into issue-131527-stackref_debug_borrow_checker
mpage File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
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
2 changes: 2 additions & 0 deletions
2 Misc/NEWS.d/next/Core_and_Builtins/2025-10-25-21-31-43.gh-issue-131527.V-JVNP.rst
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Dynamic borrow checking for stackrefs is added to ``Py_STACKREF_DEBUG`` | ||
| mode. Patch by Mikhail Efimov. |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -19,6 +19,8 @@ typedef struct _table_entry { | ||
| int linenumber; | ||
| const char *filename_borrow; | ||
| int linenumber_borrow; | ||
| int borrows; | ||
| _PyStackRef borrowed_from; | ||
| } TableEntry; | ||
| TableEntry * | ||
| @@ -34,6 +36,8 @@ make_table_entry(PyObject *obj, const char *filename, int linenumber) | ||
| result->linenumber = linenumber; | ||
| result->filename_borrow = NULL; | ||
| result->linenumber_borrow = 0; | ||
| result->borrows = 0; | ||
| result->borrowed_from = PyStackRef_NULL; | ||
| return result; | ||
| } | ||
| @@ -47,11 +51,13 @@ _Py_stackref_get_object(_PyStackRef ref) | ||
| PyInterpreterState *interp = PyInterpreterState_Get(); | ||
| assert(interp != NULL); | ||
| if (ref.index >= interp->next_stackref) { | ||
| _Py_FatalErrorFormat(__func__, "Garbled stack ref with ID %" PRIu64 "\n", ref.index); | ||
| _Py_FatalErrorFormat(__func__, | ||
| "Garbled stack ref with ID %" PRIu64 "\n", ref.index); | ||
| } | ||
| TableEntry *entry = _Py_hashtable_get(interp->open_stackrefs_table, (void *)ref.index); | ||
| if (entry == NULL) { | ||
| _Py_FatalErrorFormat(__func__, "Accessing closed stack ref with ID %" PRIu64 "\n", ref.index); | ||
| _Py_FatalErrorFormat(__func__, | ||
| "Accessing closed stack ref with ID %" PRIu64 "\n", ref.index); | ||
| } | ||
| return entry->obj; | ||
| } | ||
| @@ -68,13 +74,16 @@ _Py_stackref_close(_PyStackRef ref, const char *filename, int linenumber) | ||
| assert(!PyStackRef_IsError(ref)); | ||
| PyInterpreterState *interp = PyInterpreterState_Get(); | ||
| if (ref.index >= interp->next_stackref) { | ||
| _Py_FatalErrorFormat(__func__, "Invalid StackRef with ID %" PRIu64 " at %s:%d\n", (void *)ref.index, filename, linenumber); | ||
| _Py_FatalErrorFormat(__func__, | ||
| "Invalid StackRef with ID %" PRIu64 " at %s:%d\n", | ||
| ref.index, filename, linenumber); | ||
| } | ||
| PyObject *obj; | ||
| if (ref.index < INITIAL_STACKREF_INDEX) { | ||
| if (ref.index == 0) { | ||
| _Py_FatalErrorFormat(__func__, "Passing NULL to PyStackRef_CLOSE at %s:%d\n", filename, linenumber); | ||
| _Py_FatalErrorFormat(__func__, | ||
| "Passing NULL to _Py_stackref_close at %s:%d\n", | ||
| filename, linenumber); | ||
| } | ||
| // Pre-allocated reference to None, False or True -- Do not clear | ||
| TableEntry *entry = _Py_hashtable_get(interp->open_stackrefs_table, (void *)ref.index); | ||
| @@ -88,10 +97,27 @@ _Py_stackref_close(_PyStackRef ref, const char *filename, int linenumber) | ||
| if (entry != NULL) { | ||
| _Py_FatalErrorFormat(__func__, | ||
| "Double close of ref ID %" PRIu64 " at %s:%d. Referred to instance of %s at %p. Closed at %s:%d\n", | ||
| (void *)ref.index, filename, linenumber, entry->classname, entry->obj, entry->filename, entry->linenumber); | ||
| ref.index, filename, linenumber, entry->classname, entry->obj, entry->filename, entry->linenumber); | ||
| } | ||
| #endif | ||
| _Py_FatalErrorFormat(__func__, "Invalid StackRef with ID %" PRIu64 "\n", (void *)ref.index); | ||
| _Py_FatalErrorFormat(__func__, | ||
| "Invalid StackRef with ID %" PRIu64 " at %s:%d\n", | ||
| ref.index, filename, linenumber); | ||
| } | ||
| if (!PyStackRef_IsNull(entry->borrowed_from)) { | ||
| _PyStackRef borrowed_from = entry->borrowed_from; | ||
| TableEntry *entry_borrowed = _Py_hashtable_get(interp->open_stackrefs_table, (void *)borrowed_from.index); | ||
| if (entry_borrowed == NULL) { | ||
| _Py_FatalErrorFormat(__func__, | ||
| "Invalid borrowed StackRef with ID %" PRIu64 " at %s:%d\n", | ||
| borrowed_from.index, filename, linenumber); | ||
| } | ||
| entry_borrowed->borrows--; | ||
| } | ||
| if (entry->borrows > 0) { | ||
| _Py_FatalErrorFormat(__func__, | ||
| "StackRef with ID %" PRIu64 " closed with %d borrowed refs at %s:%d. Opened at %s:%d\n", | ||
| ref.index, entry->borrows, filename, linenumber, entry->filename, entry->linenumber); | ||
| } | ||
| obj = entry->obj; | ||
| free(entry); | ||
| @@ -143,15 +169,62 @@ _Py_stackref_record_borrow(_PyStackRef ref, const char *filename, int linenumber | ||
| if (entry != NULL) { | ||
| _Py_FatalErrorFormat(__func__, | ||
| "Borrow of closed ref ID %" PRIu64 " at %s:%d. Referred to instance of %s at %p. Closed at %s:%d\n", | ||
| (void *)ref.index, filename, linenumber, entry->classname, entry->obj, entry->filename, entry->linenumber); | ||
| ref.index, filename, linenumber, entry->classname, entry->obj, entry->filename, entry->linenumber); | ||
| } | ||
| #endif | ||
| _Py_FatalErrorFormat(__func__, "Invalid StackRef with ID %" PRIu64 " at %s:%d\n", (void *)ref.index, filename, linenumber); | ||
| _Py_FatalErrorFormat(__func__, | ||
| "Invalid StackRef with ID %" PRIu64 " at %s:%d\n", | ||
| ref.index, filename, linenumber); | ||
| } | ||
| entry->filename_borrow = filename; | ||
| entry->linenumber_borrow = linenumber; | ||
| } | ||
| _PyStackRef | ||
| _Py_stackref_get_borrowed_from(_PyStackRef ref, const char *filename, int linenumber) | ||
| { | ||
| assert(!PyStackRef_IsError(ref)); | ||
| PyInterpreterState *interp = PyInterpreterState_Get(); | ||
| TableEntry *entry = _Py_hashtable_get(interp->open_stackrefs_table, (void *)ref.index); | ||
| if (entry == NULL) { | ||
| _Py_FatalErrorFormat(__func__, | ||
| "Invalid StackRef with ID %" PRIu64 " at %s:%d\n", | ||
| ref.index, filename, linenumber); | ||
| } | ||
| return entry->borrowed_from; | ||
| } | ||
| // This function should be used no more than once per ref. | ||
efimov-mikhail marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| void | ||
| _Py_stackref_set_borrowed_from(_PyStackRef ref, _PyStackRef borrowed_from, const char *filename, int linenumber) | ||
| { | ||
| assert(!PyStackRef_IsError(ref)); | ||
| PyInterpreterState *interp = PyInterpreterState_Get(); | ||
| TableEntry *entry = _Py_hashtable_get(interp->open_stackrefs_table, (void *)ref.index); | ||
| if (entry == NULL) { | ||
| _Py_FatalErrorFormat(__func__, | ||
| "Invalid StackRef (ref) with ID %" PRIu64 " at %s:%d\n", | ||
| ref.index, filename, linenumber); | ||
| } | ||
| assert(PyStackRef_IsNull(entry->borrowed_from)); | ||
| if (PyStackRef_IsNull(borrowed_from)) { | ||
| return; | ||
| } | ||
| TableEntry *entry_borrowed = _Py_hashtable_get(interp->open_stackrefs_table, (void *)borrowed_from.index); | ||
| if (entry_borrowed == NULL) { | ||
| _Py_FatalErrorFormat(__func__, | ||
| "Invalid StackRef (borrowed_from) with ID %" PRIu64 " at %s:%d\n", | ||
| borrowed_from.index, filename, linenumber); | ||
| } | ||
| entry->borrowed_from = borrowed_from; | ||
| entry_borrowed->borrows++; | ||
| } | ||
| void | ||
| _Py_stackref_associate(PyInterpreterState *interp, PyObject *obj, _PyStackRef ref) | ||
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
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.
Uh oh!
There was an error while loading. Please reload this page.