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

File tree

12 files changed

+395
-33
lines changed

12 files changed

+395
-33
lines changed

Include/internal/pycore_interp.h

Lines changed: 6 additions & 0 deletions
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

Lines changed: 113 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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 \
Lines changed: 4 additions & 0 deletions
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

Lines changed: 2 additions & 2 deletions
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

Lines changed: 12 additions & 5 deletions
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

Lines changed: 53 additions & 14 deletions
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

Lines changed: 3 additions & 3 deletions
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

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

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy