mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	GH-127705: Add debug mode for _PyStackRefs inspired by HPy debug mode (GH-128121)
				
					
				
			This commit is contained in:
		
							parent
							
								
									78ffba4221
								
							
						
					
					
						commit
						128cc47fbd
					
				
					 12 changed files with 395 additions and 33 deletions
				
			
		|  | @ -34,6 +34,7 @@ extern "C" { | |||
| #include "pycore_optimizer.h"     // _PyOptimizerObject | ||||
| #include "pycore_obmalloc.h"      // struct _obmalloc_state | ||||
| #include "pycore_qsbr.h"          // struct _qsbr_state | ||||
| #include "pycore_stackref.h"      // Py_STACKREF_DEBUG | ||||
| #include "pycore_tstate.h"        // _PyThreadStateImpl | ||||
| #include "pycore_tuple.h"         // struct _Py_tuple_state | ||||
| #include "pycore_uniqueid.h"      // struct _Py_unique_id_pool | ||||
|  | @ -285,6 +286,11 @@ struct _is { | |||
|     _PyThreadStateImpl _initial_thread; | ||||
|     // _initial_thread should be the last field of PyInterpreterState.
 | ||||
|     // See https://github.com/python/cpython/issues/127117.
 | ||||
| 
 | ||||
| #if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG) | ||||
|     uint64_t next_stackref; | ||||
|     _Py_hashtable_t *stackref_debug_table; | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,6 +4,9 @@ | |||
| extern "C" { | ||||
| #endif | ||||
| 
 | ||||
| // Define this to get precise tracking of stackrefs.
 | ||||
| // #define Py_STACKREF_DEBUG 1
 | ||||
| 
 | ||||
| #ifndef Py_BUILD_CORE | ||||
| #  error "this header requires Py_BUILD_CORE define" | ||||
| #endif | ||||
|  | @ -49,6 +52,113 @@ extern "C" { | |||
|    CPython refcounting operations on it! | ||||
| */ | ||||
| 
 | ||||
| 
 | ||||
| #if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG) | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| typedef union _PyStackRef { | ||||
|     uint64_t index; | ||||
| } _PyStackRef; | ||||
| 
 | ||||
| #define Py_TAG_BITS 0 | ||||
| 
 | ||||
| PyAPI_FUNC(PyObject *) _Py_stackref_get_object(_PyStackRef ref); | ||||
| PyAPI_FUNC(PyObject *) _Py_stackref_close(_PyStackRef ref); | ||||
| PyAPI_FUNC(_PyStackRef) _Py_stackref_create(PyObject *obj, const char *filename, int linenumber); | ||||
| PyAPI_FUNC(void) _Py_stackref_record_borrow(_PyStackRef ref, const char *filename, int linenumber); | ||||
| extern void _Py_stackref_associate(PyInterpreterState *interp, PyObject *obj, _PyStackRef ref); | ||||
| 
 | ||||
| static const _PyStackRef PyStackRef_NULL = { .index = 0 }; | ||||
| 
 | ||||
| #define PyStackRef_None ((_PyStackRef){ .index = 1 } ) | ||||
| #define PyStackRef_False ((_PyStackRef){ .index = 2 }) | ||||
| #define PyStackRef_True ((_PyStackRef){ .index = 3 }) | ||||
| 
 | ||||
| #define LAST_PREDEFINED_STACKREF_INDEX 3 | ||||
| 
 | ||||
| static inline int | ||||
| PyStackRef_IsNull(_PyStackRef ref) | ||||
| { | ||||
|     return ref.index == 0; | ||||
| } | ||||
| 
 | ||||
| static inline int | ||||
| PyStackRef_IsTrue(_PyStackRef ref) | ||||
| { | ||||
|     return _Py_stackref_get_object(ref) == Py_True; | ||||
| } | ||||
| 
 | ||||
| static inline int | ||||
| PyStackRef_IsFalse(_PyStackRef ref) | ||||
| { | ||||
|     return _Py_stackref_get_object(ref) == Py_False; | ||||
| } | ||||
| 
 | ||||
| static inline int | ||||
| PyStackRef_IsNone(_PyStackRef ref) | ||||
| { | ||||
|     return _Py_stackref_get_object(ref) == Py_None; | ||||
| } | ||||
| 
 | ||||
| static inline PyObject * | ||||
| _PyStackRef_AsPyObjectBorrow(_PyStackRef ref, const char *filename, int linenumber) | ||||
| { | ||||
|     _Py_stackref_record_borrow(ref, filename, linenumber); | ||||
|     return _Py_stackref_get_object(ref); | ||||
| } | ||||
| 
 | ||||
| #define PyStackRef_AsPyObjectBorrow(REF) _PyStackRef_AsPyObjectBorrow((REF), __FILE__, __LINE__) | ||||
| 
 | ||||
| static inline PyObject * | ||||
| PyStackRef_AsPyObjectSteal(_PyStackRef ref) | ||||
| { | ||||
|     return _Py_stackref_close(ref); | ||||
| } | ||||
| 
 | ||||
| static inline _PyStackRef | ||||
| _PyStackRef_FromPyObjectNew(PyObject *obj, const char *filename, int linenumber) | ||||
| { | ||||
|     Py_INCREF(obj); | ||||
|     return _Py_stackref_create(obj, filename, linenumber); | ||||
| } | ||||
| #define PyStackRef_FromPyObjectNew(obj) _PyStackRef_FromPyObjectNew(_PyObject_CAST(obj), __FILE__, __LINE__) | ||||
| 
 | ||||
| static inline _PyStackRef | ||||
| _PyStackRef_FromPyObjectSteal(PyObject *obj, const char *filename, int linenumber) | ||||
| { | ||||
|     return _Py_stackref_create(obj, filename, linenumber); | ||||
| } | ||||
| #define PyStackRef_FromPyObjectSteal(obj) _PyStackRef_FromPyObjectSteal(_PyObject_CAST(obj), __FILE__, __LINE__) | ||||
| 
 | ||||
| static inline _PyStackRef | ||||
| _PyStackRef_FromPyObjectImmortal(PyObject *obj, const char *filename, int linenumber) | ||||
| { | ||||
|     assert(_Py_IsImmortal(obj)); | ||||
|     return _Py_stackref_create(obj, filename, linenumber); | ||||
| } | ||||
| #define PyStackRef_FromPyObjectImmortal(obj) _PyStackRef_FromPyObjectImmortal(_PyObject_CAST(obj), __FILE__, __LINE__) | ||||
| 
 | ||||
| static inline void | ||||
| PyStackRef_CLOSE(_PyStackRef ref) | ||||
| { | ||||
|     PyObject *obj = _Py_stackref_close(ref); | ||||
|     Py_DECREF(obj); | ||||
| } | ||||
| 
 | ||||
| static inline _PyStackRef | ||||
| _PyStackRef_DUP(_PyStackRef ref, const char *filename, int linenumber) | ||||
| { | ||||
|     PyObject *obj = _Py_stackref_get_object(ref); | ||||
|     Py_INCREF(obj); | ||||
|     return _Py_stackref_create(obj, filename, linenumber); | ||||
| } | ||||
| #define PyStackRef_DUP(REF) _PyStackRef_DUP(REF, __FILE__, __LINE__) | ||||
| 
 | ||||
| #define PyStackRef_CLOSE_SPECIALIZED(stackref, dealloc) PyStackRef_CLOSE(stackref) | ||||
| 
 | ||||
| #else | ||||
| 
 | ||||
| typedef union _PyStackRef { | ||||
|     uintptr_t bits; | ||||
| } _PyStackRef; | ||||
|  | @ -200,12 +310,15 @@ static const _PyStackRef PyStackRef_NULL = { .bits = 0 }; | |||
| #define PyStackRef_IsTrue(ref) (PyStackRef_AsPyObjectBorrow(ref) == Py_True) | ||||
| #define PyStackRef_IsFalse(ref) (PyStackRef_AsPyObjectBorrow(ref) == Py_False) | ||||
| 
 | ||||
| #endif | ||||
| 
 | ||||
| // Converts a PyStackRef back to a PyObject *, converting the
 | ||||
| // stackref to a new reference.
 | ||||
| #define PyStackRef_AsPyObjectNew(stackref) Py_NewRef(PyStackRef_AsPyObjectBorrow(stackref)) | ||||
| 
 | ||||
| #define PyStackRef_TYPE(stackref) Py_TYPE(PyStackRef_AsPyObjectBorrow(stackref)) | ||||
| 
 | ||||
| 
 | ||||
| #define PyStackRef_CLEAR(op) \ | ||||
|     do { \ | ||||
|         _PyStackRef *_tmp_op_ptr = &(op); \ | ||||
|  |  | |||
|  | @ -488,6 +488,7 @@ PYTHON_OBJS=	\ | |||
| 		Python/qsbr.o \ | ||||
| 		Python/bootstrap_hash.o \ | ||||
| 		Python/specialize.o \ | ||||
| 		Python/stackrefs.o \ | ||||
| 		Python/structmember.o \ | ||||
| 		Python/symtable.o \ | ||||
| 		Python/sysmodule.o \ | ||||
|  |  | |||
|  | @ -0,0 +1,4 @@ | |||
| Adds stackref debugging when ``Py_STACKREF_DEBUG`` is set. Finds all | ||||
| double-closes and leaks, logging the origin and last borrow. | ||||
| 
 | ||||
| Inspired by HPy's debug mode. https://docs.hpyproject.org/en/latest/debug-mode.html | ||||
|  | @ -179,9 +179,9 @@ framelocalsproxy_setitem(PyObject *self, PyObject *key, PyObject *value) | |||
|         if (kind == CO_FAST_FREE) { | ||||
|             // The cell was set when the frame was created from
 | ||||
|             // the function's closure.
 | ||||
|             assert(oldvalue.bits != 0 && PyCell_Check(PyStackRef_AsPyObjectBorrow(oldvalue))); | ||||
|             assert(!PyStackRef_IsNull(oldvalue) && PyCell_Check(PyStackRef_AsPyObjectBorrow(oldvalue))); | ||||
|             cell = PyStackRef_AsPyObjectBorrow(oldvalue); | ||||
|         } else if (kind & CO_FAST_CELL && oldvalue.bits != 0) { | ||||
|         } else if (kind & CO_FAST_CELL && !PyStackRef_IsNull(oldvalue)) { | ||||
|             PyObject *as_obj = PyStackRef_AsPyObjectBorrow(oldvalue); | ||||
|             if (PyCell_Check(as_obj)) { | ||||
|                 cell = as_obj; | ||||
|  |  | |||
|  | @ -681,7 +681,7 @@ dummy_func( | |||
|             assert(Py_REFCNT(left_o) >= 2); | ||||
|             PyStackRef_CLOSE(left); | ||||
|             DEAD(left); | ||||
|             PyObject *temp = PyStackRef_AsPyObjectBorrow(*target_local); | ||||
|             PyObject *temp = PyStackRef_AsPyObjectSteal(*target_local); | ||||
|             PyUnicode_Append(&temp, right_o); | ||||
|             *target_local = PyStackRef_FromPyObjectSteal(temp); | ||||
|             PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc); | ||||
|  | @ -4509,17 +4509,17 @@ dummy_func( | |||
| 
 | ||||
|         op(_DO_CALL_FUNCTION_EX, (func_st, unused, callargs_st, kwargs_st if (oparg & 1) -- result)) { | ||||
|             PyObject *func = PyStackRef_AsPyObjectBorrow(func_st); | ||||
|             PyObject *callargs = PyStackRef_AsPyObjectBorrow(callargs_st); | ||||
|             PyObject *kwargs = PyStackRef_AsPyObjectBorrow(kwargs_st); | ||||
| 
 | ||||
|             // DICT_MERGE is called before this opcode if there are kwargs.
 | ||||
|             // It converts all dict subtypes in kwargs into regular dicts.
 | ||||
|             assert(kwargs == NULL || PyDict_CheckExact(kwargs)); | ||||
|             assert(PyTuple_CheckExact(callargs)); | ||||
|             EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_FUNCTION_EX, func); | ||||
|             PyObject *result_o; | ||||
|             assert(!_PyErr_Occurred(tstate)); | ||||
|             if (opcode == INSTRUMENTED_CALL_FUNCTION_EX) { | ||||
|                 PyObject *callargs = PyStackRef_AsPyObjectBorrow(callargs_st); | ||||
|                 PyObject *kwargs = PyStackRef_AsPyObjectBorrow(kwargs_st); | ||||
|                 assert(kwargs == NULL || PyDict_CheckExact(kwargs)); | ||||
|                 assert(PyTuple_CheckExact(callargs)); | ||||
|                 PyObject *arg = PyTuple_GET_SIZE(callargs) > 0 ? | ||||
|                     PyTuple_GET_ITEM(callargs, 0) : &_PyInstrumentation_MISSING; | ||||
|                 int err = _Py_call_instrumentation_2args( | ||||
|  | @ -4550,7 +4550,10 @@ dummy_func( | |||
|                 if (Py_TYPE(func) == &PyFunction_Type && | ||||
|                     tstate->interp->eval_frame == NULL && | ||||
|                     ((PyFunctionObject *)func)->vectorcall == _PyFunction_Vectorcall) { | ||||
|                     PyObject *callargs = PyStackRef_AsPyObjectSteal(callargs_st); | ||||
|                     assert(PyTuple_CheckExact(callargs)); | ||||
|                     PyObject *kwargs = PyStackRef_IsNull(kwargs_st) ? NULL : PyStackRef_AsPyObjectSteal(kwargs_st); | ||||
|                     assert(kwargs == NULL || PyDict_CheckExact(kwargs)); | ||||
|                     Py_ssize_t nargs = PyTuple_GET_SIZE(callargs); | ||||
|                     int code_flags = ((PyCodeObject *)PyFunction_GET_CODE(func))->co_flags; | ||||
|                     PyObject *locals = code_flags & CO_OPTIMIZED ? NULL : Py_NewRef(PyFunction_GET_GLOBALS(func)); | ||||
|  | @ -4568,6 +4571,10 @@ dummy_func( | |||
|                     frame->return_offset = 1; | ||||
|                     DISPATCH_INLINED(new_frame); | ||||
|                 } | ||||
|                 PyObject *callargs = PyStackRef_AsPyObjectBorrow(callargs_st); | ||||
|                 assert(PyTuple_CheckExact(callargs)); | ||||
|                 PyObject *kwargs = PyStackRef_AsPyObjectBorrow(kwargs_st); | ||||
|                 assert(kwargs == NULL || PyDict_CheckExact(kwargs)); | ||||
|                 result_o = PyObject_Call(func, callargs, kwargs); | ||||
|             } | ||||
|             PyStackRef_XCLOSE(kwargs_st); | ||||
|  |  | |||
|  | @ -164,7 +164,7 @@ dump_stack(_PyInterpreterFrame *frame, _PyStackRef *stack_pointer) | |||
|             PyErr_Clear(); | ||||
|         } | ||||
|         // Don't call __repr__(), it might recurse into the interpreter.
 | ||||
|         printf("<%s at %p>", Py_TYPE(obj)->tp_name, (void *)(ptr->bits)); | ||||
|         printf("<%s at %p>", Py_TYPE(obj)->tp_name, PyStackRef_AsPyObjectBorrow(*ptr)); | ||||
|     } | ||||
|     printf("]\n"); | ||||
|     fflush(stdout); | ||||
|  | @ -805,7 +805,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | |||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| #ifdef Py_DEBUG | ||||
| #if defined(Py_DEBUG) && !defined(Py_STACKREF_DEBUG) | ||||
|     /* Set these to invalid but identifiable values for debugging. */ | ||||
|     entry_frame.f_funcobj = (_PyStackRef){.bits = 0xaaa0}; | ||||
|     entry_frame.f_locals = (PyObject*)0xaaa1; | ||||
|  | @ -1810,27 +1810,48 @@ _PyEvalFramePushAndInit_Ex(PyThreadState *tstate, _PyStackRef func, | |||
| { | ||||
|     bool has_dict = (kwargs != NULL && PyDict_GET_SIZE(kwargs) > 0); | ||||
|     PyObject *kwnames = NULL; | ||||
|     PyObject *const *newargs; | ||||
|     _PyStackRef *newargs; | ||||
|     PyObject *const *object_array = NULL; | ||||
|     _PyStackRef stack_array[8]; | ||||
|     if (has_dict) { | ||||
|         newargs = _PyStack_UnpackDict(tstate, _PyTuple_ITEMS(callargs), nargs, kwargs, &kwnames); | ||||
|         if (newargs == NULL) { | ||||
|         object_array = _PyStack_UnpackDict(tstate, _PyTuple_ITEMS(callargs), nargs, kwargs, &kwnames); | ||||
|         if (object_array == NULL) { | ||||
|             PyStackRef_CLOSE(func); | ||||
|             goto error; | ||||
|         } | ||||
|         size_t total_args = nargs + PyDict_GET_SIZE(kwargs); | ||||
|         assert(sizeof(PyObject *) == sizeof(_PyStackRef)); | ||||
|         newargs = (_PyStackRef *)object_array; | ||||
|         for (size_t i = 0; i < total_args; i++) { | ||||
|             newargs[i] = PyStackRef_FromPyObjectSteal(object_array[i]); | ||||
|         } | ||||
|     } | ||||
|     else { | ||||
|         newargs = &PyTuple_GET_ITEM(callargs, 0); | ||||
|         /* We need to incref all our args since the new frame steals the references. */ | ||||
|         for (Py_ssize_t i = 0; i < nargs; ++i) { | ||||
|             Py_INCREF(PyTuple_GET_ITEM(callargs, i)); | ||||
|         if (nargs <= 8) { | ||||
|             newargs = stack_array; | ||||
|         } | ||||
|         else { | ||||
|             newargs = PyMem_Malloc(sizeof(_PyStackRef) *nargs); | ||||
|             if (newargs == NULL) { | ||||
|                 PyErr_NoMemory(); | ||||
|                 PyStackRef_CLOSE(func); | ||||
|                 goto error; | ||||
|             } | ||||
|         } | ||||
|         /* We need to create a new reference for all our args since the new frame steals them. */ | ||||
|         for (Py_ssize_t i = 0; i < nargs; i++) { | ||||
|             newargs[i] = PyStackRef_FromPyObjectNew(PyTuple_GET_ITEM(callargs, i)); | ||||
|         } | ||||
|     } | ||||
|     _PyInterpreterFrame *new_frame = _PyEvalFramePushAndInit( | ||||
|         tstate, func, locals, | ||||
|         (_PyStackRef const *)newargs, nargs, kwnames, previous | ||||
|         newargs, nargs, kwnames, previous | ||||
|     ); | ||||
|     if (has_dict) { | ||||
|         _PyStack_UnpackDict_FreeNoDecRef(newargs, kwnames); | ||||
|         _PyStack_UnpackDict_FreeNoDecRef(object_array, kwnames); | ||||
|     } | ||||
|     else if (nargs > 8) { | ||||
|        PyMem_Free((void *)newargs); | ||||
|     } | ||||
|     /* No need to decref func here because the reference has been stolen by
 | ||||
|        _PyEvalFramePushAndInit. | ||||
|  | @ -1850,21 +1871,39 @@ _PyEval_Vector(PyThreadState *tstate, PyFunctionObject *func, | |||
|                PyObject* const* args, size_t argcount, | ||||
|                PyObject *kwnames) | ||||
| { | ||||
|     size_t total_args = argcount; | ||||
|     if (kwnames) { | ||||
|         total_args += PyTuple_GET_SIZE(kwnames); | ||||
|     } | ||||
|     _PyStackRef stack_array[8]; | ||||
|     _PyStackRef *arguments; | ||||
|     if (total_args <= 8) { | ||||
|         arguments = stack_array; | ||||
|     } | ||||
|     else { | ||||
|         arguments = PyMem_Malloc(sizeof(_PyStackRef) * total_args); | ||||
|         if (arguments == NULL) { | ||||
|             return PyErr_NoMemory(); | ||||
|         } | ||||
|     } | ||||
|     /* _PyEvalFramePushAndInit consumes the references
 | ||||
|      * to func, locals and all its arguments */ | ||||
|     Py_XINCREF(locals); | ||||
|     for (size_t i = 0; i < argcount; i++) { | ||||
|         Py_INCREF(args[i]); | ||||
|         arguments[i] = PyStackRef_FromPyObjectNew(args[i]); | ||||
|     } | ||||
|     if (kwnames) { | ||||
|         Py_ssize_t kwcount = PyTuple_GET_SIZE(kwnames); | ||||
|         for (Py_ssize_t i = 0; i < kwcount; i++) { | ||||
|             Py_INCREF(args[i+argcount]); | ||||
|             arguments[i+argcount] = PyStackRef_FromPyObjectNew(args[i+argcount]); | ||||
|         } | ||||
|     } | ||||
|     _PyInterpreterFrame *frame = _PyEvalFramePushAndInit( | ||||
|         tstate, PyStackRef_FromPyObjectNew(func), locals, | ||||
|         (_PyStackRef const *)args, argcount, kwnames, NULL); | ||||
|         arguments, argcount, kwnames, NULL); | ||||
|     if (total_args > 8) { | ||||
|         PyMem_Free(arguments); | ||||
|     } | ||||
|     if (frame == NULL) { | ||||
|         return NULL; | ||||
|     } | ||||
|  |  | |||
|  | @ -450,7 +450,7 @@ do { \ | |||
| /* How much scratch space to give stackref to PyObject* conversion. */ | ||||
| #define MAX_STACKREF_SCRATCH 10 | ||||
| 
 | ||||
| #ifdef Py_GIL_DISABLED | ||||
| #if defined(Py_GIL_DISABLED) || defined(Py_STACKREF_DEBUG) | ||||
| #define STACKREFS_TO_PYOBJECTS(ARGS, ARG_COUNT, NAME) \ | ||||
|     /* +1 because vectorcall might use -1 to write self */ \ | ||||
|     PyObject *NAME##_temp[MAX_STACKREF_SCRATCH+1]; \ | ||||
|  | @ -461,7 +461,7 @@ do { \ | |||
|     assert(NAME != NULL); | ||||
| #endif | ||||
| 
 | ||||
| #ifdef Py_GIL_DISABLED | ||||
| #if defined(Py_GIL_DISABLED) || defined(Py_STACKREF_DEBUG) | ||||
| #define STACKREFS_TO_PYOBJECTS_CLEANUP(NAME) \ | ||||
|     /* +1 because we +1 previously */ \ | ||||
|     _PyObjectArray_Free(NAME - 1, NAME##_temp); | ||||
|  | @ -470,7 +470,7 @@ do { \ | |||
|     (void)(NAME); | ||||
| #endif | ||||
| 
 | ||||
| #ifdef Py_GIL_DISABLED | ||||
| #if defined(Py_GIL_DISABLED) || defined(Py_STACKREF_DEBUG) | ||||
| #define CONVERSION_FAILED(NAME) ((NAME) == NULL) | ||||
| #else | ||||
| #define CONVERSION_FAILED(NAME) (0) | ||||
|  |  | |||
							
								
								
									
										2
									
								
								Python/executor_cases.c.h
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								Python/executor_cases.c.h
									
										
									
										generated
									
									
									
								
							|  | @ -850,7 +850,7 @@ | |||
|              */ | ||||
|             assert(Py_REFCNT(left_o) >= 2); | ||||
|             PyStackRef_CLOSE(left); | ||||
|             PyObject *temp = PyStackRef_AsPyObjectBorrow(*target_local); | ||||
|             PyObject *temp = PyStackRef_AsPyObjectSteal(*target_local); | ||||
|             PyUnicode_Append(&temp, right_o); | ||||
|             *target_local = PyStackRef_FromPyObjectSteal(temp); | ||||
|             PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc); | ||||
|  |  | |||
							
								
								
									
										23
									
								
								Python/generated_cases.c.h
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										23
									
								
								Python/generated_cases.c.h
									
										
									
										generated
									
									
									
								
							|  | @ -208,7 +208,7 @@ | |||
|                  */ | ||||
|                 assert(Py_REFCNT(left_o) >= 2); | ||||
|                 PyStackRef_CLOSE(left); | ||||
|                 PyObject *temp = PyStackRef_AsPyObjectBorrow(*target_local); | ||||
|                 PyObject *temp = PyStackRef_AsPyObjectSteal(*target_local); | ||||
|                 PyUnicode_Append(&temp, right_o); | ||||
|                 *target_local = PyStackRef_FromPyObjectSteal(temp); | ||||
|                 PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc); | ||||
|  | @ -1675,16 +1675,16 @@ | |||
|                 callargs_st = tuple; | ||||
|                 func_st = func; | ||||
|                 PyObject *func = PyStackRef_AsPyObjectBorrow(func_st); | ||||
|                 PyObject *callargs = PyStackRef_AsPyObjectBorrow(callargs_st); | ||||
|                 PyObject *kwargs = PyStackRef_AsPyObjectBorrow(kwargs_st); | ||||
|                 // DICT_MERGE is called before this opcode if there are kwargs.
 | ||||
|                 // It converts all dict subtypes in kwargs into regular dicts.
 | ||||
|                 assert(kwargs == NULL || PyDict_CheckExact(kwargs)); | ||||
|                 assert(PyTuple_CheckExact(callargs)); | ||||
|                 EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_FUNCTION_EX, func); | ||||
|                 PyObject *result_o; | ||||
|                 assert(!_PyErr_Occurred(tstate)); | ||||
|                 if (opcode == INSTRUMENTED_CALL_FUNCTION_EX) { | ||||
|                     PyObject *callargs = PyStackRef_AsPyObjectBorrow(callargs_st); | ||||
|                     PyObject *kwargs = PyStackRef_AsPyObjectBorrow(kwargs_st); | ||||
|                     assert(kwargs == NULL || PyDict_CheckExact(kwargs)); | ||||
|                     assert(PyTuple_CheckExact(callargs)); | ||||
|                     PyObject *arg = PyTuple_GET_SIZE(callargs) > 0 ? | ||||
|                     PyTuple_GET_ITEM(callargs, 0) : &_PyInstrumentation_MISSING; | ||||
|                     stack_pointer[-1 - (oparg & 1)] = callargs_st; | ||||
|  | @ -1724,19 +1724,22 @@ | |||
|                     if (Py_TYPE(func) == &PyFunction_Type && | ||||
|                         tstate->interp->eval_frame == NULL && | ||||
|                         ((PyFunctionObject *)func)->vectorcall == _PyFunction_Vectorcall) { | ||||
|                         PyObject *callargs = PyStackRef_AsPyObjectSteal(callargs_st); | ||||
|                         assert(PyTuple_CheckExact(callargs)); | ||||
|                         PyObject *kwargs = PyStackRef_IsNull(kwargs_st) ? NULL : PyStackRef_AsPyObjectSteal(kwargs_st); | ||||
|                         assert(kwargs == NULL || PyDict_CheckExact(kwargs)); | ||||
|                         Py_ssize_t nargs = PyTuple_GET_SIZE(callargs); | ||||
|                         int code_flags = ((PyCodeObject *)PyFunction_GET_CODE(func))->co_flags; | ||||
|                         PyObject *locals = code_flags & CO_OPTIMIZED ? NULL : Py_NewRef(PyFunction_GET_GLOBALS(func)); | ||||
|                         stack_pointer[-1 - (oparg & 1)] = callargs_st; | ||||
|                         if (oparg & 1) stack_pointer[-(oparg & 1)] = kwargs_st; | ||||
|                         stack_pointer += -2 - (oparg & 1); | ||||
|                         assert(WITHIN_STACK_BOUNDS()); | ||||
|                         _PyFrame_SetStackPointer(frame, stack_pointer); | ||||
|                         _PyInterpreterFrame *new_frame = _PyEvalFramePushAndInit_Ex( | ||||
|                             tstate, func_st, locals, | ||||
|                             nargs, callargs, kwargs, frame); | ||||
|                         stack_pointer = _PyFrame_GetStackPointer(frame); | ||||
|                         // Need to sync the stack since we exit with DISPATCH_INLINED.
 | ||||
|                         stack_pointer += -3 - (oparg & 1); | ||||
|                         stack_pointer += -1; | ||||
|                         assert(WITHIN_STACK_BOUNDS()); | ||||
|                         if (new_frame == NULL) { | ||||
|                             goto error; | ||||
|  | @ -1745,6 +1748,10 @@ | |||
|                         frame->return_offset = 1; | ||||
|                         DISPATCH_INLINED(new_frame); | ||||
|                     } | ||||
|                     PyObject *callargs = PyStackRef_AsPyObjectBorrow(callargs_st); | ||||
|                     assert(PyTuple_CheckExact(callargs)); | ||||
|                     PyObject *kwargs = PyStackRef_AsPyObjectBorrow(kwargs_st); | ||||
|                     assert(kwargs == NULL || PyDict_CheckExact(kwargs)); | ||||
|                     stack_pointer[-1 - (oparg & 1)] = callargs_st; | ||||
|                     if (oparg & 1) stack_pointer[-(oparg & 1)] = kwargs_st; | ||||
|                     _PyFrame_SetStackPointer(frame, stack_pointer); | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ | |||
| #include "pycore_pymem.h"         // _PyMem_SetDefaultAllocator() | ||||
| #include "pycore_pystate.h" | ||||
| #include "pycore_runtime_init.h"  // _PyRuntimeState_INIT | ||||
| #include "pycore_stackref.h"      // Py_STACKREF_DEBUG | ||||
| #include "pycore_obmalloc.h"      // _PyMem_obmalloc_state_on_heap() | ||||
| #include "pycore_uniqueid.h"      // _PyObject_FinalizePerThreadRefcounts() | ||||
| 
 | ||||
|  | @ -663,6 +664,23 @@ init_interpreter(PyInterpreterState *interp, | |||
|         /* Fix the self-referential, statically initialized fields. */ | ||||
|         interp->dtoa = (struct _dtoa_state)_dtoa_state_INIT(interp); | ||||
|     } | ||||
| #if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG) | ||||
|     interp->next_stackref = 1; | ||||
|     _Py_hashtable_allocator_t alloc = { | ||||
|         .malloc = malloc, | ||||
|         .free = free, | ||||
|     }; | ||||
|     interp->stackref_debug_table = _Py_hashtable_new_full( | ||||
|         _Py_hashtable_hash_ptr, | ||||
|         _Py_hashtable_compare_direct, | ||||
|         NULL, | ||||
|         NULL, | ||||
|         &alloc | ||||
|     ); | ||||
|     _Py_stackref_associate(interp, Py_None, PyStackRef_None); | ||||
|     _Py_stackref_associate(interp, Py_False, PyStackRef_False); | ||||
|     _Py_stackref_associate(interp, Py_True, PyStackRef_True); | ||||
| #endif | ||||
| 
 | ||||
|     interp->_initialized = 1; | ||||
|     return _PyStatus_OK(); | ||||
|  | @ -768,6 +786,11 @@ PyInterpreterState_New(void) | |||
|     return interp; | ||||
| } | ||||
| 
 | ||||
| #if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG) | ||||
| extern void | ||||
| _Py_stackref_report_leaks(PyInterpreterState *interp); | ||||
| #endif | ||||
| 
 | ||||
| static void | ||||
| interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate) | ||||
| { | ||||
|  | @ -877,6 +900,12 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate) | |||
|     Py_CLEAR(interp->sysdict); | ||||
|     Py_CLEAR(interp->builtins); | ||||
| 
 | ||||
| #if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG) | ||||
|     _Py_stackref_report_leaks(interp); | ||||
|     _Py_hashtable_destroy(interp->stackref_debug_table); | ||||
|     interp->stackref_debug_table = NULL; | ||||
| #endif | ||||
| 
 | ||||
|     if (tstate->interp == interp) { | ||||
|         /* We are now safe to fix tstate->_status.cleared. */ | ||||
|         // XXX Do this (much) earlier?
 | ||||
|  |  | |||
							
								
								
									
										156
									
								
								Python/stackrefs.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								Python/stackrefs.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,156 @@ | |||
| 
 | ||||
| #include "Python.h" | ||||
| 
 | ||||
| #include "pycore_stackref.h" | ||||
| 
 | ||||
| #if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG) | ||||
| 
 | ||||
| #if SIZEOF_VOID_P < 8 | ||||
| #error "Py_STACKREF_DEBUG requires 64 bit machine" | ||||
| #endif | ||||
| 
 | ||||
| #include "pycore_interp.h" | ||||
| #include "pycore_hashtable.h" | ||||
| 
 | ||||
| typedef struct _table_entry { | ||||
|     PyObject *obj; | ||||
|     const char *classname; | ||||
|     const char *filename; | ||||
|     int linenumber; | ||||
|     const char *filename_borrow; | ||||
|     int linenumber_borrow; | ||||
| } TableEntry; | ||||
| 
 | ||||
| TableEntry * | ||||
| make_table_entry(PyObject *obj, const char *filename, int linenumber) | ||||
| { | ||||
|     TableEntry *result = malloc(sizeof(TableEntry)); | ||||
|     if (result == NULL) { | ||||
|         return NULL; | ||||
|     } | ||||
|     result->obj = obj; | ||||
|     result->classname = Py_TYPE(obj)->tp_name; | ||||
|     result->filename = filename; | ||||
|     result->linenumber = linenumber; | ||||
|     result->filename_borrow = NULL; | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| PyObject * | ||||
| _Py_stackref_get_object(_PyStackRef ref) | ||||
| { | ||||
|     if (ref.index == 0) { | ||||
|         return NULL; | ||||
|     } | ||||
|     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); | ||||
|     } | ||||
|     TableEntry *entry = _Py_hashtable_get(interp->stackref_debug_table, (void *)ref.index); | ||||
|     if (entry == NULL) { | ||||
|         _Py_FatalErrorFormat(__func__, "Accessing closed stack ref with ID %" PRIu64 "\n", ref.index); | ||||
|     } | ||||
|     return entry->obj; | ||||
| } | ||||
| 
 | ||||
| PyObject * | ||||
| _Py_stackref_close(_PyStackRef ref) | ||||
| { | ||||
|     PyInterpreterState *interp = PyInterpreterState_Get(); | ||||
|     if (ref.index >= interp->next_stackref) { | ||||
|         _Py_FatalErrorFormat(__func__, "Garbled stack ref with ID %" PRIu64 "\n", ref.index); | ||||
|     } | ||||
|     PyObject *obj; | ||||
|     if (ref.index <= LAST_PREDEFINED_STACKREF_INDEX) { | ||||
|         // Pre-allocated reference to None, False or True -- Do not clear
 | ||||
|         TableEntry *entry = _Py_hashtable_get(interp->stackref_debug_table, (void *)ref.index); | ||||
|         obj = entry->obj; | ||||
|     } | ||||
|     else { | ||||
|         TableEntry *entry = _Py_hashtable_steal(interp->stackref_debug_table, (void *)ref.index); | ||||
|         if (entry == NULL) { | ||||
|             _Py_FatalErrorFormat(__func__, "Invalid StackRef with ID %" PRIu64 "\n", (void *)ref.index); | ||||
|         } | ||||
|         obj = entry->obj; | ||||
|         free(entry); | ||||
|     } | ||||
|     return obj; | ||||
| } | ||||
| 
 | ||||
| _PyStackRef | ||||
| _Py_stackref_create(PyObject *obj, const char *filename, int linenumber) | ||||
| { | ||||
|     if (obj == NULL) { | ||||
|         Py_FatalError("Cannot create a stackref for NULL"); | ||||
|     } | ||||
|     PyInterpreterState *interp = PyInterpreterState_Get(); | ||||
|     uint64_t new_id = interp->next_stackref++; | ||||
|     TableEntry *entry = make_table_entry(obj, filename, linenumber); | ||||
|     if (entry == NULL) { | ||||
|         Py_FatalError("No memory left for stackref debug table"); | ||||
|     } | ||||
|     if (_Py_hashtable_set(interp->stackref_debug_table, (void *)new_id, entry) < 0) { | ||||
|         Py_FatalError("No memory left for stackref debug table"); | ||||
|     } | ||||
|     return (_PyStackRef){ .index = new_id }; | ||||
| } | ||||
| 
 | ||||
| void | ||||
| _Py_stackref_record_borrow(_PyStackRef ref, const char *filename, int linenumber) | ||||
| { | ||||
|     if (ref.index <= LAST_PREDEFINED_STACKREF_INDEX) { | ||||
|         return; | ||||
|     } | ||||
|     PyInterpreterState *interp = PyInterpreterState_Get(); | ||||
|     TableEntry *entry = _Py_hashtable_get(interp->stackref_debug_table, (void *)ref.index); | ||||
|     if (entry == NULL) { | ||||
|         _Py_FatalErrorFormat(__func__, "Invalid StackRef with ID %" PRIu64 "\n", (void *)ref.index); | ||||
|     } | ||||
|     entry->filename_borrow = filename; | ||||
|     entry->linenumber_borrow = linenumber; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void | ||||
| _Py_stackref_associate(PyInterpreterState *interp, PyObject *obj, _PyStackRef ref) | ||||
| { | ||||
|     assert(interp->next_stackref >= ref.index); | ||||
|     interp->next_stackref = ref.index+1; | ||||
|     TableEntry *entry = make_table_entry(obj, "builtin-object", 0); | ||||
|     if (entry == NULL) { | ||||
|         Py_FatalError("No memory left for stackref debug table"); | ||||
|     } | ||||
|     if (_Py_hashtable_set(interp->stackref_debug_table, (void *)ref.index, (void *)entry) < 0) { | ||||
|         Py_FatalError("No memory left for stackref debug table"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static int | ||||
| report_leak(_Py_hashtable_t *ht, const void *key, const void *value, void *leak) | ||||
| { | ||||
|     TableEntry *entry = (TableEntry *)value; | ||||
|     if (!_Py_IsStaticImmortal(entry->obj)) { | ||||
|         *(int *)leak = 1; | ||||
|         printf("Stackref leak. Refers to instance of %s at %p. Created at %s:%d", | ||||
|                entry->classname, entry->obj, entry->filename, entry->linenumber); | ||||
|         if (entry->filename_borrow != NULL) { | ||||
|             printf(". Last borrow at %s:%d",entry->filename_borrow, entry->linenumber_borrow); | ||||
|         } | ||||
|         printf("\n"); | ||||
|     } | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| void | ||||
| _Py_stackref_report_leaks(PyInterpreterState *interp) | ||||
| { | ||||
|     int leak = 0; | ||||
|     _Py_hashtable_foreach(interp->stackref_debug_table, report_leak, &leak); | ||||
|     if (leak) { | ||||
|         Py_FatalError("Stackrefs leaked."); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Mark Shannon
						Mark Shannon