diff options
Diffstat (limited to 'Python')
-rw-r--r-- | Python/bytecodes.c | 17 | ||||
-rw-r--r-- | Python/ceval.c | 67 | ||||
-rw-r--r-- | Python/ceval_macros.h | 6 | ||||
-rw-r--r-- | Python/executor_cases.c.h | 2 | ||||
-rw-r--r-- | Python/generated_cases.c.h | 23 | ||||
-rw-r--r-- | Python/pystate.c | 29 | ||||
-rw-r--r-- | Python/stackrefs.c | 156 |
7 files changed, 269 insertions, 31 deletions
diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 30c12dd..63cf197 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -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); diff --git a/Python/ceval.c b/Python/ceval.c index bfdf568..3cf11b6 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -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; } diff --git a/Python/ceval_macros.h b/Python/ceval_macros.h index 398816d..f15633f 100644 --- a/Python/ceval_macros.h +++ b/Python/ceval_macros.h @@ -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) diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 6e752c5..2233502 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -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); diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index ee5c55a..bed16b6 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -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); diff --git a/Python/pystate.c b/Python/pystate.c index 839413a..c546b7c 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -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? diff --git a/Python/stackrefs.c b/Python/stackrefs.c new file mode 100644 index 0000000..9bb4689 --- /dev/null +++ b/Python/stackrefs.c @@ -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 |