Skip to content

Commit 78d50e9

Browse files
authored
GH-127705: better double free message. (GH-130785)
* Add location information when accessing already closed stackref * Add #def option to track closed stackrefs to provide precise information for use after free and double frees.
1 parent f33d21e commit 78d50e9

File tree

5 files changed

+69
-20
lines changed

5 files changed

+69
-20
lines changed

Include/internal/pycore_interp.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,10 @@ struct _is {
296296

297297
#if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
298298
uint64_t next_stackref;
299-
_Py_hashtable_t *stackref_debug_table;
299+
_Py_hashtable_t *open_stackrefs_table;
300+
# ifdef Py_STACKREF_CLOSE_DEBUG
301+
_Py_hashtable_t *closed_stackrefs_table;
302+
# endif
300303
#endif
301304
};
302305

Include/internal/pycore_stackref.h

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ extern "C" {
77
// Define this to get precise tracking of stackrefs.
88
// #define Py_STACKREF_DEBUG 1
99

10+
// Define this to get precise tracking of closed stackrefs.
11+
// This will use unbounded memory, as it can only grow.
12+
// Use this to track double closes in short-lived programs
13+
// #define Py_STACKREF_CLOSE_DEBUG 1
14+
1015
#ifndef Py_BUILD_CORE
1116
# error "this header requires Py_BUILD_CORE define"
1217
#endif
@@ -64,7 +69,7 @@ typedef union _PyStackRef {
6469
#define Py_TAG_BITS 0
6570

6671
PyAPI_FUNC(PyObject *) _Py_stackref_get_object(_PyStackRef ref);
67-
PyAPI_FUNC(PyObject *) _Py_stackref_close(_PyStackRef ref);
72+
PyAPI_FUNC(PyObject *) _Py_stackref_close(_PyStackRef ref, const char *filename, int linenumber);
6873
PyAPI_FUNC(_PyStackRef) _Py_stackref_create(PyObject *obj, const char *filename, int linenumber);
6974
PyAPI_FUNC(void) _Py_stackref_record_borrow(_PyStackRef ref, const char *filename, int linenumber);
7075
extern void _Py_stackref_associate(PyInterpreterState *interp, PyObject *obj, _PyStackRef ref);
@@ -111,10 +116,11 @@ _PyStackRef_AsPyObjectBorrow(_PyStackRef ref, const char *filename, int linenumb
111116
#define PyStackRef_AsPyObjectBorrow(REF) _PyStackRef_AsPyObjectBorrow((REF), __FILE__, __LINE__)
112117

113118
static inline PyObject *
114-
PyStackRef_AsPyObjectSteal(_PyStackRef ref)
119+
_PyStackRef_AsPyObjectSteal(_PyStackRef ref, const char *filename, int linenumber)
115120
{
116-
return _Py_stackref_close(ref);
121+
return _Py_stackref_close(ref, filename, linenumber);
117122
}
123+
#define PyStackRef_AsPyObjectSteal(REF) _PyStackRef_AsPyObjectSteal((REF), __FILE__, __LINE__)
118124

119125
static inline _PyStackRef
120126
_PyStackRef_FromPyObjectNew(PyObject *obj, const char *filename, int linenumber)
@@ -140,11 +146,12 @@ _PyStackRef_FromPyObjectImmortal(PyObject *obj, const char *filename, int linenu
140146
#define PyStackRef_FromPyObjectImmortal(obj) _PyStackRef_FromPyObjectImmortal(_PyObject_CAST(obj), __FILE__, __LINE__)
141147

142148
static inline void
143-
PyStackRef_CLOSE(_PyStackRef ref)
149+
_PyStackRef_CLOSE(_PyStackRef ref, const char *filename, int linenumber)
144150
{
145-
PyObject *obj = _Py_stackref_close(ref);
151+
PyObject *obj = _Py_stackref_close(ref, filename, linenumber);
146152
Py_DECREF(obj);
147153
}
154+
#define PyStackRef_CLOSE(REF) _PyStackRef_CLOSE((REF), __FILE__, __LINE__)
148155

149156
static inline _PyStackRef
150157
_PyStackRef_DUP(_PyStackRef ref, const char *filename, int linenumber)

Objects/object.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2984,7 +2984,7 @@ _Py_Dealloc(PyObject *op)
29842984
destructor dealloc = type->tp_dealloc;
29852985
#ifdef Py_DEBUG
29862986
PyThreadState *tstate = _PyThreadState_GET();
2987-
#ifndef Py_GIL_DISABLED
2987+
#if !defined(Py_GIL_DISABLED) && !defined(Py_STACKREF_DEBUG)
29882988
/* This assertion doesn't hold for the free-threading build, as
29892989
* PyStackRef_CLOSE_SPECIALIZED is not implemented */
29902990
assert(tstate->current_frame == NULL || tstate->current_frame->stackpointer != NULL);

Python/pystate.c

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -676,13 +676,22 @@ init_interpreter(PyInterpreterState *interp,
676676
.malloc = malloc,
677677
.free = free,
678678
};
679-
interp->stackref_debug_table = _Py_hashtable_new_full(
679+
interp->open_stackrefs_table = _Py_hashtable_new_full(
680680
_Py_hashtable_hash_ptr,
681681
_Py_hashtable_compare_direct,
682682
NULL,
683683
NULL,
684684
&alloc
685685
);
686+
# ifdef Py_STACKREF_CLOSE_DEBUG
687+
interp->closed_stackrefs_table = _Py_hashtable_new_full(
688+
_Py_hashtable_hash_ptr,
689+
_Py_hashtable_compare_direct,
690+
NULL,
691+
NULL,
692+
&alloc
693+
);
694+
# endif
686695
_Py_stackref_associate(interp, Py_None, PyStackRef_None);
687696
_Py_stackref_associate(interp, Py_False, PyStackRef_False);
688697
_Py_stackref_associate(interp, Py_True, PyStackRef_True);
@@ -901,9 +910,13 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
901910
Py_CLEAR(interp->builtins);
902911

903912
#if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
913+
# ifdef Py_STACKREF_CLOSE_DEBUG
914+
_Py_hashtable_destroy(interp->closed_stackrefs_table);
915+
interp->closed_stackrefs_table = NULL;
916+
# endif
904917
_Py_stackref_report_leaks(interp);
905-
_Py_hashtable_destroy(interp->stackref_debug_table);
906-
interp->stackref_debug_table = NULL;
918+
_Py_hashtable_destroy(interp->open_stackrefs_table);
919+
interp->open_stackrefs_table = NULL;
907920
#endif
908921

909922
if (tstate->interp == interp) {

Python/stackrefs.c

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,33 +47,51 @@ _Py_stackref_get_object(_PyStackRef ref)
4747
if (ref.index >= interp->next_stackref) {
4848
_Py_FatalErrorFormat(__func__, "Garbled stack ref with ID %" PRIu64 "\n", ref.index);
4949
}
50-
TableEntry *entry = _Py_hashtable_get(interp->stackref_debug_table, (void *)ref.index);
50+
TableEntry *entry = _Py_hashtable_get(interp->open_stackrefs_table, (void *)ref.index);
5151
if (entry == NULL) {
5252
_Py_FatalErrorFormat(__func__, "Accessing closed stack ref with ID %" PRIu64 "\n", ref.index);
5353
}
5454
return entry->obj;
5555
}
5656

5757
PyObject *
58-
_Py_stackref_close(_PyStackRef ref)
58+
_Py_stackref_close(_PyStackRef ref, const char *filename, int linenumber)
5959
{
6060
PyInterpreterState *interp = PyInterpreterState_Get();
6161
if (ref.index >= interp->next_stackref) {
62-
_Py_FatalErrorFormat(__func__, "Garbled stack ref with ID %" PRIu64 "\n", ref.index);
62+
_Py_FatalErrorFormat(__func__, "Invalid StackRef with ID %" PRIu64 " at %s:%d\n", (void *)ref.index, filename, linenumber);
63+
6364
}
6465
PyObject *obj;
6566
if (ref.index <= LAST_PREDEFINED_STACKREF_INDEX) {
6667
// Pre-allocated reference to None, False or True -- Do not clear
67-
TableEntry *entry = _Py_hashtable_get(interp->stackref_debug_table, (void *)ref.index);
68+
TableEntry *entry = _Py_hashtable_get(interp->open_stackrefs_table, (void *)ref.index);
6869
obj = entry->obj;
6970
}
7071
else {
71-
TableEntry *entry = _Py_hashtable_steal(interp->stackref_debug_table, (void *)ref.index);
72+
TableEntry *entry = _Py_hashtable_steal(interp->open_stackrefs_table, (void *)ref.index);
7273
if (entry == NULL) {
74+
#ifdef Py_STACKREF_CLOSE_DEBUG
75+
entry = _Py_hashtable_get(interp->closed_stackrefs_table, (void *)ref.index);
76+
if (entry != NULL) {
77+
_Py_FatalErrorFormat(__func__,
78+
"Double close of ref ID %" PRIu64 " at %s:%d. Referred to instance of %s at %p. Closed at %s:%d\n",
79+
(void *)ref.index, filename, linenumber, entry->classname, entry->obj, entry->filename, entry->linenumber);
80+
}
81+
#endif
7382
_Py_FatalErrorFormat(__func__, "Invalid StackRef with ID %" PRIu64 "\n", (void *)ref.index);
7483
}
7584
obj = entry->obj;
7685
free(entry);
86+
#ifdef Py_STACKREF_CLOSE_DEBUG
87+
TableEntry *close_entry = make_table_entry(obj, filename, linenumber);
88+
if (close_entry == NULL) {
89+
Py_FatalError("No memory left for stackref debug table");
90+
}
91+
if (_Py_hashtable_set(interp->closed_stackrefs_table, (void *)ref.index, close_entry) < 0) {
92+
Py_FatalError("No memory left for stackref debug table");
93+
}
94+
#endif
7795
}
7896
return obj;
7997
}
@@ -90,7 +108,7 @@ _Py_stackref_create(PyObject *obj, const char *filename, int linenumber)
90108
if (entry == NULL) {
91109
Py_FatalError("No memory left for stackref debug table");
92110
}
93-
if (_Py_hashtable_set(interp->stackref_debug_table, (void *)new_id, entry) < 0) {
111+
if (_Py_hashtable_set(interp->open_stackrefs_table, (void *)new_id, entry) < 0) {
94112
Py_FatalError("No memory left for stackref debug table");
95113
}
96114
return (_PyStackRef){ .index = new_id };
@@ -103,9 +121,17 @@ _Py_stackref_record_borrow(_PyStackRef ref, const char *filename, int linenumber
103121
return;
104122
}
105123
PyInterpreterState *interp = PyInterpreterState_Get();
106-
TableEntry *entry = _Py_hashtable_get(interp->stackref_debug_table, (void *)ref.index);
124+
TableEntry *entry = _Py_hashtable_get(interp->open_stackrefs_table, (void *)ref.index);
107125
if (entry == NULL) {
108-
_Py_FatalErrorFormat(__func__, "Invalid StackRef with ID %" PRIu64 "\n", (void *)ref.index);
126+
#ifdef Py_STACKREF_CLOSE_DEBUG
127+
entry = _Py_hashtable_get(interp->closed_stackrefs_table, (void *)ref.index);
128+
if (entry != NULL) {
129+
_Py_FatalErrorFormat(__func__,
130+
"Borrow of closed ref ID %" PRIu64 " at %s:%d. Referred to instance of %s at %p. Closed at %s:%d\n",
131+
(void *)ref.index, filename, linenumber, entry->classname, entry->obj, entry->filename, entry->linenumber);
132+
}
133+
#endif
134+
_Py_FatalErrorFormat(__func__, "Invalid StackRef with ID %" PRIu64 " at %s:%d\n", (void *)ref.index, filename, linenumber);
109135
}
110136
entry->filename_borrow = filename;
111137
entry->linenumber_borrow = linenumber;
@@ -121,7 +147,7 @@ _Py_stackref_associate(PyInterpreterState *interp, PyObject *obj, _PyStackRef re
121147
if (entry == NULL) {
122148
Py_FatalError("No memory left for stackref debug table");
123149
}
124-
if (_Py_hashtable_set(interp->stackref_debug_table, (void *)ref.index, (void *)entry) < 0) {
150+
if (_Py_hashtable_set(interp->open_stackrefs_table, (void *)ref.index, (void *)entry) < 0) {
125151
Py_FatalError("No memory left for stackref debug table");
126152
}
127153
}
@@ -147,7 +173,7 @@ void
147173
_Py_stackref_report_leaks(PyInterpreterState *interp)
148174
{
149175
int leak = 0;
150-
_Py_hashtable_foreach(interp->stackref_debug_table, report_leak, &leak);
176+
_Py_hashtable_foreach(interp->open_stackrefs_table, report_leak, &leak);
151177
if (leak) {
152178
Py_FatalError("Stackrefs leaked.");
153179
}

0 commit comments

Comments
 (0)