Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 128cc47

Browse files
authored
GH-127705: Add debug mode for _PyStackRefs inspired by HPy debug mode (GH-128121)
1 parent 78ffba4 commit 128cc47

12 files changed

+395
-33
lines changed

Include/internal/pycore_interp.h

+6
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ extern "C" {
3434
#include "pycore_optimizer.h" // _PyOptimizerObject
3535
#include "pycore_obmalloc.h" // struct _obmalloc_state
3636
#include "pycore_qsbr.h" // struct _qsbr_state
37+
#include "pycore_stackref.h" // Py_STACKREF_DEBUG
3738
#include "pycore_tstate.h" // _PyThreadStateImpl
3839
#include "pycore_tuple.h" // struct _Py_tuple_state
3940
#include "pycore_uniqueid.h" // struct _Py_unique_id_pool
@@ -285,6 +286,11 @@ struct _is {
285286
_PyThreadStateImpl _initial_thread;
286287
// _initial_thread should be the last field of PyInterpreterState.
287288
// See https://github.com/python/cpython/issues/127117.
289+
290+
#if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
291+
uint64_t next_stackref;
292+
_Py_hashtable_t *stackref_debug_table;
293+
#endif
288294
};
289295

290296

Include/internal/pycore_stackref.h

+113
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
extern "C" {
55
#endif
66

7+
// Define this to get precise tracking of stackrefs.
8+
// #define Py_STACKREF_DEBUG 1
9+
710
#ifndef Py_BUILD_CORE
811
# error "this header requires Py_BUILD_CORE define"
912
#endif
@@ -49,6 +52,113 @@ extern "C" {
4952
CPython refcounting operations on it!
5053
*/
5154

55+
56+
#if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG)
57+
58+
59+
60+
typedef union _PyStackRef {
61+
uint64_t index;
62+
} _PyStackRef;
63+
64+
#define Py_TAG_BITS 0
65+
66+
PyAPI_FUNC(PyObject *) _Py_stackref_get_object(_PyStackRef ref);
67+
PyAPI_FUNC(PyObject *) _Py_stackref_close(_PyStackRef ref);
68+
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);
70+
extern void _Py_stackref_associate(PyInterpreterState *interp, PyObject *obj, _PyStackRef ref);
71+
72+
static const _PyStackRef PyStackRef_NULL = { .index = 0 };
73+
74+
#define PyStackRef_None ((_PyStackRef){ .index = 1 } )
75+
#define PyStackRef_False ((_PyStackRef){ .index = 2 })
76+
#define PyStackRef_True ((_PyStackRef){ .index = 3 })
77+
78+
#define LAST_PREDEFINED_STACKREF_INDEX 3
79+
80+
static inline int
81+
PyStackRef_IsNull(_PyStackRef ref)
82+
{
83+
return ref.index == 0;
84+
}
85+
86+
static inline int
87+
PyStackRef_IsTrue(_PyStackRef ref)
88+
{
89+
return _Py_stackref_get_object(ref) == Py_True;
90+
}
91+
92+
static inline int
93+
PyStackRef_IsFalse(_PyStackRef ref)
94+
{
95+
return _Py_stackref_get_object(ref) == Py_False;
96+
}
97+
98+
static inline int
99+
PyStackRef_IsNone(_PyStackRef ref)
100+
{
101+
return _Py_stackref_get_object(ref) == Py_None;
102+
}
103+
104+
static inline PyObject *
105+
_PyStackRef_AsPyObjectBorrow(_PyStackRef ref, const char *filename, int linenumber)
106+
{
107+
_Py_stackref_record_borrow(ref, filename, linenumber);
108+
return _Py_stackref_get_object(ref);
109+
}
110+
111+
#define PyStackRef_AsPyObjectBorrow(REF) _PyStackRef_AsPyObjectBorrow((REF), __FILE__, __LINE__)
112+
113+
static inline PyObject *
114+
PyStackRef_AsPyObjectSteal(_PyStackRef ref)
115+
{
116+
return _Py_stackref_close(ref);
117+
}
118+
119+
static inline _PyStackRef
120+
_PyStackRef_FromPyObjectNew(PyObject *obj, const char *filename, int linenumber)
121+
{
122+
Py_INCREF(obj);
123+
return _Py_stackref_create(obj, filename, linenumber);
124+
}
125+
#define PyStackRef_FromPyObjectNew(obj) _PyStackRef_FromPyObjectNew(_PyObject_CAST(obj), __FILE__, __LINE__)
126+
127+
static inline _PyStackRef
128+
_PyStackRef_FromPyObjectSteal(PyObject *obj, const char *filename, int linenumber)
129+
{
130+
return _Py_stackref_create(obj, filename, linenumber);
131+
}
132+
#define PyStackRef_FromPyObjectSteal(obj) _PyStackRef_FromPyObjectSteal(_PyObject_CAST(obj), __FILE__, __LINE__)
133+
134+
static inline _PyStackRef
135+
_PyStackRef_FromPyObjectImmortal(PyObject *obj, const char *filename, int linenumber)
136+
{
137+
assert(_Py_IsImmortal(obj));
138+
return _Py_stackref_create(obj, filename, linenumber);
139+
}
140+
#define PyStackRef_FromPyObjectImmortal(obj) _PyStackRef_FromPyObjectImmortal(_PyObject_CAST(obj), __FILE__, __LINE__)
141+
142+
static inline void
143+
PyStackRef_CLOSE(_PyStackRef ref)
144+
{
145+
PyObject *obj = _Py_stackref_close(ref);
146+
Py_DECREF(obj);
147+
}
148+
149+
static inline _PyStackRef
150+
_PyStackRef_DUP(_PyStackRef ref, const char *filename, int linenumber)
151+
{
152+
PyObject *obj = _Py_stackref_get_object(ref);
153+
Py_INCREF(obj);
154+
return _Py_stackref_create(obj, filename, linenumber);
155+
}
156+
#define PyStackRef_DUP(REF) _PyStackRef_DUP(REF, __FILE__, __LINE__)
157+
158+
#define PyStackRef_CLOSE_SPECIALIZED(stackref, dealloc) PyStackRef_CLOSE(stackref)
159+
160+
#else
161+
52162
typedef union _PyStackRef {
53163
uintptr_t bits;
54164
} _PyStackRef;
@@ -200,12 +310,15 @@ static const _PyStackRef PyStackRef_NULL = { .bits = 0 };
200310
#define PyStackRef_IsTrue(ref) (PyStackRef_AsPyObjectBorrow(ref) == Py_True)
201311
#define PyStackRef_IsFalse(ref) (PyStackRef_AsPyObjectBorrow(ref) == Py_False)
202312

313+
#endif
314+
203315
// Converts a PyStackRef back to a PyObject *, converting the
204316
// stackref to a new reference.
205317
#define PyStackRef_AsPyObjectNew(stackref) Py_NewRef(PyStackRef_AsPyObjectBorrow(stackref))
206318

207319
#define PyStackRef_TYPE(stackref) Py_TYPE(PyStackRef_AsPyObjectBorrow(stackref))
208320

321+
209322
#define PyStackRef_CLEAR(op) \
210323
do { \
211324
_PyStackRef *_tmp_op_ptr = &(op); \

Makefile.pre.in

+1
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,7 @@ PYTHON_OBJS= \
488488
Python/qsbr.o \
489489
Python/bootstrap_hash.o \
490490
Python/specialize.o \
491+
Python/stackrefs.o \
491492
Python/structmember.o \
492493
Python/symtable.o \
493494
Python/sysmodule.o \
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Adds stackref debugging when ``Py_STACKREF_DEBUG`` is set. Finds all
2+
double-closes and leaks, logging the origin and last borrow.
3+
4+
Inspired by HPy's debug mode. https://docs.hpyproject.org/en/latest/debug-mode.html

Objects/frameobject.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,9 @@ framelocalsproxy_setitem(PyObject *self, PyObject *key, PyObject *value)
179179
if (kind == CO_FAST_FREE) {
180180
// The cell was set when the frame was created from
181181
// the function's closure.
182-
assert(oldvalue.bits != 0 && PyCell_Check(PyStackRef_AsPyObjectBorrow(oldvalue)));
182+
assert(!PyStackRef_IsNull(oldvalue) && PyCell_Check(PyStackRef_AsPyObjectBorrow(oldvalue)));
183183
cell = PyStackRef_AsPyObjectBorrow(oldvalue);
184-
} else if (kind & CO_FAST_CELL && oldvalue.bits != 0) {
184+
} else if (kind & CO_FAST_CELL && !PyStackRef_IsNull(oldvalue)) {
185185
PyObject *as_obj = PyStackRef_AsPyObjectBorrow(oldvalue);
186186
if (PyCell_Check(as_obj)) {
187187
cell = as_obj;

Python/bytecodes.c

+12-5
Original file line numberDiff line numberDiff line change
@@ -681,7 +681,7 @@ dummy_func(
681681
assert(Py_REFCNT(left_o) >= 2);
682682
PyStackRef_CLOSE(left);
683683
DEAD(left);
684-
PyObject *temp = PyStackRef_AsPyObjectBorrow(*target_local);
684+
PyObject *temp = PyStackRef_AsPyObjectSteal(*target_local);
685685
PyUnicode_Append(&temp, right_o);
686686
*target_local = PyStackRef_FromPyObjectSteal(temp);
687687
PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc);
@@ -4509,17 +4509,17 @@ dummy_func(
45094509

45104510
op(_DO_CALL_FUNCTION_EX, (func_st, unused, callargs_st, kwargs_st if (oparg & 1) -- result)) {
45114511
PyObject *func = PyStackRef_AsPyObjectBorrow(func_st);
4512-
PyObject *callargs = PyStackRef_AsPyObjectBorrow(callargs_st);
4513-
PyObject *kwargs = PyStackRef_AsPyObjectBorrow(kwargs_st);
45144512

45154513
// DICT_MERGE is called before this opcode if there are kwargs.
45164514
// It converts all dict subtypes in kwargs into regular dicts.
4517-
assert(kwargs == NULL || PyDict_CheckExact(kwargs));
4518-
assert(PyTuple_CheckExact(callargs));
45194515
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_FUNCTION_EX, func);
45204516
PyObject *result_o;
45214517
assert(!_PyErr_Occurred(tstate));
45224518
if (opcode == INSTRUMENTED_CALL_FUNCTION_EX) {
4519+
PyObject *callargs = PyStackRef_AsPyObjectBorrow(callargs_st);
4520+
PyObject *kwargs = PyStackRef_AsPyObjectBorrow(kwargs_st);
4521+
assert(kwargs == NULL || PyDict_CheckExact(kwargs));
4522+
assert(PyTuple_CheckExact(callargs));
45234523
PyObject *arg = PyTuple_GET_SIZE(callargs) > 0 ?
45244524
PyTuple_GET_ITEM(callargs, 0) : &_PyInstrumentation_MISSING;
45254525
int err = _Py_call_instrumentation_2args(
@@ -4550,7 +4550,10 @@ dummy_func(
45504550
if (Py_TYPE(func) == &PyFunction_Type &&
45514551
tstate->interp->eval_frame == NULL &&
45524552
((PyFunctionObject *)func)->vectorcall == _PyFunction_Vectorcall) {
4553+
PyObject *callargs = PyStackRef_AsPyObjectSteal(callargs_st);
45534554
assert(PyTuple_CheckExact(callargs));
4555+
PyObject *kwargs = PyStackRef_IsNull(kwargs_st) ? NULL : PyStackRef_AsPyObjectSteal(kwargs_st);
4556+
assert(kwargs == NULL || PyDict_CheckExact(kwargs));
45544557
Py_ssize_t nargs = PyTuple_GET_SIZE(callargs);
45554558
int code_flags = ((PyCodeObject *)PyFunction_GET_CODE(func))->co_flags;
45564559
PyObject *locals = code_flags & CO_OPTIMIZED ? NULL : Py_NewRef(PyFunction_GET_GLOBALS(func));
@@ -4568,6 +4571,10 @@ dummy_func(
45684571
frame->return_offset = 1;
45694572
DISPATCH_INLINED(new_frame);
45704573
}
4574+
PyObject *callargs = PyStackRef_AsPyObjectBorrow(callargs_st);
4575+
assert(PyTuple_CheckExact(callargs));
4576+
PyObject *kwargs = PyStackRef_AsPyObjectBorrow(kwargs_st);
4577+
assert(kwargs == NULL || PyDict_CheckExact(kwargs));
45714578
result_o = PyObject_Call(func, callargs, kwargs);
45724579
}
45734580
PyStackRef_XCLOSE(kwargs_st);

Python/ceval.c

+53-14
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ dump_stack(_PyInterpreterFrame *frame, _PyStackRef *stack_pointer)
164164
PyErr_Clear();
165165
}
166166
// Don't call __repr__(), it might recurse into the interpreter.
167-
printf("<%s at %p>", Py_TYPE(obj)->tp_name, (void *)(ptr->bits));
167+
printf("<%s at %p>", Py_TYPE(obj)->tp_name, PyStackRef_AsPyObjectBorrow(*ptr));
168168
}
169169
printf("]\n");
170170
fflush(stdout);
@@ -805,7 +805,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
805805

806806

807807

808-
#ifdef Py_DEBUG
808+
#if defined(Py_DEBUG) && !defined(Py_STACKREF_DEBUG)
809809
/* Set these to invalid but identifiable values for debugging. */
810810
entry_frame.f_funcobj = (_PyStackRef){.bits = 0xaaa0};
811811
entry_frame.f_locals = (PyObject*)0xaaa1;
@@ -1810,27 +1810,48 @@ _PyEvalFramePushAndInit_Ex(PyThreadState *tstate, _PyStackRef func,
18101810
{
18111811
bool has_dict = (kwargs != NULL && PyDict_GET_SIZE(kwargs) > 0);
18121812
PyObject *kwnames = NULL;
1813-
PyObject *const *newargs;
1813+
_PyStackRef *newargs;
1814+
PyObject *const *object_array = NULL;
1815+
_PyStackRef stack_array[8];
18141816
if (has_dict) {
1815-
newargs = _PyStack_UnpackDict(tstate, _PyTuple_ITEMS(callargs), nargs, kwargs, &kwnames);
1816-
if (newargs == NULL) {
1817+
object_array = _PyStack_UnpackDict(tstate, _PyTuple_ITEMS(callargs), nargs, kwargs, &kwnames);
1818+
if (object_array == NULL) {
18171819
PyStackRef_CLOSE(func);
18181820
goto error;
18191821
}
1822+
size_t total_args = nargs + PyDict_GET_SIZE(kwargs);
1823+
assert(sizeof(PyObject *) == sizeof(_PyStackRef));
1824+
newargs = (_PyStackRef *)object_array;
1825+
for (size_t i = 0; i < total_args; i++) {
1826+
newargs[i] = PyStackRef_FromPyObjectSteal(object_array[i]);
1827+
}
18201828
}
18211829
else {
1822-
newargs = &PyTuple_GET_ITEM(callargs, 0);
1823-
/* We need to incref all our args since the new frame steals the references. */
1824-
for (Py_ssize_t i = 0; i < nargs; ++i) {
1825-
Py_INCREF(PyTuple_GET_ITEM(callargs, i));
1830+
if (nargs <= 8) {
1831+
newargs = stack_array;
1832+
}
1833+
else {
1834+
newargs = PyMem_Malloc(sizeof(_PyStackRef) *nargs);
1835+
if (newargs == NULL) {
1836+
PyErr_NoMemory();
1837+
PyStackRef_CLOSE(func);
1838+
goto error;
1839+
}
1840+
}
1841+
/* We need to create a new reference for all our args since the new frame steals them. */
1842+
for (Py_ssize_t i = 0; i < nargs; i++) {
1843+
newargs[i] = PyStackRef_FromPyObjectNew(PyTuple_GET_ITEM(callargs, i));
18261844
}
18271845
}
18281846
_PyInterpreterFrame *new_frame = _PyEvalFramePushAndInit(
18291847
tstate, func, locals,
1830-
(_PyStackRef const *)newargs, nargs, kwnames, previous
1848+
newargs, nargs, kwnames, previous
18311849
);
18321850
if (has_dict) {
1833-
_PyStack_UnpackDict_FreeNoDecRef(newargs, kwnames);
1851+
_PyStack_UnpackDict_FreeNoDecRef(object_array, kwnames);
1852+
}
1853+
else if (nargs > 8) {
1854+
PyMem_Free((void *)newargs);
18341855
}
18351856
/* No need to decref func here because the reference has been stolen by
18361857
_PyEvalFramePushAndInit.
@@ -1850,21 +1871,39 @@ _PyEval_Vector(PyThreadState *tstate, PyFunctionObject *func,
18501871
PyObject* const* args, size_t argcount,
18511872
PyObject *kwnames)
18521873
{
1874+
size_t total_args = argcount;
1875+
if (kwnames) {
1876+
total_args += PyTuple_GET_SIZE(kwnames);
1877+
}
1878+
_PyStackRef stack_array[8];
1879+
_PyStackRef *arguments;
1880+
if (total_args <= 8) {
1881+
arguments = stack_array;
1882+
}
1883+
else {
1884+
arguments = PyMem_Malloc(sizeof(_PyStackRef) * total_args);
1885+
if (arguments == NULL) {
1886+
return PyErr_NoMemory();
1887+
}
1888+
}
18531889
/* _PyEvalFramePushAndInit consumes the references
18541890
* to func, locals and all its arguments */
18551891
Py_XINCREF(locals);
18561892
for (size_t i = 0; i < argcount; i++) {
1857-
Py_INCREF(args[i]);
1893+
arguments[i] = PyStackRef_FromPyObjectNew(args[i]);
18581894
}
18591895
if (kwnames) {
18601896
Py_ssize_t kwcount = PyTuple_GET_SIZE(kwnames);
18611897
for (Py_ssize_t i = 0; i < kwcount; i++) {
1862-
Py_INCREF(args[i+argcount]);
1898+
arguments[i+argcount] = PyStackRef_FromPyObjectNew(args[i+argcount]);
18631899
}
18641900
}
18651901
_PyInterpreterFrame *frame = _PyEvalFramePushAndInit(
18661902
tstate, PyStackRef_FromPyObjectNew(func), locals,
1867-
(_PyStackRef const *)args, argcount, kwnames, NULL);
1903+
arguments, argcount, kwnames, NULL);
1904+
if (total_args > 8) {
1905+
PyMem_Free(arguments);
1906+
}
18681907
if (frame == NULL) {
18691908
return NULL;
18701909
}

Python/ceval_macros.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ do { \
450450
/* How much scratch space to give stackref to PyObject* conversion. */
451451
#define MAX_STACKREF_SCRATCH 10
452452

453-
#ifdef Py_GIL_DISABLED
453+
#if defined(Py_GIL_DISABLED) || defined(Py_STACKREF_DEBUG)
454454
#define STACKREFS_TO_PYOBJECTS(ARGS, ARG_COUNT, NAME) \
455455
/* +1 because vectorcall might use -1 to write self */ \
456456
PyObject *NAME##_temp[MAX_STACKREF_SCRATCH+1]; \
@@ -461,7 +461,7 @@ do { \
461461
assert(NAME != NULL);
462462
#endif
463463

464-
#ifdef Py_GIL_DISABLED
464+
#if defined(Py_GIL_DISABLED) || defined(Py_STACKREF_DEBUG)
465465
#define STACKREFS_TO_PYOBJECTS_CLEANUP(NAME) \
466466
/* +1 because we +1 previously */ \
467467
_PyObjectArray_Free(NAME - 1, NAME##_temp);
@@ -470,7 +470,7 @@ do { \
470470
(void)(NAME);
471471
#endif
472472

473-
#ifdef Py_GIL_DISABLED
473+
#if defined(Py_GIL_DISABLED) || defined(Py_STACKREF_DEBUG)
474474
#define CONVERSION_FAILED(NAME) ((NAME) == NULL)
475475
#else
476476
#define CONVERSION_FAILED(NAME) (0)

Python/executor_cases.c.h

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)