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