From 177e4b19a089b3c853c03a82d67c1e21608ec436 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Mon, 6 Nov 2023 12:11:53 -0700 Subject: [PATCH 1/7] Revert "gh-76785: Move _Py_excinfo Functions Out of the Internal C-API (gh-111715)" This reverts commit d4426e8d001cfb4590911e2e7de6963e12529faf. --- Include/internal/pycore_crossinterp.h | 11 -- Include/internal/pycore_pyerrors.h | 24 ++++ Python/crossinterp.c | 123 ------------------ Python/errors.c | 175 ++++++++++++++++++++++++++ 4 files changed, 199 insertions(+), 134 deletions(-) diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h index ee9ff0090c2484..9600dfb9600e60 100644 --- a/Include/internal/pycore_crossinterp.h +++ b/Include/internal/pycore_crossinterp.h @@ -164,17 +164,6 @@ extern void _PyXI_Fini(PyInterpreterState *interp); /* short-term data sharing */ /***************************/ -// Ultimately we'd like to preserve enough information about the -// exception and traceback that we could re-constitute (or at least -// simulate, a la traceback.TracebackException), and even chain, a copy -// of the exception in the calling interpreter. - -typedef struct _excinfo { - const char *type; - const char *msg; -} _Py_excinfo; - - typedef enum error_code { _PyXI_ERR_NO_ERROR = 0, _PyXI_ERR_UNCAUGHT_EXCEPTION = -1, diff --git a/Include/internal/pycore_pyerrors.h b/Include/internal/pycore_pyerrors.h index 0f16fb894d17e1..a953d2bb18d4ad 100644 --- a/Include/internal/pycore_pyerrors.h +++ b/Include/internal/pycore_pyerrors.h @@ -68,6 +68,30 @@ extern PyStatus _PyErr_InitTypes(PyInterpreterState *); extern void _PyErr_FiniTypes(PyInterpreterState *); +/* exception snapshots */ + +// Ultimately we'd like to preserve enough information about the +// exception and traceback that we could re-constitute (or at least +// simulate, a la traceback.TracebackException), and even chain, a copy +// of the exception in the calling interpreter. + +typedef struct _excinfo { + const char *type; + const char *msg; +} _Py_excinfo; + +extern void _Py_excinfo_Clear(_Py_excinfo *info); +extern int _Py_excinfo_Copy(_Py_excinfo *dest, _Py_excinfo *src); +extern const char * _Py_excinfo_InitFromException( + _Py_excinfo *info, + PyObject *exc); +extern void _Py_excinfo_Apply(_Py_excinfo *info, PyObject *exctype); +extern const char * _Py_excinfo_AsUTF8( + _Py_excinfo *info, + char *buf, + size_t bufsize); + + /* other API */ static inline PyObject* _PyErr_Occurred(PyThreadState *tstate) diff --git a/Python/crossinterp.c b/Python/crossinterp.c index de28cb7071740a..a65355a49c5252 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -800,17 +800,6 @@ _xidregistry_fini(struct _xidregistry *registry) /* convenience utilities */ /*************************/ -static const char * -_copy_raw_string(const char *str) -{ - char *copied = PyMem_RawMalloc(strlen(str)+1); - if (copied == NULL) { - return NULL; - } - strcpy(copied, str); - return copied; -} - static const char * _copy_string_obj_raw(PyObject *strobj) { @@ -846,118 +835,6 @@ _release_xid_data(_PyCrossInterpreterData *data, int rawfree) } -/* exception snapshots */ - -static int -_exc_type_name_as_utf8(PyObject *exc, const char **p_typename) -{ - // XXX Use PyObject_GetAttrString(Py_TYPE(exc), '__name__')? - PyObject *nameobj = PyUnicode_FromString(Py_TYPE(exc)->tp_name); - if (nameobj == NULL) { - assert(PyErr_Occurred()); - *p_typename = "unable to format exception type name"; - return -1; - } - const char *name = PyUnicode_AsUTF8(nameobj); - if (name == NULL) { - assert(PyErr_Occurred()); - Py_DECREF(nameobj); - *p_typename = "unable to encode exception type name"; - return -1; - } - name = _copy_raw_string(name); - Py_DECREF(nameobj); - if (name == NULL) { - *p_typename = "out of memory copying exception type name"; - return -1; - } - *p_typename = name; - return 0; -} - -static int -_exc_msg_as_utf8(PyObject *exc, const char **p_msg) -{ - PyObject *msgobj = PyObject_Str(exc); - if (msgobj == NULL) { - assert(PyErr_Occurred()); - *p_msg = "unable to format exception message"; - return -1; - } - const char *msg = PyUnicode_AsUTF8(msgobj); - if (msg == NULL) { - assert(PyErr_Occurred()); - Py_DECREF(msgobj); - *p_msg = "unable to encode exception message"; - return -1; - } - msg = _copy_raw_string(msg); - Py_DECREF(msgobj); - if (msg == NULL) { - assert(PyErr_ExceptionMatches(PyExc_MemoryError)); - *p_msg = "out of memory copying exception message"; - return -1; - } - *p_msg = msg; - return 0; -} - -static void -_Py_excinfo_Clear(_Py_excinfo *info) -{ - if (info->type != NULL) { - PyMem_RawFree((void *)info->type); - } - if (info->msg != NULL) { - PyMem_RawFree((void *)info->msg); - } - *info = (_Py_excinfo){ NULL }; -} - -static const char * -_Py_excinfo_InitFromException(_Py_excinfo *info, PyObject *exc) -{ - assert(exc != NULL); - - // Extract the exception type name. - const char *typename = NULL; - if (_exc_type_name_as_utf8(exc, &typename) < 0) { - assert(typename != NULL); - return typename; - } - - // Extract the exception message. - const char *msg = NULL; - if (_exc_msg_as_utf8(exc, &msg) < 0) { - assert(msg != NULL); - return msg; - } - - info->type = typename; - info->msg = msg; - return NULL; -} - -static void -_Py_excinfo_Apply(_Py_excinfo *info, PyObject *exctype) -{ - if (info->type != NULL) { - if (info->msg != NULL) { - PyErr_Format(exctype, "%s: %s", info->type, info->msg); - } - else { - PyErr_SetString(exctype, info->type); - } - } - else if (info->msg != NULL) { - PyErr_SetString(exctype, info->msg); - } - else { - PyErr_SetNone(exctype); - } -} - - /***************************/ /* short-term data sharing */ /***************************/ diff --git a/Python/errors.c b/Python/errors.c index ed5eec5c261970..c55ebfdb502d61 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -1934,3 +1934,178 @@ PyErr_ProgramTextObject(PyObject *filename, int lineno) { return _PyErr_ProgramDecodedTextObject(filename, lineno, NULL); } + + +/***********************/ +/* exception snapshots */ +/***********************/ + +static const char * +_copy_raw_string(const char *str) +{ + char *copied = PyMem_RawMalloc(strlen(str)+1); + if (copied == NULL) { + return NULL; + } + strcpy(copied, str); + return copied; +} + +static int +_exc_type_name_as_utf8(PyObject *exc, const char **p_typename) +{ + // XXX Use PyObject_GetAttrString(Py_TYPE(exc), '__name__')? + PyObject *nameobj = PyUnicode_FromString(Py_TYPE(exc)->tp_name); + if (nameobj == NULL) { + assert(PyErr_Occurred()); + *p_typename = "unable to format exception type name"; + return -1; + } + const char *name = PyUnicode_AsUTF8(nameobj); + if (name == NULL) { + assert(PyErr_Occurred()); + Py_DECREF(nameobj); + *p_typename = "unable to encode exception type name"; + return -1; + } + name = _copy_raw_string(name); + Py_DECREF(nameobj); + if (name == NULL) { + *p_typename = "out of memory copying exception type name"; + return -1; + } + *p_typename = name; + return 0; +} + +static int +_exc_msg_as_utf8(PyObject *exc, const char **p_msg) +{ + PyObject *msgobj = PyObject_Str(exc); + if (msgobj == NULL) { + assert(PyErr_Occurred()); + *p_msg = "unable to format exception message"; + return -1; + } + const char *msg = PyUnicode_AsUTF8(msgobj); + if (msg == NULL) { + assert(PyErr_Occurred()); + Py_DECREF(msgobj); + *p_msg = "unable to encode exception message"; + return -1; + } + msg = _copy_raw_string(msg); + Py_DECREF(msgobj); + if (msg == NULL) { + assert(PyErr_ExceptionMatches(PyExc_MemoryError)); + *p_msg = "out of memory copying exception message"; + return -1; + } + *p_msg = msg; + return 0; +} + +void +_Py_excinfo_Clear(_Py_excinfo *info) +{ + if (info->type != NULL) { + PyMem_RawFree((void *)info->type); + } + if (info->msg != NULL) { + PyMem_RawFree((void *)info->msg); + } + *info = (_Py_excinfo){ NULL }; +} + +int +_Py_excinfo_Copy(_Py_excinfo *dest, _Py_excinfo *src) +{ + // XXX Clear dest first? + + if (src->type == NULL) { + dest->type = NULL; + } + else { + dest->type = _copy_raw_string(src->type); + if (dest->type == NULL) { + return -1; + } + } + + if (src->msg == NULL) { + dest->msg = NULL; + } + else { + dest->msg = _copy_raw_string(src->msg); + if (dest->msg == NULL) { + return -1; + } + } + + return 0; +} + +const char * +_Py_excinfo_InitFromException(_Py_excinfo *info, PyObject *exc) +{ + assert(exc != NULL); + + // Extract the exception type name. + const char *typename = NULL; + if (_exc_type_name_as_utf8(exc, &typename) < 0) { + assert(typename != NULL); + return typename; + } + + // Extract the exception message. + const char *msg = NULL; + if (_exc_msg_as_utf8(exc, &msg) < 0) { + assert(msg != NULL); + return msg; + } + + info->type = typename; + info->msg = msg; + return NULL; +} + +void +_Py_excinfo_Apply(_Py_excinfo *info, PyObject *exctype) +{ + if (info->type != NULL) { + if (info->msg != NULL) { + PyErr_Format(exctype, "%s: %s", info->type, info->msg); + } + else { + PyErr_SetString(exctype, info->type); + } + } + else if (info->msg != NULL) { + PyErr_SetString(exctype, info->msg); + } + else { + PyErr_SetNone(exctype); + } +} + +const char * +_Py_excinfo_AsUTF8(_Py_excinfo *info, char *buf, size_t bufsize) +{ + // XXX Dynamically allocate if no buf provided? + assert(buf != NULL); + if (info->type != NULL) { + if (info->msg != NULL) { + snprintf(buf, bufsize, "%s: %s", info->type, info->msg); + return buf; + } + else { + return info->type; + } + } + else if (info->msg != NULL) { + return info->msg; + } + else { + return NULL; + } +} From 73e49185ae43fc2b32b0c31a4fcf74b589bfadc8 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 21 Sep 2023 16:36:39 -0600 Subject: [PATCH 2/7] Add the ExceptionSnapshot type. --- Modules/_xxsubinterpretersmodule.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 001fa887847cbd..6d60219ef65975 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -51,6 +51,9 @@ add_new_exception(PyObject *mod, const char *name, PyObject *base) /* module state *************************************************************/ typedef struct { + /* heap types */ + PyTypeObject *ExceptionSnapshotType; + /* exceptions */ PyObject *RunFailedError; } module_state; @@ -67,6 +70,9 @@ get_module_state(PyObject *mod) static int traverse_module_state(module_state *state, visitproc visit, void *arg) { + /* heap types */ + Py_VISIT(state->ExceptionSnapshotType); + /* exceptions */ Py_VISIT(state->RunFailedError); @@ -76,6 +82,9 @@ traverse_module_state(module_state *state, visitproc visit, void *arg) static int clear_module_state(module_state *state) { + /* heap types */ + Py_CLEAR(state->ExceptionSnapshotType); + /* exceptions */ Py_CLEAR(state->RunFailedError); @@ -759,6 +768,11 @@ The 'interpreters' module provides a more convenient interface."); static int module_exec(PyObject *mod) { + module_state *state = get_module_state(mod); + if (state == NULL) { + goto error; + } + /* Add exception types */ if (exceptions_init(mod) != 0) { goto error; @@ -769,6 +783,15 @@ module_exec(PyObject *mod) goto error; } + // ExceptionSnapshot + state->ExceptionSnapshotType = PyStructSequence_NewType(&exc_snapshot_desc); + if (state->ExceptionSnapshotType == NULL) { + goto error; + } + if (PyModule_AddType(mod, state->ExceptionSnapshotType) < 0) { + goto error; + } + return 0; error: From 80415632cb5b04f2af000116492b53fde8b2a3b0 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 4 Oct 2023 21:39:17 -0600 Subject: [PATCH 3/7] Use a custom type for ExceptionSnapshot, with new _excinfo & _error_code structs. --- Modules/_xxsubinterpretersmodule.c | 187 ++++++++++++++++++++++++++++- 1 file changed, 185 insertions(+), 2 deletions(-) diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 6d60219ef65975..b12df4160f569a 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -28,6 +28,22 @@ _get_current_interp(void) return PyInterpreterState_Get(); } +static PyObject * +_get_current_module(void) +{ + PyObject *name = PyUnicode_FromString(MODULE_NAME); + if (name == NULL) { + return NULL; + } + PyObject *mod = PyImport_GetModule(name); + Py_DECREF(name); + if (mod == NULL) { + return NULL; + } + assert(mod != Py_None); + return mod; +} + static PyObject * add_new_exception(PyObject *mod, const char *name, PyObject *base) { @@ -67,6 +83,21 @@ get_module_state(PyObject *mod) return state; } +static module_state * +_get_current_module_state(void) +{ + PyObject *mod = _get_current_module(); + if (mod == NULL) { + // XXX import it? + PyErr_SetString(PyExc_RuntimeError, + MODULE_NAME " module not imported yet"); + return NULL; + } + module_state *state = get_module_state(mod); + Py_DECREF(mod); + return state; +} + static int traverse_module_state(module_state *state, visitproc visit, void *arg) { @@ -184,6 +215,159 @@ get_code_str(PyObject *arg, Py_ssize_t *len_p, PyObject **bytes_p, int *flags_p) } +/* exception snapshot objects ***********************************************/ + +typedef struct exc_snapshot { + PyObject_HEAD + _Py_excinfo info; +} exc_snapshot; + +static PyObject * +exc_snapshot_from_info(PyTypeObject *cls, _Py_excinfo *info) +{ + exc_snapshot *self = (exc_snapshot *)PyObject_New(exc_snapshot, cls); + if (self == NULL) { + PyErr_NoMemory(); + return NULL; + } + if (_Py_excinfo_Copy(&self->info, info) < 0) { + Py_DECREF(self); + } + return (PyObject *)self; +} + +static void +exc_snapshot_dealloc(exc_snapshot *self) +{ + PyTypeObject *tp = Py_TYPE(self); + _Py_excinfo_Clear(&self->info); + tp->tp_free(self); + /* "Instances of heap-allocated types hold a reference to their type." + * See: https://docs.python.org/3.11/howto/isolating-extensions.html#garbage-collection-protocol + * See: https://docs.python.org/3.11/c-api/typeobj.html#c.PyTypeObject.tp_traverse + */ + // XXX Why don't we implement Py_TPFLAGS_HAVE_GC, e.g. Py_tp_traverse, + // like we do for _abc._abc_data? + Py_DECREF(tp); +} + +static PyObject * +exc_snapshot_repr(exc_snapshot *self) +{ + PyTypeObject *type = Py_TYPE(self); + const char *clsname = _PyType_Name(type); + return PyUnicode_FromFormat("%s(name='%s', msg='%s')", + clsname, self->info.type, self->info.msg); +} + +static PyObject * +exc_snapshot_str(exc_snapshot *self) +{ + char buf[256]; + const char *msg = _Py_excinfo_AsUTF8(&self->info, buf, 256); + if (msg == NULL) { + msg = ""; + } + return PyUnicode_FromString(msg); +} + +static Py_hash_t +exc_snapshot_hash(exc_snapshot *self) +{ + PyObject *str = exc_snapshot_str(self); + if (str == NULL) { + return -1; + } + Py_hash_t hash = PyObject_Hash(str); + Py_DECREF(str); + return hash; +} + +PyDoc_STRVAR(exc_snapshot_doc, +"ExceptionSnapshot\n\ +\n\ +A minimal summary of a raised exception."); + +static PyMemberDef exc_snapshot_members[] = { +#define OFFSET(field) \ + (offsetof(exc_snapshot, info) + offsetof(_Py_excinfo, field)) + {"type", Py_T_STRING, OFFSET(type), Py_READONLY, + PyDoc_STR("the name of the original exception type")}, + {"msg", Py_T_STRING, OFFSET(msg), Py_READONLY, + PyDoc_STR("the message string of the original exception")}, +#undef OFFSET + {NULL} +}; + +static PyObject * +exc_snapshot_apply(exc_snapshot *self, PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"exctype", NULL}; + PyObject *exctype = NULL; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, + "|O:ExceptionSnapshot.apply" , kwlist, + &exctype)) { + return NULL; + } + + if (exctype == NULL) { + module_state *state = _get_current_module_state(); + if (state == NULL) { + return NULL; + } + exctype = state->RunFailedError; + } + + _Py_excinfo_Apply(&self->info, exctype); + return NULL; +} + +PyDoc_STRVAR(exc_snapshot_apply_doc, +"Raise an exception based on the snapshot."); + +static PyMethodDef exc_snapshot_methods[] = { + {"apply", _PyCFunction_CAST(exc_snapshot_apply), + METH_VARARGS | METH_KEYWORDS, exc_snapshot_apply_doc}, + {NULL} +}; + +static PyType_Slot ExcSnapshotType_slots[] = { + {Py_tp_dealloc, (destructor)exc_snapshot_dealloc}, + {Py_tp_doc, (void *)exc_snapshot_doc}, + {Py_tp_repr, (reprfunc)exc_snapshot_repr}, + {Py_tp_str, (reprfunc)exc_snapshot_str}, + {Py_tp_hash, exc_snapshot_hash}, + {Py_tp_members, exc_snapshot_members}, + {Py_tp_methods, exc_snapshot_methods}, + {0, NULL}, +}; + +static PyType_Spec ExcSnapshotType_spec = { + .name = MODULE_NAME ".ExceptionSnapshot", + .basicsize = sizeof(exc_snapshot), + .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE), + .slots = ExcSnapshotType_slots, +}; + +static int +ExceptionSnapshot_InitType(PyObject *mod, PyTypeObject **p_type) +{ + if (*p_type != NULL) { + return 0; + } + + PyTypeObject *cls = (PyTypeObject *)PyType_FromMetaclass( + NULL, mod, &ExcSnapshotType_spec, NULL); + if (cls == NULL) { + return -1; + } + + *p_type = cls; + return 0; +} + + /* interpreter-specific code ************************************************/ static int @@ -784,8 +968,7 @@ module_exec(PyObject *mod) } // ExceptionSnapshot - state->ExceptionSnapshotType = PyStructSequence_NewType(&exc_snapshot_desc); - if (state->ExceptionSnapshotType == NULL) { + if (ExceptionSnapshot_InitType(mod, &state->ExceptionSnapshotType) < 0) { goto error; } if (PyModule_AddType(mod, state->ExceptionSnapshotType) < 0) { From 8be52ae35544717705162e420f2cb73085882183 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 24 Oct 2023 16:32:37 -0600 Subject: [PATCH 4/7] Move ExceptionSnapshot to the interpeter state. --- Include/internal/pycore_exceptions.h | 13 +- Modules/_xxsubinterpretersmodule.c | 191 +----------------------- Objects/exceptions.c | 207 ++++++++++++++++++++++++++- 3 files changed, 215 insertions(+), 196 deletions(-) diff --git a/Include/internal/pycore_exceptions.h b/Include/internal/pycore_exceptions.h index 4a9df709131998..b1c8e48e00ad62 100644 --- a/Include/internal/pycore_exceptions.h +++ b/Include/internal/pycore_exceptions.h @@ -8,6 +8,8 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif +#include "pycore_pyerrors.h" + /* runtime lifecycle */ @@ -17,7 +19,7 @@ extern int _PyExc_InitTypes(PyInterpreterState *); extern void _PyExc_Fini(PyInterpreterState *); -/* other API */ +/* runtime state */ struct _Py_exc_state { // The dict mapping from errno codes to OSError subclasses @@ -26,10 +28,19 @@ struct _Py_exc_state { int memerrors_numfree; // The ExceptionGroup type PyObject *PyExc_ExceptionGroup; + + PyTypeObject *ExceptionSnapshotType; }; extern void _PyExc_ClearExceptionGroupType(PyInterpreterState *); +/* other API */ + +PyAPI_FUNC(PyTypeObject *) _PyExc_GetExceptionSnapshotType( + PyInterpreterState *interp); + +PyAPI_FUNC(PyObject *) PyExceptionSnapshot_FromInfo(_Py_excinfo *info); + #ifdef __cplusplus } diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index b12df4160f569a..a8d870226304e6 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -7,7 +7,7 @@ #include "Python.h" #include "pycore_crossinterp.h" // struct _xid -#include "pycore_pyerrors.h" // _Py_excinfo +#include "pycore_exceptions.h" // PyExceptionSnapshot_FromInfo() #include "pycore_initconfig.h" // _PyErr_SetFromPyStatus() #include "pycore_modsupport.h" // _PyArg_BadArgument() #include "pycore_pyerrors.h" // _PyErr_ChainExceptions1() @@ -28,22 +28,6 @@ _get_current_interp(void) return PyInterpreterState_Get(); } -static PyObject * -_get_current_module(void) -{ - PyObject *name = PyUnicode_FromString(MODULE_NAME); - if (name == NULL) { - return NULL; - } - PyObject *mod = PyImport_GetModule(name); - Py_DECREF(name); - if (mod == NULL) { - return NULL; - } - assert(mod != Py_None); - return mod; -} - static PyObject * add_new_exception(PyObject *mod, const char *name, PyObject *base) { @@ -83,21 +67,6 @@ get_module_state(PyObject *mod) return state; } -static module_state * -_get_current_module_state(void) -{ - PyObject *mod = _get_current_module(); - if (mod == NULL) { - // XXX import it? - PyErr_SetString(PyExc_RuntimeError, - MODULE_NAME " module not imported yet"); - return NULL; - } - module_state *state = get_module_state(mod); - Py_DECREF(mod); - return state; -} - static int traverse_module_state(module_state *state, visitproc visit, void *arg) { @@ -215,159 +184,6 @@ get_code_str(PyObject *arg, Py_ssize_t *len_p, PyObject **bytes_p, int *flags_p) } -/* exception snapshot objects ***********************************************/ - -typedef struct exc_snapshot { - PyObject_HEAD - _Py_excinfo info; -} exc_snapshot; - -static PyObject * -exc_snapshot_from_info(PyTypeObject *cls, _Py_excinfo *info) -{ - exc_snapshot *self = (exc_snapshot *)PyObject_New(exc_snapshot, cls); - if (self == NULL) { - PyErr_NoMemory(); - return NULL; - } - if (_Py_excinfo_Copy(&self->info, info) < 0) { - Py_DECREF(self); - } - return (PyObject *)self; -} - -static void -exc_snapshot_dealloc(exc_snapshot *self) -{ - PyTypeObject *tp = Py_TYPE(self); - _Py_excinfo_Clear(&self->info); - tp->tp_free(self); - /* "Instances of heap-allocated types hold a reference to their type." - * See: https://docs.python.org/3.11/howto/isolating-extensions.html#garbage-collection-protocol - * See: https://docs.python.org/3.11/c-api/typeobj.html#c.PyTypeObject.tp_traverse - */ - // XXX Why don't we implement Py_TPFLAGS_HAVE_GC, e.g. Py_tp_traverse, - // like we do for _abc._abc_data? - Py_DECREF(tp); -} - -static PyObject * -exc_snapshot_repr(exc_snapshot *self) -{ - PyTypeObject *type = Py_TYPE(self); - const char *clsname = _PyType_Name(type); - return PyUnicode_FromFormat("%s(name='%s', msg='%s')", - clsname, self->info.type, self->info.msg); -} - -static PyObject * -exc_snapshot_str(exc_snapshot *self) -{ - char buf[256]; - const char *msg = _Py_excinfo_AsUTF8(&self->info, buf, 256); - if (msg == NULL) { - msg = ""; - } - return PyUnicode_FromString(msg); -} - -static Py_hash_t -exc_snapshot_hash(exc_snapshot *self) -{ - PyObject *str = exc_snapshot_str(self); - if (str == NULL) { - return -1; - } - Py_hash_t hash = PyObject_Hash(str); - Py_DECREF(str); - return hash; -} - -PyDoc_STRVAR(exc_snapshot_doc, -"ExceptionSnapshot\n\ -\n\ -A minimal summary of a raised exception."); - -static PyMemberDef exc_snapshot_members[] = { -#define OFFSET(field) \ - (offsetof(exc_snapshot, info) + offsetof(_Py_excinfo, field)) - {"type", Py_T_STRING, OFFSET(type), Py_READONLY, - PyDoc_STR("the name of the original exception type")}, - {"msg", Py_T_STRING, OFFSET(msg), Py_READONLY, - PyDoc_STR("the message string of the original exception")}, -#undef OFFSET - {NULL} -}; - -static PyObject * -exc_snapshot_apply(exc_snapshot *self, PyObject *args, PyObject *kwargs) -{ - static char *kwlist[] = {"exctype", NULL}; - PyObject *exctype = NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwargs, - "|O:ExceptionSnapshot.apply" , kwlist, - &exctype)) { - return NULL; - } - - if (exctype == NULL) { - module_state *state = _get_current_module_state(); - if (state == NULL) { - return NULL; - } - exctype = state->RunFailedError; - } - - _Py_excinfo_Apply(&self->info, exctype); - return NULL; -} - -PyDoc_STRVAR(exc_snapshot_apply_doc, -"Raise an exception based on the snapshot."); - -static PyMethodDef exc_snapshot_methods[] = { - {"apply", _PyCFunction_CAST(exc_snapshot_apply), - METH_VARARGS | METH_KEYWORDS, exc_snapshot_apply_doc}, - {NULL} -}; - -static PyType_Slot ExcSnapshotType_slots[] = { - {Py_tp_dealloc, (destructor)exc_snapshot_dealloc}, - {Py_tp_doc, (void *)exc_snapshot_doc}, - {Py_tp_repr, (reprfunc)exc_snapshot_repr}, - {Py_tp_str, (reprfunc)exc_snapshot_str}, - {Py_tp_hash, exc_snapshot_hash}, - {Py_tp_members, exc_snapshot_members}, - {Py_tp_methods, exc_snapshot_methods}, - {0, NULL}, -}; - -static PyType_Spec ExcSnapshotType_spec = { - .name = MODULE_NAME ".ExceptionSnapshot", - .basicsize = sizeof(exc_snapshot), - .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | - Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE), - .slots = ExcSnapshotType_slots, -}; - -static int -ExceptionSnapshot_InitType(PyObject *mod, PyTypeObject **p_type) -{ - if (*p_type != NULL) { - return 0; - } - - PyTypeObject *cls = (PyTypeObject *)PyType_FromMetaclass( - NULL, mod, &ExcSnapshotType_spec, NULL); - if (cls == NULL) { - return -1; - } - - *p_type = cls; - return 0; -} - - /* interpreter-specific code ************************************************/ static int @@ -952,6 +768,7 @@ The 'interpreters' module provides a more convenient interface."); static int module_exec(PyObject *mod) { + PyInterpreterState *interp = PyInterpreterState_Get(); module_state *state = get_module_state(mod); if (state == NULL) { goto error; @@ -968,9 +785,7 @@ module_exec(PyObject *mod) } // ExceptionSnapshot - if (ExceptionSnapshot_InitType(mod, &state->ExceptionSnapshotType) < 0) { - goto error; - } + state->ExceptionSnapshotType = _PyExc_GetExceptionSnapshotType(interp); if (PyModule_AddType(mod, state->ExceptionSnapshotType) < 0) { goto error; } diff --git a/Objects/exceptions.c b/Objects/exceptions.c index a685ed803cd02d..a6d396bfe64a48 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -26,9 +26,8 @@ PyObject *PyExc_WindowsError = NULL; // borrowed ref static struct _Py_exc_state* -get_exc_state(void) +get_exc_state(PyInterpreterState *interp) { - PyInterpreterState *interp = _PyInterpreterState_GET(); return &interp->exc_state; } @@ -697,7 +696,8 @@ _PyBaseExceptionGroupObject_cast(PyObject *exc) static PyObject * BaseExceptionGroup_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { - struct _Py_exc_state *state = get_exc_state(); + PyInterpreterState *interp = _PyInterpreterState_GET(); + struct _Py_exc_state *state = get_exc_state(interp); PyTypeObject *PyExc_ExceptionGroup = (PyTypeObject*)state->PyExc_ExceptionGroup; @@ -1491,7 +1491,8 @@ ComplexExtendsException(PyExc_BaseException, BaseExceptionGroup, */ static PyObject* create_exception_group_class(void) { - struct _Py_exc_state *state = get_exc_state(); + PyInterpreterState *interp = _PyInterpreterState_GET(); + struct _Py_exc_state *state = get_exc_state(interp); PyObject *bases = PyTuple_Pack( 2, PyExc_BaseExceptionGroup, PyExc_Exception); @@ -1858,7 +1859,8 @@ OSError_new(PyTypeObject *type, PyObject *args, PyObject *kwds) )) goto error; - struct _Py_exc_state *state = get_exc_state(); + PyInterpreterState *interp = _PyInterpreterState_GET(); + struct _Py_exc_state *state = get_exc_state(interp); if (myerrno && PyLong_Check(myerrno) && state->errnomap && (PyObject *) type == PyExc_OSError) { PyObject *newtype; @@ -3283,7 +3285,8 @@ static PyObject * get_memory_error(int allow_allocation, PyObject *args, PyObject *kwds) { PyBaseExceptionObject *self; - struct _Py_exc_state *state = get_exc_state(); + PyInterpreterState *interp = _PyInterpreterState_GET(); + struct _Py_exc_state *state = get_exc_state(interp); if (state->memerrors_freelist == NULL) { if (!allow_allocation) { PyInterpreterState *interp = _PyInterpreterState_GET(); @@ -3352,7 +3355,8 @@ MemoryError_dealloc(PyBaseExceptionObject *self) return; } - struct _Py_exc_state *state = get_exc_state(); + PyInterpreterState *interp = _PyInterpreterState_GET(); + struct _Py_exc_state *state = get_exc_state(interp); if (state->memerrors_numfree >= MEMERRORS_SAVE) { Py_TYPE(self)->tp_free((PyObject *)self); } @@ -3660,6 +3664,9 @@ static struct static_exception static_exceptions[] = { }; +static int +_exc_snapshot_init_type(PyInterpreterState *interp); + int _PyExc_InitTypes(PyInterpreterState *interp) { @@ -3669,13 +3676,20 @@ _PyExc_InitTypes(PyInterpreterState *interp) return -1; } } + if (_exc_snapshot_init_type(interp) < 0) { + return -1; + } return 0; } +static void +_exc_snapshot_clear_type(PyInterpreterState *interp); + static void _PyExc_FiniTypes(PyInterpreterState *interp) { + _exc_snapshot_clear_type(interp); for (Py_ssize_t i=Py_ARRAY_LENGTH(static_exceptions) - 1; i >= 0; i--) { PyTypeObject *exc = static_exceptions[i].exc; _PyStaticType_Dealloc(interp, exc); @@ -3824,3 +3838,182 @@ _PyException_AddNote(PyObject *exc, PyObject *note) return res; } + +/* exception snapshots */ + +typedef struct exc_snapshot { + PyObject_HEAD + _Py_excinfo info; +} PyExceptionSnapshotObject; + +static void +exc_snapshot_dealloc(PyExceptionSnapshotObject *self) +{ + PyTypeObject *tp = Py_TYPE(self); + _Py_excinfo_Clear(&self->info); + tp->tp_free(self); + /* "Instances of heap-allocated types hold a reference to their type." + * See: https://docs.python.org/3.11/howto/isolating-extensions.html#garbage-collection-protocol + * See: https://docs.python.org/3.11/c-api/typeobj.html#c.PyTypeObject.tp_traverse + */ + // XXX Why don't we implement Py_TPFLAGS_HAVE_GC, e.g. Py_tp_traverse, + // like we do for _abc._abc_data? + Py_DECREF(tp); +} + +static PyObject * +exc_snapshot_repr(PyExceptionSnapshotObject *self) +{ + PyTypeObject *type = Py_TYPE(self); + const char *clsname = _PyType_Name(type); + return PyUnicode_FromFormat("%s(name='%s', msg='%s')", + clsname, self->info.type, self->info.msg); +} + +static PyObject * +exc_snapshot_str(PyExceptionSnapshotObject *self) +{ + char buf[256]; + const char *msg = _Py_excinfo_AsUTF8(&self->info, buf, 256); + if (msg == NULL) { + msg = ""; + } + return PyUnicode_FromString(msg); +} + +static Py_hash_t +exc_snapshot_hash(PyExceptionSnapshotObject *self) +{ + PyObject *str = exc_snapshot_str(self); + if (str == NULL) { + return -1; + } + Py_hash_t hash = PyObject_Hash(str); + Py_DECREF(str); + return hash; +} + +PyDoc_STRVAR(exc_snapshot_doc, +"ExceptionSnapshot\n\ +\n\ +A minimal summary of a raised exception."); + +static PyMemberDef exc_snapshot_members[] = { +#define OFFSET(field) \ + (offsetof(PyExceptionSnapshotObject, info) + offsetof(_Py_excinfo, field)) + {"type", Py_T_STRING, OFFSET(type), Py_READONLY, + PyDoc_STR("the name of the original exception type")}, + {"msg", Py_T_STRING, OFFSET(msg), Py_READONLY, + PyDoc_STR("the message string of the original exception")}, +#undef OFFSET + {NULL} +}; + +static PyObject * +exc_snapshot_apply(PyExceptionSnapshotObject *self, + PyObject *args, PyObject *kwargs) +{ + static char *kwlist[] = {"exctype", NULL}; + PyObject *exctype = NULL; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, + "|O:ExceptionSnapshot.apply" , kwlist, + &exctype)) { + return NULL; + } + + if (exctype == NULL) { + exctype = PyExc_RuntimeError; + } + + _Py_excinfo_Apply(&self->info, exctype); + return NULL; +} + +PyDoc_STRVAR(exc_snapshot_apply_doc, +"Raise an exception based on the snapshot."); + +static PyMethodDef exc_snapshot_methods[] = { + {"apply", _PyCFunction_CAST(exc_snapshot_apply), + METH_VARARGS | METH_KEYWORDS, exc_snapshot_apply_doc}, + {NULL} +}; + +static PyType_Slot ExcSnapshotType_slots[] = { + {Py_tp_dealloc, (destructor)exc_snapshot_dealloc}, + {Py_tp_doc, (void *)exc_snapshot_doc}, + {Py_tp_repr, (reprfunc)exc_snapshot_repr}, + {Py_tp_str, (reprfunc)exc_snapshot_str}, + {Py_tp_hash, exc_snapshot_hash}, + {Py_tp_members, exc_snapshot_members}, + {Py_tp_methods, exc_snapshot_methods}, + {0, NULL}, +}; + +static PyType_Spec ExcSnapshotType_spec = { + // XXX Move it to builtins? + .name = "_interpreters.ExceptionSnapshot", + .basicsize = sizeof(PyExceptionSnapshotObject), + .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_DISALLOW_INSTANTIATION | Py_TPFLAGS_IMMUTABLETYPE), + .slots = ExcSnapshotType_slots, +}; + +static int +_exc_snapshot_init_type(PyInterpreterState *interp) +{ + struct _Py_exc_state *state = get_exc_state(interp); + assert(state->ExceptionSnapshotType == NULL); + PyTypeObject *cls = (PyTypeObject *)PyType_FromMetaclass( + NULL, NULL, &ExcSnapshotType_spec, NULL); + if (cls == NULL) { + return -1; + } + state->ExceptionSnapshotType = cls; + return 0; +} + +static void +_exc_snapshot_clear_type(PyInterpreterState *interp) +{ + struct _Py_exc_state *state = get_exc_state(interp); + Py_CLEAR(state->ExceptionSnapshotType); +} + +PyTypeObject * +_PyExc_GetExceptionSnapshotType(PyInterpreterState *interp) +{ + struct _Py_exc_state *state = get_exc_state(interp); + assert(state->ExceptionSnapshotType != NULL); + return (PyTypeObject *)Py_NewRef(state->ExceptionSnapshotType); +} + +static PyExceptionSnapshotObject * +new_exc_snapshot(PyInterpreterState *interp) +{ + struct _Py_exc_state *state = get_exc_state(interp); + assert(state->ExceptionSnapshotType != NULL); + PyTypeObject *cls = state->ExceptionSnapshotType; + + PyExceptionSnapshotObject *self = \ + (PyExceptionSnapshotObject *)PyObject_New(PyExceptionSnapshotObject, cls); + if (self == NULL) { + PyErr_NoMemory(); + return NULL; + } + self->info = (_Py_excinfo){0}; + return self; +} + +PyObject * +PyExceptionSnapshot_FromInfo(_Py_excinfo *info) +{ + PyInterpreterState *interp = _PyInterpreterState_GET(); + PyExceptionSnapshotObject *self = new_exc_snapshot(interp); + if (self == NULL) { + return NULL; + } + if (_Py_excinfo_Copy(&self->info, info) < 0) { + Py_DECREF(self); + } + return (PyObject *)self; +} From 5e9b17e9d5abf7f9a8ed40cc104aba308057ffa2 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 31 Oct 2023 13:17:13 -0600 Subject: [PATCH 5/7] Add _PyXI_ResolveCapturedException(). --- Include/internal/pycore_crossinterp.h | 3 +++ Python/crossinterp.c | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h index 9600dfb9600e60..ebf31619293b29 100644 --- a/Include/internal/pycore_crossinterp.h +++ b/Include/internal/pycore_crossinterp.h @@ -258,6 +258,9 @@ PyAPI_FUNC(void) _PyXI_Exit(_PyXI_session *session); PyAPI_FUNC(void) _PyXI_ApplyCapturedException( _PyXI_session *session, PyObject *excwrapper); +PyAPI_FUNC(PyObject *) _PyXI_ResolveCapturedException( + _PyXI_session *session, + PyObject *excwrapper); PyAPI_FUNC(int) _PyXI_HasCapturedException(_PyXI_session *session); diff --git a/Python/crossinterp.c b/Python/crossinterp.c index a65355a49c5252..4919f6dbdf05a2 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -1584,6 +1584,26 @@ _PyXI_HasCapturedException(_PyXI_session *session) return session->exc != NULL; } +PyObject * +_PyXI_ResolveCapturedException(_PyXI_session *session, PyObject *excwrapper) +{ + assert(!PyErr_Occurred()); + assert(session->exc != NULL); + PyObject *snapshot = NULL; + if (session->exc->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) { + snapshot = PyExceptionSnapshot_FromInfo(&session->exc->uncaught); + if (snapshot == NULL) { + return NULL; + } + assert(!PyErr_Occurred()); + } + else { + _PyXI_ApplyCapturedException(session, excwrapper); + assert(PyErr_Occurred()); + } + return snapshot; +} + int _PyXI_Enter(_PyXI_session *session, PyInterpreterState *interp, PyObject *nsupdates) From ce2db5d7e6315a0835872dc3578b2c729cd34585 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 1 Nov 2023 16:45:39 -0600 Subject: [PATCH 6/7] Add _PyExc_FiniHeapObjects(), to call before _PyInterpreterState_Clear(). --- Include/internal/pycore_exceptions.h | 3 ++- Objects/exceptions.c | 25 ++++++++----------------- Python/pylifecycle.c | 5 +++-- 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/Include/internal/pycore_exceptions.h b/Include/internal/pycore_exceptions.h index b1c8e48e00ad62..7a2ac3436875df 100644 --- a/Include/internal/pycore_exceptions.h +++ b/Include/internal/pycore_exceptions.h @@ -16,6 +16,8 @@ extern "C" { extern PyStatus _PyExc_InitState(PyInterpreterState *); extern PyStatus _PyExc_InitGlobalObjects(PyInterpreterState *); extern int _PyExc_InitTypes(PyInterpreterState *); +extern void _PyExc_FiniHeapObjects(PyInterpreterState *); +extern void _PyExc_FiniTypes(PyInterpreterState *); extern void _PyExc_Fini(PyInterpreterState *); @@ -32,7 +34,6 @@ struct _Py_exc_state { PyTypeObject *ExceptionSnapshotType; }; -extern void _PyExc_ClearExceptionGroupType(PyInterpreterState *); /* other API */ diff --git a/Objects/exceptions.c b/Objects/exceptions.c index a6d396bfe64a48..13867bbb8b944e 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -3682,14 +3682,9 @@ _PyExc_InitTypes(PyInterpreterState *interp) return 0; } - -static void -_exc_snapshot_clear_type(PyInterpreterState *interp); - -static void +void _PyExc_FiniTypes(PyInterpreterState *interp) { - _exc_snapshot_clear_type(interp); for (Py_ssize_t i=Py_ARRAY_LENGTH(static_exceptions) - 1; i >= 0; i--) { PyTypeObject *exc = static_exceptions[i].exc; _PyStaticType_Dealloc(interp, exc); @@ -3806,11 +3801,16 @@ _PyBuiltins_AddExceptions(PyObject *bltinmod) return 0; } + +// _PyExc_FiniHeapObjects() must be called before the interpreter +// state is cleared, since there are heap types to clean up. + void -_PyExc_ClearExceptionGroupType(PyInterpreterState *interp) +_PyExc_FiniHeapObjects(PyInterpreterState *interp) { - struct _Py_exc_state *state = &interp->exc_state; + struct _Py_exc_state *state = get_exc_state(interp); Py_CLEAR(state->PyExc_ExceptionGroup); + Py_CLEAR(state->ExceptionSnapshotType); } void @@ -3819,8 +3819,6 @@ _PyExc_Fini(PyInterpreterState *interp) struct _Py_exc_state *state = &interp->exc_state; free_preallocated_memerrors(state); Py_CLEAR(state->errnomap); - - _PyExc_FiniTypes(interp); } int @@ -3972,13 +3970,6 @@ _exc_snapshot_init_type(PyInterpreterState *interp) return 0; } -static void -_exc_snapshot_clear_type(PyInterpreterState *interp) -{ - struct _Py_exc_state *state = get_exc_state(interp); - Py_CLEAR(state->ExceptionSnapshotType); -} - PyTypeObject * _PyExc_GetExceptionSnapshotType(PyInterpreterState *interp) { diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index ac8d5208322882..f5ebd7e5572533 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1742,7 +1742,7 @@ finalize_interp_types(PyInterpreterState *interp) { _PyUnicode_FiniTypes(interp); _PySys_FiniTypes(interp); - _PyExc_Fini(interp); + _PyExc_FiniTypes(interp); _PyAsyncGen_Fini(interp); _PyContext_Fini(interp); _PyFloat_FiniType(interp); @@ -1779,7 +1779,7 @@ finalize_interp_clear(PyThreadState *tstate) int is_main_interp = _Py_IsMainInterpreter(tstate->interp); _PyXI_Fini(tstate->interp); - _PyExc_ClearExceptionGroupType(tstate->interp); + _PyExc_FiniHeapObjects(tstate->interp); _Py_clear_generic_types(tstate->interp); /* Clear interpreter state and all thread states */ @@ -1799,6 +1799,7 @@ finalize_interp_clear(PyThreadState *tstate) _PyPerfTrampoline_Fini(); } + _PyExc_Fini(tstate->interp); finalize_interp_types(tstate->interp); } From b9f7974f1efa5d19028b05ef8fd93b7c412264d9 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 1 Nov 2023 16:50:32 -0600 Subject: [PATCH 7/7] Export fewer symbols. --- Include/internal/pycore_exceptions.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/internal/pycore_exceptions.h b/Include/internal/pycore_exceptions.h index 7a2ac3436875df..4c9b3cbeb57169 100644 --- a/Include/internal/pycore_exceptions.h +++ b/Include/internal/pycore_exceptions.h @@ -40,7 +40,7 @@ struct _Py_exc_state { PyAPI_FUNC(PyTypeObject *) _PyExc_GetExceptionSnapshotType( PyInterpreterState *interp); -PyAPI_FUNC(PyObject *) PyExceptionSnapshot_FromInfo(_Py_excinfo *info); +extern PyObject * PyExceptionSnapshot_FromInfo(_Py_excinfo *info); #ifdef __cplusplus 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