Skip to content

Commit 3d67f2c

Browse files
committed
Fix stackref leaks
1 parent 5c621a6 commit 3d67f2c

File tree

5 files changed

+64
-18
lines changed

5 files changed

+64
-18
lines changed

Include/internal/pycore_stackref.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ typedef union _PyStackRef {
6666
PyAPI_FUNC(PyObject *) _Py_stackref_get_object(_PyStackRef ref);
6767
PyAPI_FUNC(PyObject *) _Py_stackref_close(_PyStackRef ref);
6868
PyAPI_FUNC(_PyStackRef) _Py_stackref_create(PyObject *obj, const char *filename, int linenumber);
69+
PyAPI_FUNC(void) _Py_stackref_record_borrow(_PyStackRef ref, const char *filename, int linenumber);
6970
extern void _Py_stackref_associate(PyInterpreterState *interp, PyObject *obj, _PyStackRef ref);
7071

7172
static const _PyStackRef PyStackRef_NULL = { .index = 0 };
@@ -99,11 +100,14 @@ PyStackRef_IsNone(_PyStackRef ref)
99100
}
100101

101102
static inline PyObject *
102-
PyStackRef_AsPyObjectBorrow(_PyStackRef ref)
103+
_PyStackRef_AsPyObjectBorrow(_PyStackRef ref, const char *filename, int linenumber)
103104
{
105+
_Py_stackref_record_borrow(ref, filename, linenumber);
104106
return _Py_stackref_get_object(ref);
105107
}
106108

109+
#define PyStackRef_AsPyObjectBorrow(REF) _PyStackRef_AsPyObjectBorrow((REF), __FILE__, __LINE__)
110+
107111
static inline PyObject *
108112
PyStackRef_AsPyObjectSteal(_PyStackRef ref)
109113
{

Python/bytecodes.c

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,7 @@ dummy_func(
679679
assert(Py_REFCNT(left_o) >= 2);
680680
PyStackRef_CLOSE(left);
681681
DEAD(left);
682-
PyObject *temp = PyStackRef_AsPyObjectBorrow(*target_local);
682+
PyObject *temp = PyStackRef_AsPyObjectSteal(*target_local);
683683
PyUnicode_Append(&temp, right_o);
684684
*target_local = PyStackRef_FromPyObjectSteal(temp);
685685
PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc);
@@ -4476,17 +4476,17 @@ dummy_func(
44764476

44774477
op(_DO_CALL_FUNCTION_EX, (func_st, unused, callargs_st, kwargs_st if (oparg & 1) -- result)) {
44784478
PyObject *func = PyStackRef_AsPyObjectBorrow(func_st);
4479-
PyObject *callargs = PyStackRef_AsPyObjectBorrow(callargs_st);
4480-
PyObject *kwargs = PyStackRef_AsPyObjectBorrow(kwargs_st);
44814479

44824480
// DICT_MERGE is called before this opcode if there are kwargs.
44834481
// It converts all dict subtypes in kwargs into regular dicts.
4484-
assert(kwargs == NULL || PyDict_CheckExact(kwargs));
4485-
assert(PyTuple_CheckExact(callargs));
44864482
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_FUNCTION_EX, func);
44874483
PyObject *result_o;
44884484
assert(!_PyErr_Occurred(tstate));
44894485
if (opcode == INSTRUMENTED_CALL_FUNCTION_EX) {
4486+
PyObject *callargs = PyStackRef_AsPyObjectBorrow(callargs_st);
4487+
PyObject *kwargs = PyStackRef_AsPyObjectBorrow(kwargs_st);
4488+
assert(kwargs == NULL || PyDict_CheckExact(kwargs));
4489+
assert(PyTuple_CheckExact(callargs));
44904490
PyObject *arg = PyTuple_GET_SIZE(callargs) > 0 ?
44914491
PyTuple_GET_ITEM(callargs, 0) : &_PyInstrumentation_MISSING;
44924492
int err = _Py_call_instrumentation_2args(
@@ -4517,7 +4517,10 @@ dummy_func(
45174517
if (Py_TYPE(func) == &PyFunction_Type &&
45184518
tstate->interp->eval_frame == NULL &&
45194519
((PyFunctionObject *)func)->vectorcall == _PyFunction_Vectorcall) {
4520+
PyObject *callargs = PyStackRef_AsPyObjectSteal(callargs_st);
45204521
assert(PyTuple_CheckExact(callargs));
4522+
PyObject *kwargs = PyStackRef_IsNull(kwargs_st) ? NULL : PyStackRef_AsPyObjectSteal(kwargs_st);
4523+
assert(kwargs == NULL || PyDict_CheckExact(kwargs));
45214524
Py_ssize_t nargs = PyTuple_GET_SIZE(callargs);
45224525
int code_flags = ((PyCodeObject *)PyFunction_GET_CODE(func))->co_flags;
45234526
PyObject *locals = code_flags & CO_OPTIMIZED ? NULL : Py_NewRef(PyFunction_GET_GLOBALS(func));
@@ -4535,6 +4538,10 @@ dummy_func(
45354538
frame->return_offset = 1;
45364539
DISPATCH_INLINED(new_frame);
45374540
}
4541+
PyObject *callargs = PyStackRef_AsPyObjectBorrow(callargs_st);
4542+
assert(PyTuple_CheckExact(callargs));
4543+
PyObject *kwargs = PyStackRef_AsPyObjectBorrow(kwargs_st);
4544+
assert(kwargs == NULL || PyDict_CheckExact(kwargs));
45384545
result_o = PyObject_Call(func, callargs, kwargs);
45394546
}
45404547
PyStackRef_XCLOSE(kwargs_st);

Python/executor_cases.c.h

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/generated_cases.c.h

Lines changed: 15 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/stackrefs.c

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ typedef struct _table_entry {
1717
const char *classname;
1818
const char *filename;
1919
int linenumber;
20+
const char *filename_borrow;
21+
int linenumber_borrow;
2022
} TableEntry;
2123

2224
TableEntry *
@@ -30,6 +32,7 @@ make_table_entry(PyObject *obj, const char *filename, int linenumber)
3032
result->classname = Py_TYPE(obj)->tp_name;
3133
result->filename = filename;
3234
result->linenumber = linenumber;
35+
result->filename_borrow = NULL;
3336
return result;
3437
}
3538

@@ -92,6 +95,22 @@ _Py_stackref_create(PyObject *obj, const char *filename, int linenumber)
9295
return (_PyStackRef){ .index = new_id };
9396
}
9497

98+
void
99+
_Py_stackref_record_borrow(_PyStackRef ref, const char *filename, int linenumber)
100+
{
101+
if (ref.index <= 3) {
102+
return;
103+
}
104+
PyInterpreterState *interp = PyInterpreterState_Get();
105+
TableEntry *entry = _Py_hashtable_get(interp->stackref_debug_table, (void *)ref.index);
106+
if (entry == NULL) {
107+
_Py_FatalErrorFormat(__func__, "Invalid StackRef with ID %" PRIu64 "\n", (void *)ref.index);
108+
}
109+
entry->filename_borrow = filename;
110+
entry->linenumber_borrow = linenumber;
111+
}
112+
113+
95114
void
96115
_Py_stackref_associate(PyInterpreterState *interp, PyObject *obj, _PyStackRef ref)
97116
{
@@ -108,20 +127,29 @@ _Py_stackref_associate(PyInterpreterState *interp, PyObject *obj, _PyStackRef re
108127

109128

110129
static int
111-
report_leak(_Py_hashtable_t *ht, const void *key, const void *value, void *user_data)
130+
report_leak(_Py_hashtable_t *ht, const void *key, const void *value, void *leak)
112131
{
113132
TableEntry *entry = (TableEntry *)value;
114133
if (!_Py_IsStaticImmortal(entry->obj)) {
115-
printf("Stackref leak. Refers to instance of %s at %p. Created at %s:%d\n",
134+
*(int *)leak = 1;
135+
printf("Stackref leak. Refers to instance of %s at %p. Created at %s:%d",
116136
entry->classname, entry->obj, entry->filename, entry->linenumber);
137+
if (entry->filename_borrow != NULL) {
138+
printf(". Last borrow at %s:%d",entry->filename_borrow, entry->linenumber_borrow);
139+
}
140+
printf("\n");
117141
}
118142
return 0;
119143
}
120144

121145
void
122146
_Py_stackref_report_leaks(PyInterpreterState *interp)
123147
{
124-
_Py_hashtable_foreach(interp->stackref_debug_table, report_leak, NULL);
148+
int leak = 0;
149+
_Py_hashtable_foreach(interp->stackref_debug_table, report_leak, &leak);
150+
if (leak) {
151+
Py_FatalError("Stackrefs leaked.");
152+
}
125153
}
126154

127155
#endif

0 commit comments

Comments
 (0)