From d93f584855b7f983a21b5256a67629239b008b75 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 10 Nov 2023 16:05:42 -0700 Subject: [PATCH 1/4] Add more type info to _PyXI_excinfo. --- Include/internal/pycore_crossinterp.h | 29 ++- Modules/_xxsubinterpretersmodule.c | 2 +- Python/crossinterp.c | 334 ++++++++++++++++---------- 3 files changed, 230 insertions(+), 135 deletions(-) diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h index ee9ff0090c2484..c65b3379101ffc 100644 --- a/Include/internal/pycore_crossinterp.h +++ b/Include/internal/pycore_crossinterp.h @@ -170,9 +170,14 @@ extern void _PyXI_Fini(PyInterpreterState *interp); // of the exception in the calling interpreter. typedef struct _excinfo { - const char *type; + struct _excinfo_type { + PyTypeObject *builtin; + const char *name; + const char *qualname; + const char *module; + } type; const char *msg; -} _Py_excinfo; +} _PyXI_excinfo; typedef enum error_code { @@ -193,14 +198,16 @@ typedef struct _sharedexception { // The kind of error to propagate. _PyXI_errcode code; // The exception information to propagate, if applicable. - // This is populated only for _PyXI_ERR_UNCAUGHT_EXCEPTION. - _Py_excinfo uncaught; -} _PyXI_exception_info; + // This is populated only for some error codes, + // but always for _PyXI_ERR_UNCAUGHT_EXCEPTION. + _PyXI_excinfo uncaught; +} _PyXI_error; -PyAPI_FUNC(void) _PyXI_ApplyExceptionInfo( - _PyXI_exception_info *info, +PyAPI_FUNC(void) _PyXI_ApplyError( + _PyXI_error *err, PyObject *exctype); + typedef struct xi_session _PyXI_session; typedef struct _sharedns _PyXI_namespace; @@ -251,13 +258,13 @@ struct xi_session { // This is set if the interpreter is entered and raised an exception // that needs to be handled in some special way during exit. - _PyXI_errcode *exc_override; + _PyXI_errcode *error_override; // This is set if exit captured an exception to propagate. - _PyXI_exception_info *exc; + _PyXI_error *error; // -- pre-allocated memory -- - _PyXI_exception_info _exc; - _PyXI_errcode _exc_override; + _PyXI_error _error; + _PyXI_errcode _error_override; }; PyAPI_FUNC(int) _PyXI_Enter( diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 001fa887847cbd..b52fdae64f7839 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -237,7 +237,7 @@ _run_in_interpreter(PyInterpreterState *interp, // Prep and switch interpreters. if (_PyXI_Enter(&session, interp, shareables) < 0) { assert(!PyErr_Occurred()); - _PyXI_ApplyExceptionInfo(session.exc, excwrapper); + _PyXI_ApplyError(session.error, excwrapper); assert(PyErr_Occurred()); return -1; } diff --git a/Python/crossinterp.c b/Python/crossinterp.c index a908f9ae340ee9..b90c3ece474700 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -7,6 +7,7 @@ #include "pycore_initconfig.h" // _PyStatus_OK() #include "pycore_pyerrors.h" // _PyErr_Clear() #include "pycore_pystate.h" // _PyInterpreterState_GET() +#include "pycore_typeobject.h" // _PyType_GetModuleName() #include "pycore_weakref.h" // _PyWeakref_GET_REF() @@ -564,6 +565,8 @@ _lookup_getdata_from_registry(PyInterpreterState *interp, PyObject *obj) /* cross-interpreter data for builtin types */ +// bytes + struct _shared_bytes_data { char *bytes; Py_ssize_t len; @@ -595,6 +598,8 @@ _bytes_shared(PyThreadState *tstate, PyObject *obj, return 0; } +// str + struct _shared_str_data { int kind; const void *buffer; @@ -626,6 +631,8 @@ _str_shared(PyThreadState *tstate, PyObject *obj, return 0; } +// int + static PyObject * _new_long_object(_PyCrossInterpreterData *data) { @@ -653,6 +660,8 @@ _long_shared(PyThreadState *tstate, PyObject *obj, return 0; } +// float + static PyObject * _new_float_object(_PyCrossInterpreterData *data) { @@ -676,6 +685,8 @@ _float_shared(PyThreadState *tstate, PyObject *obj, return 0; } +// None + static PyObject * _new_none_object(_PyCrossInterpreterData *data) { @@ -693,6 +704,8 @@ _none_shared(PyThreadState *tstate, PyObject *obj, return 0; } +// bool + static PyObject * _new_bool_object(_PyCrossInterpreterData *data) { @@ -713,6 +726,8 @@ _bool_shared(PyThreadState *tstate, PyObject *obj, return 0; } +// tuple + struct _shared_tuple_data { Py_ssize_t len; _PyCrossInterpreterData **data; @@ -806,6 +821,8 @@ _tuple_shared(PyThreadState *tstate, PyObject *obj, return -1; } +// registration + static void _register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry) { @@ -898,17 +915,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) { @@ -944,115 +950,199 @@ _release_xid_data(_PyCrossInterpreterData *data, int rawfree) } +/***********************/ /* exception snapshots */ +/***********************/ static int -_exc_type_name_as_utf8(PyObject *exc, const char **p_typename) +_excinfo_init_type(struct _excinfo_type *info, PyObject *exc) { - // 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; + /* Note that this copies directly rather than into an intermediate + struct and does not clear on error. If we need that then we + should have a separate function to wrap this one + and do all that there. */ + PyObject *strobj = NULL; + + PyTypeObject *type = Py_TYPE(exc); + if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) { + assert(_Py_IsImmortal((PyObject *)type)); + info->builtin = type; } - const char *name = PyUnicode_AsUTF8(nameobj); - if (name == NULL) { - assert(PyErr_Occurred()); - Py_DECREF(nameobj); - *p_typename = "unable to encode exception type name"; + else { + // Only builtin types are preserved. + info->builtin = NULL; + } + + // __name__ + strobj = PyType_GetName(type); + if (strobj == NULL) { return -1; } - name = _copy_raw_string(name); - Py_DECREF(nameobj); - if (name == NULL) { - *p_typename = "out of memory copying exception type name"; + info->name = _copy_string_obj_raw(strobj); + Py_DECREF(strobj); + if (info->name == NULL) { 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"; + // __qualname__ + strobj = PyType_GetQualName(type); + if (strobj == NULL) { return -1; } - const char *msg = PyUnicode_AsUTF8(msgobj); - if (msg == NULL) { - assert(PyErr_Occurred()); - Py_DECREF(msgobj); - *p_msg = "unable to encode exception message"; + info->qualname = _copy_string_obj_raw(strobj); + Py_DECREF(strobj); + if (info->name == NULL) { 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"; + + // __module__ + strobj = _PyType_GetModuleName(type); + if (strobj == NULL) { + return -1; + } + info->module = _copy_string_obj_raw(strobj); + Py_DECREF(strobj); + if (info->name == NULL) { return -1; } - *p_msg = msg; + return 0; } static void -_Py_excinfo_Clear(_Py_excinfo *info) +_excinfo_clear_type(struct _excinfo_type *info) { - if (info->type != NULL) { - PyMem_RawFree((void *)info->type); + if (info->builtin != NULL) { + assert(info->builtin->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN); + assert(_Py_IsImmortal((PyObject *)info->builtin)); + } + if (info->name != NULL) { + PyMem_RawFree((void *)info->name); + } + if (info->qualname != NULL) { + PyMem_RawFree((void *)info->qualname); + } + if (info->module != NULL) { + PyMem_RawFree((void *)info->module); + } + *info = (struct _excinfo_type){NULL}; +} + +static void +_excinfo_normalize_type(struct _excinfo_type *info, + const char **p_module, const char **p_qualname) +{ + if (info->name == NULL) { + assert(info->builtin == NULL); + assert(info->qualname == NULL); + assert(info->module == NULL); + // This is inspired by TracebackException.format_exception_only(). + *p_module = NULL; + *p_qualname = NULL; + return; } + + const char *module = info->module; + const char *qualname = info->qualname; + if (qualname == NULL) { + qualname = info->name; + } + assert(module != NULL); + if (strcmp(module, "builtins") == 0) { + module = NULL; + } + else if (strcmp(module, "__main__") == 0) { + module = NULL; + } + *p_qualname = qualname; + *p_module = module; +} + +static void +_PyXI_excinfo_Clear(_PyXI_excinfo *info) +{ + _excinfo_clear_type(&info->type); if (info->msg != NULL) { PyMem_RawFree((void *)info->msg); } - *info = (_Py_excinfo){ NULL }; + *info = (_PyXI_excinfo){{NULL}}; +} + +static PyObject * +_PyXI_excinfo_format(_PyXI_excinfo *info) +{ + const char *module, *qualname; + _excinfo_normalize_type(&info->type, &module, &qualname); + if (qualname != NULL) { + if (module != NULL) { + if (info->msg != NULL) { + return PyUnicode_FromFormat("%s.%s: %s", + module, qualname, info->msg); + } + else { + return PyUnicode_FromFormat("%s.%s", module, qualname); + } + } + else { + if (info->msg != NULL) { + return PyUnicode_FromFormat("%s: %s", qualname, info->msg); + } + else { + return PyUnicode_FromString(qualname); + } + } + } + else if (info->msg != NULL) { + return PyUnicode_FromString(info->msg); + } + else { + Py_RETURN_NONE; + } } static const char * -_Py_excinfo_InitFromException(_Py_excinfo *info, PyObject *exc) +_PyXI_excinfo_InitFromException(_PyXI_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; + if (PyErr_GivenExceptionMatches(exc, PyExc_MemoryError)) { + _PyXI_excinfo_Clear(info); + return NULL; + } + const char *failure = NULL; + + if (_excinfo_init_type(&info->type, exc) < 0) { + failure = "error while initializing exception type snapshot"; + goto error; } // Extract the exception message. - const char *msg = NULL; - if (_exc_msg_as_utf8(exc, &msg) < 0) { - assert(msg != NULL); - return msg; + PyObject *msgobj = PyObject_Str(exc); + if (msgobj == NULL) { + failure = "error while formatting exception"; + goto error; + } + info->msg = _copy_string_obj_raw(msgobj); + Py_DECREF(msgobj); + if (info->msg == NULL) { + failure = "error while copying exception message"; + goto error; } - info->type = typename; - info->msg = msg; return NULL; + +error: + assert(failure != NULL); + _PyXI_excinfo_Clear(info); + return failure; } static void -_Py_excinfo_Apply(_Py_excinfo *info, PyObject *exctype) +_PyXI_excinfo_Apply(_PyXI_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); - } + PyObject *formatted = _PyXI_excinfo_format(info); + PyErr_SetObject(exctype, formatted); + Py_DECREF(formatted); } @@ -1111,66 +1201,65 @@ _PyXI_ApplyErrorCode(_PyXI_errcode code, PyInterpreterState *interp) /* shared exceptions */ static const char * -_PyXI_InitExceptionInfo(_PyXI_exception_info *info, - PyObject *excobj, _PyXI_errcode code) +_PyXI_InitError(_PyXI_error *error, PyObject *excobj, _PyXI_errcode code) { - if (info->interp == NULL) { - info->interp = PyInterpreterState_Get(); + if (error->interp == NULL) { + error->interp = PyInterpreterState_Get(); } const char *failure = NULL; if (code == _PyXI_ERR_UNCAUGHT_EXCEPTION) { // There is an unhandled exception we need to propagate. - failure = _Py_excinfo_InitFromException(&info->uncaught, excobj); + failure = _PyXI_excinfo_InitFromException(&error->uncaught, excobj); if (failure != NULL) { - // We failed to initialize info->uncaught. + // We failed to initialize error->uncaught. // XXX Print the excobj/traceback? Emit a warning? // XXX Print the current exception/traceback? if (PyErr_ExceptionMatches(PyExc_MemoryError)) { - info->code = _PyXI_ERR_NO_MEMORY; + error->code = _PyXI_ERR_NO_MEMORY; } else { - info->code = _PyXI_ERR_OTHER; + error->code = _PyXI_ERR_OTHER; } PyErr_Clear(); } else { - info->code = code; + error->code = code; } - assert(info->code != _PyXI_ERR_NO_ERROR); + assert(error->code != _PyXI_ERR_NO_ERROR); } else { // There is an error code we need to propagate. assert(excobj == NULL); assert(code != _PyXI_ERR_NO_ERROR); - info->code = code; - _Py_excinfo_Clear(&info->uncaught); + error->code = code; + _PyXI_excinfo_Clear(&error->uncaught); } return failure; } void -_PyXI_ApplyExceptionInfo(_PyXI_exception_info *info, PyObject *exctype) +_PyXI_ApplyError(_PyXI_error *error, PyObject *exctype) { if (exctype == NULL) { exctype = PyExc_RuntimeError; } - if (info->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) { + if (error->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) { // Raise an exception that proxies the propagated exception. - _Py_excinfo_Apply(&info->uncaught, exctype); + _PyXI_excinfo_Apply(&error->uncaught, exctype); } - else if (info->code == _PyXI_ERR_NOT_SHAREABLE) { + else if (error->code == _PyXI_ERR_NOT_SHAREABLE) { // Propagate the exception directly. - _set_xid_lookup_failure(info->interp, NULL, info->uncaught.msg); + _set_xid_lookup_failure(error->interp, NULL, error->uncaught.msg); } else { // Raise an exception corresponding to the code. - assert(info->code != _PyXI_ERR_NO_ERROR); - (void)_PyXI_ApplyErrorCode(info->code, info->interp); - if (info->uncaught.type != NULL || info->uncaught.msg != NULL) { + assert(error->code != _PyXI_ERR_NO_ERROR); + (void)_PyXI_ApplyErrorCode(error->code, error->interp); + if (error->uncaught.type.name != NULL || error->uncaught.msg != NULL) { // __context__ will be set to a proxy of the propagated exception. PyObject *exc = PyErr_GetRaisedException(); - _Py_excinfo_Apply(&info->uncaught, exctype); + _PyXI_excinfo_Apply(&error->uncaught, exctype); PyObject *exc2 = PyErr_GetRaisedException(); PyException_SetContext(exc, exc2); PyErr_SetRaisedException(exc); @@ -1603,7 +1692,7 @@ _PyXI_NamespaceFromDict(PyObject *nsobj, _PyXI_session *session) error: assert(PyErr_Occurred() - || (session != NULL && session->exc_override != NULL)); + || (session != NULL && session->error_override != NULL)); _sharedns_free(ns); return NULL; } @@ -1637,9 +1726,9 @@ _enter_session(_PyXI_session *session, PyInterpreterState *interp) assert(!session->running); assert(session->main_ns == NULL); // Set elsewhere and cleared in _capture_current_exception(). - assert(session->exc_override == NULL); + assert(session->error_override == NULL); // Set elsewhere and cleared in _PyXI_ApplyCapturedException(). - assert(session->exc == NULL); + assert(session->error == NULL); // Switch to interpreter. PyThreadState *tstate = PyThreadState_Get(); @@ -1708,23 +1797,23 @@ _propagate_not_shareable_error(_PyXI_session *session) PyInterpreterState *interp = _PyInterpreterState_GET(); if (PyErr_ExceptionMatches(_get_not_shareable_error_type(interp))) { // We want to propagate the exception directly. - session->_exc_override = _PyXI_ERR_NOT_SHAREABLE; - session->exc_override = &session->_exc_override; + session->_error_override = _PyXI_ERR_NOT_SHAREABLE; + session->error_override = &session->_error_override; } } static void _capture_current_exception(_PyXI_session *session) { - assert(session->exc == NULL); + assert(session->error == NULL); if (!PyErr_Occurred()) { - assert(session->exc_override == NULL); + assert(session->error_override == NULL); return; } // Handle the exception override. - _PyXI_errcode *override = session->exc_override; - session->exc_override = NULL; + _PyXI_errcode *override = session->error_override; + session->error_override = NULL; _PyXI_errcode errcode = override != NULL ? *override : _PyXI_ERR_UNCAUGHT_EXCEPTION; @@ -1747,19 +1836,18 @@ _capture_current_exception(_PyXI_session *session) } // Capture the exception. - _PyXI_exception_info *exc = &session->_exc; - *exc = (_PyXI_exception_info){ + _PyXI_error *err = &session->_error; + *err = (_PyXI_error){ .interp = session->init_tstate->interp, }; const char *failure; if (excval == NULL) { - failure = _PyXI_InitExceptionInfo(exc, NULL, errcode); + failure = _PyXI_InitError(err, NULL, errcode); } else { - failure = _PyXI_InitExceptionInfo(exc, excval, - _PyXI_ERR_UNCAUGHT_EXCEPTION); + failure = _PyXI_InitError(err, excval, _PyXI_ERR_UNCAUGHT_EXCEPTION); if (failure == NULL && override != NULL) { - exc->code = errcode; + err->code = errcode; } } @@ -1769,7 +1857,7 @@ _capture_current_exception(_PyXI_session *session) fprintf(stderr, "RunFailedError: script raised an uncaught exception (%s)", failure); - exc = NULL; + err = NULL; } // a temporary hack (famous last words) @@ -1786,23 +1874,23 @@ _capture_current_exception(_PyXI_session *session) // Finished! assert(!PyErr_Occurred()); - session->exc = exc; + session->error = err; } void _PyXI_ApplyCapturedException(_PyXI_session *session, PyObject *excwrapper) { assert(!PyErr_Occurred()); - assert(session->exc != NULL); - _PyXI_ApplyExceptionInfo(session->exc, excwrapper); + assert(session->error != NULL); + _PyXI_ApplyError(session->error, excwrapper); assert(PyErr_Occurred()); - session->exc = NULL; + session->error = NULL; } int _PyXI_HasCapturedException(_PyXI_session *session) { - return session->exc != NULL; + return session->error != NULL; } int @@ -1814,7 +1902,7 @@ _PyXI_Enter(_PyXI_session *session, if (nsupdates != NULL) { sharedns = _PyXI_NamespaceFromDict(nsupdates, NULL); if (sharedns == NULL && PyErr_Occurred()) { - assert(session->exc == NULL); + assert(session->error == NULL); return -1; } } @@ -1864,7 +1952,7 @@ _PyXI_Enter(_PyXI_session *session, assert(PyErr_Occurred()); // We want to propagate all exceptions here directly (best effort). assert(errcode != _PyXI_ERR_UNCAUGHT_EXCEPTION); - session->exc_override = &errcode; + session->error_override = &errcode; _capture_current_exception(session); _exit_session(session); if (sharedns != NULL) { From 362412de1bb8479efd01f5076b9f698d58edd715 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 22 Nov 2023 16:51:22 -0700 Subject: [PATCH 2/4] Return a excinfo object from _PyXI_ApplyError(). --- Include/internal/pycore_crossinterp.h | 8 +- Modules/_xxsubinterpretersmodule.c | 22 ++++- Python/crossinterp.c | 132 +++++++++++++++++++++++--- 3 files changed, 143 insertions(+), 19 deletions(-) diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h index c65b3379101ffc..ec9dac96292f35 100644 --- a/Include/internal/pycore_crossinterp.h +++ b/Include/internal/pycore_crossinterp.h @@ -203,9 +203,7 @@ typedef struct _sharedexception { _PyXI_excinfo uncaught; } _PyXI_error; -PyAPI_FUNC(void) _PyXI_ApplyError( - _PyXI_error *err, - PyObject *exctype); +PyAPI_FUNC(PyObject *) _PyXI_ApplyError(_PyXI_error *err); typedef struct xi_session _PyXI_session; @@ -273,9 +271,7 @@ PyAPI_FUNC(int) _PyXI_Enter( PyObject *nsupdates); PyAPI_FUNC(void) _PyXI_Exit(_PyXI_session *session); -PyAPI_FUNC(void) _PyXI_ApplyCapturedException( - _PyXI_session *session, - PyObject *excwrapper); +PyAPI_FUNC(PyObject *) _PyXI_ApplyCapturedException(_PyXI_session *session); PyAPI_FUNC(int) _PyXI_HasCapturedException(_PyXI_session *session); diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index b52fdae64f7839..1c04a96cdd9205 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -225,6 +225,18 @@ _run_script(PyObject *ns, const char *codestr, Py_ssize_t codestrlen, int flags) return 0; } +static void +_raise_formatted(PyObject *exctype, PyObject *excinfo) +{ + PyObject *formatted = PyObject_GetAttrString(excinfo, "formatted"); + if (formatted == NULL) { + assert(PyErr_Occurred()); + return; + } + PyErr_SetObject(exctype, formatted); + Py_DECREF(formatted); +} + static int _run_in_interpreter(PyInterpreterState *interp, const char *codestr, Py_ssize_t codestrlen, @@ -237,7 +249,10 @@ _run_in_interpreter(PyInterpreterState *interp, // Prep and switch interpreters. if (_PyXI_Enter(&session, interp, shareables) < 0) { assert(!PyErr_Occurred()); - _PyXI_ApplyError(session.error, excwrapper); + PyObject *excinfo = _PyXI_ApplyError(session.error); + if (excinfo != NULL) { + _raise_formatted(excwrapper, excinfo); + } assert(PyErr_Occurred()); return -1; } @@ -251,7 +266,10 @@ _run_in_interpreter(PyInterpreterState *interp, // Propagate any exception out to the caller. assert(!PyErr_Occurred()); if (res < 0) { - _PyXI_ApplyCapturedException(&session, excwrapper); + PyObject *excinfo = _PyXI_ApplyCapturedException(&session); + if (excinfo != NULL) { + _raise_formatted(excwrapper, excinfo); + } } else { assert(!_PyXI_HasCapturedException(&session)); diff --git a/Python/crossinterp.c b/Python/crossinterp.c index b90c3ece474700..21b96ef05ed799 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -5,6 +5,7 @@ #include "pycore_ceval.h" // _Py_simple_func #include "pycore_crossinterp.h" // struct _xid #include "pycore_initconfig.h" // _PyStatus_OK() +#include "pycore_namespace.h" //_PyNamespace_New() #include "pycore_pyerrors.h" // _PyErr_Clear() #include "pycore_pystate.h" // _PyInterpreterState_GET() #include "pycore_typeobject.h" // _PyType_GetModuleName() @@ -1145,6 +1146,116 @@ _PyXI_excinfo_Apply(_PyXI_excinfo *info, PyObject *exctype) Py_DECREF(formatted); } +static PyObject * +_PyXI_excinfo_TypeAsObject(_PyXI_excinfo *info) +{ + PyObject *ns = _PyNamespace_New(NULL); + if (ns == NULL) { + return NULL; + } + int empty = 1; + + if (info->type.name != NULL) { + PyObject *name = PyUnicode_FromString(info->type.name); + if (name == NULL) { + goto error; + } + int res = PyObject_SetAttrString(ns, "__name__", name); + Py_DECREF(name); + if (res < 0) { + goto error; + } + empty = 0; + } + + if (info->type.qualname != NULL) { + PyObject *qualname = PyUnicode_FromString(info->type.qualname); + if (qualname == NULL) { + goto error; + } + int res = PyObject_SetAttrString(ns, "__qualname__", qualname); + Py_DECREF(qualname); + if (res < 0) { + goto error; + } + empty = 0; + } + + if (info->type.module != NULL) { + PyObject *module = PyUnicode_FromString(info->type.module); + if (module == NULL) { + goto error; + } + int res = PyObject_SetAttrString(ns, "__module__", module); + Py_DECREF(module); + if (res < 0) { + goto error; + } + empty = 0; + } + + if (empty) { + Py_CLEAR(ns); + } + + return ns; + +error: + Py_DECREF(ns); + return NULL; +} + +static PyObject * +_PyXI_excinfo_AsObject(_PyXI_excinfo *info) +{ + PyObject *ns = _PyNamespace_New(NULL); + if (ns == NULL) { + return NULL; + } + int res; + + PyObject *type = _PyXI_excinfo_TypeAsObject(info); + if (type == NULL) { + if (PyErr_Occurred()) { + goto error; + } + type = Py_NewRef(Py_None); + } + res = PyObject_SetAttrString(ns, "type", type); + Py_DECREF(type); + if (res < 0) { + goto error; + } + + PyObject *msg = info->msg != NULL + ? PyUnicode_FromString(info->msg) + : Py_NewRef(Py_None); + if (msg == NULL) { + goto error; + } + res = PyObject_SetAttrString(ns, "msg", msg); + Py_DECREF(msg); + if (res < 0) { + goto error; + } + + PyObject *formatted = _PyXI_excinfo_format(info); + if (formatted == NULL) { + goto error; + } + res = PyObject_SetAttrString(ns, "formatted", formatted); + Py_DECREF(formatted); + if (res < 0) { + goto error; + } + + return ns; + +error: + Py_DECREF(ns); + return NULL; +} + /***************************/ /* short-term data sharing */ @@ -1238,15 +1349,12 @@ _PyXI_InitError(_PyXI_error *error, PyObject *excobj, _PyXI_errcode code) return failure; } -void -_PyXI_ApplyError(_PyXI_error *error, PyObject *exctype) +PyObject * +_PyXI_ApplyError(_PyXI_error *error) { - if (exctype == NULL) { - exctype = PyExc_RuntimeError; - } if (error->code == _PyXI_ERR_UNCAUGHT_EXCEPTION) { // Raise an exception that proxies the propagated exception. - _PyXI_excinfo_Apply(&error->uncaught, exctype); + return _PyXI_excinfo_AsObject(&error->uncaught); } else if (error->code == _PyXI_ERR_NOT_SHAREABLE) { // Propagate the exception directly. @@ -1259,13 +1367,14 @@ _PyXI_ApplyError(_PyXI_error *error, PyObject *exctype) if (error->uncaught.type.name != NULL || error->uncaught.msg != NULL) { // __context__ will be set to a proxy of the propagated exception. PyObject *exc = PyErr_GetRaisedException(); - _PyXI_excinfo_Apply(&error->uncaught, exctype); + _PyXI_excinfo_Apply(&error->uncaught, PyExc_RuntimeError); PyObject *exc2 = PyErr_GetRaisedException(); PyException_SetContext(exc, exc2); PyErr_SetRaisedException(exc); } } assert(PyErr_Occurred()); + return NULL; } /* shared namespaces */ @@ -1877,14 +1986,15 @@ _capture_current_exception(_PyXI_session *session) session->error = err; } -void -_PyXI_ApplyCapturedException(_PyXI_session *session, PyObject *excwrapper) +PyObject * +_PyXI_ApplyCapturedException(_PyXI_session *session) { assert(!PyErr_Occurred()); assert(session->error != NULL); - _PyXI_ApplyError(session->error, excwrapper); - assert(PyErr_Occurred()); + PyObject *res = _PyXI_ApplyError(session->error); + assert((res == NULL) != (PyErr_Occurred() == NULL)); session->error = NULL; + return res; } int From 61845dd695b44dbcc8db6e959996979f102cf779 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 22 Nov 2023 17:12:43 -0700 Subject: [PATCH 3/4] Return an excinfo object from _interpreters.exec(). --- Lib/test/support/interpreters.py | 6 +++- Lib/test/test__xxinterpchannels.py | 12 ++++---- Lib/test/test__xxsubinterpreters.py | 14 +++++---- Lib/test/test_import/__init__.py | 10 ++++--- Lib/test/test_importlib/test_util.py | 22 ++++++-------- Modules/_xxsubinterpretersmodule.c | 43 ++++++++++++---------------- 6 files changed, 51 insertions(+), 56 deletions(-) diff --git a/Lib/test/support/interpreters.py b/Lib/test/support/interpreters.py index ab9342b767dfae..5fd3a787d5b29d 100644 --- a/Lib/test/support/interpreters.py +++ b/Lib/test/support/interpreters.py @@ -110,7 +110,11 @@ def run(self, src_str, /, channels=None): that time, the previous interpreter is allowed to run in other threads. """ - _interpreters.exec(self._id, src_str, channels) + err = _interpreters.exec(self._id, src_str, channels) + if err is not None: + exc = RunFailedError(err.formatted) + exc.snapshot = err + raise exc def create_channel(): diff --git a/Lib/test/test__xxinterpchannels.py b/Lib/test/test__xxinterpchannels.py index 1c1ef3fac9d65f..2b75e2f1916c82 100644 --- a/Lib/test/test__xxinterpchannels.py +++ b/Lib/test/test__xxinterpchannels.py @@ -1017,16 +1017,16 @@ def test_close_multiple_users(self): _channels.recv({cid}) """)) channels.close(cid) - with self.assertRaises(interpreters.RunFailedError) as cm: - interpreters.run_string(id1, dedent(f""" + + excsnap = interpreters.run_string(id1, dedent(f""" _channels.send({cid}, b'spam') """)) - self.assertIn('ChannelClosedError', str(cm.exception)) - with self.assertRaises(interpreters.RunFailedError) as cm: - interpreters.run_string(id2, dedent(f""" + self.assertEqual(excsnap.type.__name__, 'ChannelClosedError') + + excsnap = interpreters.run_string(id2, dedent(f""" _channels.send({cid}, b'spam') """)) - self.assertIn('ChannelClosedError', str(cm.exception)) + self.assertEqual(excsnap.type.__name__, 'ChannelClosedError') def test_close_multiple_times(self): cid = channels.create() diff --git a/Lib/test/test__xxsubinterpreters.py b/Lib/test/test__xxsubinterpreters.py index d2056c9f52287b..ef2b65cb8d5638 100644 --- a/Lib/test/test__xxsubinterpreters.py +++ b/Lib/test/test__xxsubinterpreters.py @@ -956,11 +956,12 @@ class NeverError(Exception): pass raise NeverError # never raised """).format(dedent(text)) if fails: - with self.assertRaises(excwrapper) as caught: - interpreters.run_string(self.id, script) - return caught.exception + err = interpreters.run_string(self.id, script) + self.assertIsNot(err, None) + return err else: - interpreters.run_string(self.id, script) + err = interpreters.run_string(self.id, script) + self.assertIs(err, None) return None except: raise # re-raise @@ -982,11 +983,12 @@ def _assert_run_failed(self, exctype, msg, script): exc = self.run_script(script, fails=True) # Check the wrapper exception. + self.assertEqual(exc.type.__name__, exctype_name) if msg is None: - self.assertEqual(str(exc).split(':')[0], + self.assertEqual(exc.formatted.split(':')[0], exctype_name) else: - self.assertEqual(str(exc), + self.assertEqual(exc.formatted, '{}: {}'.format(exctype_name, msg)) return exc diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py index aa465c70dfbcd0..1ecac4f37fe1c1 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -1968,10 +1968,12 @@ def test_disallowed_reimport(self): print(_testsinglephase) ''') interpid = _interpreters.create() - with self.assertRaises(_interpreters.RunFailedError): - _interpreters.run_string(interpid, script) - with self.assertRaises(_interpreters.RunFailedError): - _interpreters.run_string(interpid, script) + + excsnap = _interpreters.run_string(interpid, script) + self.assertIsNot(excsnap, None) + + excsnap = _interpreters.run_string(interpid, script) + self.assertIsNot(excsnap, None) class TestSinglePhaseSnapshot(ModuleSnapshot): diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py index 5da72a21f586ee..914176559806f4 100644 --- a/Lib/test/test_importlib/test_util.py +++ b/Lib/test/test_importlib/test_util.py @@ -655,25 +655,19 @@ def test_magic_number(self): @unittest.skipIf(_interpreters is None, 'subinterpreters required') class IncompatibleExtensionModuleRestrictionsTests(unittest.TestCase): - ERROR = re.compile("^ImportError: module (.*) does not support loading in subinterpreters") - def run_with_own_gil(self, script): interpid = _interpreters.create(isolated=True) - try: - _interpreters.run_string(interpid, script) - except _interpreters.RunFailedError as exc: - if m := self.ERROR.match(str(exc)): - modname, = m.groups() - raise ImportError(modname) + excsnap = _interpreters.run_string(interpid, script) + if excsnap is not None: + if excsnap.type.__name__ == 'ImportError': + raise ImportError(excsnap.msg) def run_with_shared_gil(self, script): interpid = _interpreters.create(isolated=False) - try: - _interpreters.run_string(interpid, script) - except _interpreters.RunFailedError as exc: - if m := self.ERROR.match(str(exc)): - modname, = m.groups() - raise ImportError(modname) + excsnap = _interpreters.run_string(interpid, script) + if excsnap is not None: + if excsnap.type.__name__ == 'ImportError': + raise ImportError(excsnap.msg) @unittest.skipIf(_testsinglephase is None, "test requires _testsinglephase module") def test_single_phase_init_module(self): diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index 1c04a96cdd9205..eea3ae9f2a7acf 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -225,23 +225,11 @@ _run_script(PyObject *ns, const char *codestr, Py_ssize_t codestrlen, int flags) return 0; } -static void -_raise_formatted(PyObject *exctype, PyObject *excinfo) -{ - PyObject *formatted = PyObject_GetAttrString(excinfo, "formatted"); - if (formatted == NULL) { - assert(PyErr_Occurred()); - return; - } - PyErr_SetObject(exctype, formatted); - Py_DECREF(formatted); -} - static int _run_in_interpreter(PyInterpreterState *interp, const char *codestr, Py_ssize_t codestrlen, PyObject *shareables, int flags, - PyObject *excwrapper) + PyObject **p_excinfo) { assert(!PyErr_Occurred()); _PyXI_session session = {0}; @@ -251,7 +239,7 @@ _run_in_interpreter(PyInterpreterState *interp, assert(!PyErr_Occurred()); PyObject *excinfo = _PyXI_ApplyError(session.error); if (excinfo != NULL) { - _raise_formatted(excwrapper, excinfo); + *p_excinfo = excinfo; } assert(PyErr_Occurred()); return -1; @@ -268,7 +256,7 @@ _run_in_interpreter(PyInterpreterState *interp, if (res < 0) { PyObject *excinfo = _PyXI_ApplyCapturedException(&session); if (excinfo != NULL) { - _raise_formatted(excwrapper, excinfo); + *p_excinfo = excinfo; } } else { @@ -539,7 +527,8 @@ convert_code_arg(PyObject *arg, const char *fname, const char *displayname, static int _interp_exec(PyObject *self, - PyObject *id_arg, PyObject *code_arg, PyObject *shared_arg) + PyObject *id_arg, PyObject *code_arg, PyObject *shared_arg, + PyObject **p_excinfo) { // Look up the interpreter. PyInterpreterState *interp = PyInterpreterID_LookUp(id_arg); @@ -558,10 +547,8 @@ _interp_exec(PyObject *self, } // Run the code in the interpreter. - module_state *state = get_module_state(self); - assert(state != NULL); int res = _run_in_interpreter(interp, codestr, codestrlen, - shared_arg, flags, state->RunFailedError); + shared_arg, flags, p_excinfo); Py_XDECREF(bytes_obj); if (res < 0) { return -1; @@ -595,10 +582,12 @@ interp_exec(PyObject *self, PyObject *args, PyObject *kwds) return NULL; } - int res = _interp_exec(self, id, code, shared); + PyObject *excinfo = NULL; + int res = _interp_exec(self, id, code, shared, &excinfo); Py_DECREF(code); if (res < 0) { - return NULL; + assert((excinfo == NULL) != (PyErr_Occurred() == NULL)); + return excinfo; } Py_RETURN_NONE; } @@ -638,10 +627,12 @@ interp_run_string(PyObject *self, PyObject *args, PyObject *kwds) return NULL; } - int res = _interp_exec(self, id, script, shared); + PyObject *excinfo = NULL; + int res = _interp_exec(self, id, script, shared, &excinfo); Py_DECREF(script); if (res < 0) { - return NULL; + assert((excinfo == NULL) != (PyErr_Occurred() == NULL)); + return excinfo; } Py_RETURN_NONE; } @@ -672,10 +663,12 @@ interp_run_func(PyObject *self, PyObject *args, PyObject *kwds) return NULL; } - int res = _interp_exec(self, id, (PyObject *)code, shared); + PyObject *excinfo = NULL; + int res = _interp_exec(self, id, (PyObject *)code, shared, &excinfo); Py_DECREF(code); if (res < 0) { - return NULL; + assert((excinfo == NULL) != (PyErr_Occurred() == NULL)); + return excinfo; } Py_RETURN_NONE; } From dfff3e30ffdf25511d472385bf804bb15f71173e Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 22 Sep 2023 13:18:55 -0600 Subject: [PATCH 4/4] _interpreters.RunFailedError -> interpreters.RunFailedError --- Lib/test/support/interpreters.py | 22 ++++++++--- Lib/test/test__xxsubinterpreters.py | 11 +++--- Lib/test/test_interpreters.py | 5 +++ Modules/_xxsubinterpretersmodule.c | 57 +---------------------------- 4 files changed, 28 insertions(+), 67 deletions(-) diff --git a/Lib/test/support/interpreters.py b/Lib/test/support/interpreters.py index 5fd3a787d5b29d..089fe7ef56df78 100644 --- a/Lib/test/support/interpreters.py +++ b/Lib/test/support/interpreters.py @@ -14,6 +14,7 @@ __all__ = [ 'Interpreter', 'get_current', 'get_main', 'create', 'list_all', + 'RunFailedError', 'SendChannel', 'RecvChannel', 'create_channel', 'list_all_channels', 'is_shareable', 'ChannelError', 'ChannelNotFoundError', @@ -21,6 +22,19 @@ ] +class RunFailedError(RuntimeError): + + def __init__(self, excinfo): + msg = excinfo.formatted + if not msg: + if excinfo.type and snapshot.msg: + msg = f'{snapshot.type.__name__}: {snapshot.msg}' + else: + msg = snapshot.type.__name__ or snapshot.msg + super().__init__(msg) + self.snapshot = excinfo + + def create(*, isolated=True): """Return a new (idle) Python interpreter.""" id = _interpreters.create(isolated=isolated) @@ -110,11 +124,9 @@ def run(self, src_str, /, channels=None): that time, the previous interpreter is allowed to run in other threads. """ - err = _interpreters.exec(self._id, src_str, channels) - if err is not None: - exc = RunFailedError(err.formatted) - exc.snapshot = err - raise exc + excinfo = _interpreters.exec(self._id, src_str, channels) + if excinfo is not None: + raise RunFailedError(excinfo) def create_channel(): diff --git a/Lib/test/test__xxsubinterpreters.py b/Lib/test/test__xxsubinterpreters.py index ef2b65cb8d5638..64a9db95e5eaf5 100644 --- a/Lib/test/test__xxsubinterpreters.py +++ b/Lib/test/test__xxsubinterpreters.py @@ -940,7 +940,6 @@ def add_module(self, modname, text): return script_helper.make_script(tempdir, modname, text) def run_script(self, text, *, fails=False): - excwrapper = interpreters.RunFailedError r, w = os.pipe() try: script = dedent(f""" @@ -980,18 +979,18 @@ def _assert_run_failed(self, exctype, msg, script): exctype_name = exctype.__name__ # Run the script. - exc = self.run_script(script, fails=True) + excinfo = self.run_script(script, fails=True) # Check the wrapper exception. - self.assertEqual(exc.type.__name__, exctype_name) + self.assertEqual(excinfo.type.__name__, exctype_name) if msg is None: - self.assertEqual(exc.formatted.split(':')[0], + self.assertEqual(excinfo.formatted.split(':')[0], exctype_name) else: - self.assertEqual(exc.formatted, + self.assertEqual(excinfo.formatted, '{}: {}'.format(exctype_name, msg)) - return exc + return excinfo def assert_run_failed(self, exctype, script): self._assert_run_failed(exctype, None, script) diff --git a/Lib/test/test_interpreters.py b/Lib/test/test_interpreters.py index 7c030bcf0321cd..5663706c0ccfb7 100644 --- a/Lib/test/test_interpreters.py +++ b/Lib/test/test_interpreters.py @@ -478,6 +478,11 @@ def test_success(self): self.assertEqual(out, 'it worked!') + def test_failure(self): + interp = interpreters.create() + with self.assertRaises(interpreters.RunFailedError): + interp.run('raise Exception') + def test_in_thread(self): interp = interpreters.create() script, file = _captured_script('print("it worked!", end="")') diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c index eea3ae9f2a7acf..02c2abed27ddfa 100644 --- a/Modules/_xxsubinterpretersmodule.c +++ b/Modules/_xxsubinterpretersmodule.c @@ -28,31 +28,11 @@ _get_current_interp(void) return PyInterpreterState_Get(); } -static PyObject * -add_new_exception(PyObject *mod, const char *name, PyObject *base) -{ - assert(!PyObject_HasAttrStringWithError(mod, name)); - PyObject *exctype = PyErr_NewException(name, base, NULL); - if (exctype == NULL) { - return NULL; - } - int res = PyModule_AddType(mod, (PyTypeObject *)exctype); - if (res < 0) { - Py_DECREF(exctype); - return NULL; - } - return exctype; -} - -#define ADD_NEW_EXCEPTION(MOD, NAME, BASE) \ - add_new_exception(MOD, MODULE_NAME "." Py_STRINGIFY(NAME), BASE) - /* module state *************************************************************/ typedef struct { - /* exceptions */ - PyObject *RunFailedError; + int _notused; } module_state; static inline module_state * @@ -67,18 +47,12 @@ get_module_state(PyObject *mod) static int traverse_module_state(module_state *state, visitproc visit, void *arg) { - /* exceptions */ - Py_VISIT(state->RunFailedError); - return 0; } static int clear_module_state(module_state *state) { - /* exceptions */ - Py_CLEAR(state->RunFailedError); - return 0; } @@ -177,30 +151,6 @@ get_code_str(PyObject *arg, Py_ssize_t *len_p, PyObject **bytes_p, int *flags_p) /* interpreter-specific code ************************************************/ -static int -exceptions_init(PyObject *mod) -{ - module_state *state = get_module_state(mod); - if (state == NULL) { - return -1; - } - -#define ADD(NAME, BASE) \ - do { \ - assert(state->NAME == NULL); \ - state->NAME = ADD_NEW_EXCEPTION(mod, NAME, BASE); \ - if (state->NAME == NULL) { \ - return -1; \ - } \ - } while (0) - - // An uncaught exception came out of interp_run_string(). - ADD(RunFailedError, PyExc_RuntimeError); -#undef ADD - - return 0; -} - static int _run_script(PyObject *ns, const char *codestr, Py_ssize_t codestrlen, int flags) { @@ -770,11 +720,6 @@ The 'interpreters' module provides a more convenient interface."); static int module_exec(PyObject *mod) { - /* Add exception types */ - if (exceptions_init(mod) != 0) { - goto error; - } - // PyInterpreterID if (PyModule_AddType(mod, &PyInterpreterID_Type) < 0) { goto error; 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