From 9e56eedd018e1a4681789e634016cbb7699dcb8a Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 22 Nov 2023 17:55:00 -0700 Subject: gh-76785: Return an "excinfo" Object From Interpreter.run() (gh-111573) --- Include/internal/pycore_crossinterp.h | 33 +-- Lib/test/support/interpreters.py | 18 +- Lib/test/test__xxinterpchannels.py | 12 +- Lib/test/test__xxsubinterpreters.py | 19 +- Lib/test/test_import/__init__.py | 10 +- Lib/test/test_importlib/test_util.py | 22 +- Lib/test/test_interpreters.py | 5 + Modules/_xxsubinterpretersmodule.c | 94 ++----- Python/crossinterp.c | 448 ++++++++++++++++++++++++---------- 9 files changed, 418 insertions(+), 243 deletions(-) diff --git a/Include/internal/pycore_crossinterp.h b/Include/internal/pycore_crossinterp.h index ee9ff00..ec9dac9 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,13 +198,13 @@ 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(PyObject *) _PyXI_ApplyError(_PyXI_error *err); -PyAPI_FUNC(void) _PyXI_ApplyExceptionInfo( - _PyXI_exception_info *info, - PyObject *exctype); typedef struct xi_session _PyXI_session; typedef struct _sharedns _PyXI_namespace; @@ -251,13 +256,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( @@ -266,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/Lib/test/support/interpreters.py b/Lib/test/support/interpreters.py index ab9342b..089fe7e 100644 --- a/Lib/test/support/interpreters.py +++ b/Lib/test/support/interpreters.py @@ -14,6 +14,7 @@ from _xxinterpchannels import ( __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 @@ __all__ = [ ] +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,7 +124,9 @@ class Interpreter: that time, the previous interpreter is allowed to run in other threads. """ - _interpreters.exec(self._id, src_str, channels) + 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__xxinterpchannels.py b/Lib/test/test__xxinterpchannels.py index 1c1ef3f..2b75e2f 100644 --- a/Lib/test/test__xxinterpchannels.py +++ b/Lib/test/test__xxinterpchannels.py @@ -1017,16 +1017,16 @@ class ChannelTests(TestBase): _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 d2056c9..64a9db9 100644 --- a/Lib/test/test__xxsubinterpreters.py +++ b/Lib/test/test__xxsubinterpreters.py @@ -940,7 +940,6 @@ class RunFailedTests(TestBase): 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""" @@ -956,11 +955,12 @@ class RunFailedTests(TestBase): 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 @@ -979,17 +979,18 @@ class RunFailedTests(TestBase): 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(excinfo.type.__name__, exctype_name) if msg is None: - self.assertEqual(str(exc).split(':')[0], + self.assertEqual(excinfo.formatted.split(':')[0], exctype_name) else: - self.assertEqual(str(exc), + 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_import/__init__.py b/Lib/test/test_import/__init__.py index aa465c7..1ecac4f 100644 --- a/Lib/test/test_import/__init__.py +++ b/Lib/test/test_import/__init__.py @@ -1968,10 +1968,12 @@ class SubinterpImportTests(unittest.TestCase): 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 5da72a2..9141765 100644 --- a/Lib/test/test_importlib/test_util.py +++ b/Lib/test/test_importlib/test_util.py @@ -655,25 +655,19 @@ class MagicNumberTests(unittest.TestCase): @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/Lib/test/test_interpreters.py b/Lib/test/test_interpreters.py index 7c030bc..5663706 100644 --- a/Lib/test/test_interpreters.py +++ b/Lib/test/test_interpreters.py @@ -478,6 +478,11 @@ class TestInterpreterRun(TestBase): 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 001fa88..02c2abe 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; } @@ -178,30 +152,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) { PyObject *result = NULL; @@ -229,7 +179,7 @@ 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}; @@ -237,7 +187,10 @@ _run_in_interpreter(PyInterpreterState *interp, // Prep and switch interpreters. if (_PyXI_Enter(&session, interp, shareables) < 0) { assert(!PyErr_Occurred()); - _PyXI_ApplyExceptionInfo(session.exc, excwrapper); + PyObject *excinfo = _PyXI_ApplyError(session.error); + if (excinfo != NULL) { + *p_excinfo = excinfo; + } assert(PyErr_Occurred()); return -1; } @@ -251,7 +204,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) { + *p_excinfo = excinfo; + } } else { assert(!_PyXI_HasCapturedException(&session)); @@ -521,7 +477,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); @@ -540,10 +497,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; @@ -577,10 +532,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; } @@ -620,10 +577,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; } @@ -654,10 +613,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; } @@ -759,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; diff --git a/Python/crossinterp.c b/Python/crossinterp.c index a908f9a..21b96ef 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -5,8 +5,10 @@ #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() #include "pycore_weakref.h" // _PyWeakref_GET_REF() @@ -564,6 +566,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 +599,8 @@ _bytes_shared(PyThreadState *tstate, PyObject *obj, return 0; } +// str + struct _shared_str_data { int kind; const void *buffer; @@ -626,6 +632,8 @@ _str_shared(PyThreadState *tstate, PyObject *obj, return 0; } +// int + static PyObject * _new_long_object(_PyCrossInterpreterData *data) { @@ -653,6 +661,8 @@ _long_shared(PyThreadState *tstate, PyObject *obj, return 0; } +// float + static PyObject * _new_float_object(_PyCrossInterpreterData *data) { @@ -676,6 +686,8 @@ _float_shared(PyThreadState *tstate, PyObject *obj, return 0; } +// None + static PyObject * _new_none_object(_PyCrossInterpreterData *data) { @@ -693,6 +705,8 @@ _none_shared(PyThreadState *tstate, PyObject *obj, return 0; } +// bool + static PyObject * _new_bool_object(_PyCrossInterpreterData *data) { @@ -713,6 +727,8 @@ _bool_shared(PyThreadState *tstate, PyObject *obj, return 0; } +// tuple + struct _shared_tuple_data { Py_ssize_t len; _PyCrossInterpreterData **data; @@ -806,6 +822,8 @@ error: return -1; } +// registration + static void _register_builtins_for_crossinterpreter_data(struct _xidregistry *xidregistry) { @@ -899,17 +917,6 @@ _xidregistry_fini(struct _xidregistry *registry) /*************************/ 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) { const char *str = PyUnicode_AsUTF8(strobj); @@ -944,115 +951,309 @@ _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) +{ + PyObject *formatted = _PyXI_excinfo_format(info); + PyErr_SetObject(exctype, formatted); + Py_DECREF(formatted); +} + +static PyObject * +_PyXI_excinfo_TypeAsObject(_PyXI_excinfo *info) { - if (info->type != NULL) { - if (info->msg != NULL) { - PyErr_Format(exctype, "%s: %s", info->type, info->msg); + 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; } - else { - PyErr_SetString(exctype, info->type); + int res = PyObject_SetAttrString(ns, "__name__", name); + Py_DECREF(name); + if (res < 0) { + goto error; } + empty = 0; } - else if (info->msg != NULL) { - PyErr_SetString(exctype, info->msg); + + 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; } - else { - PyErr_SetNone(exctype); + + 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; } @@ -1111,72 +1312,69 @@ _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) +PyObject * +_PyXI_ApplyError(_PyXI_error *error) { - 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); + return _PyXI_excinfo_AsObject(&error->uncaught); } - 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, PyExc_RuntimeError); PyObject *exc2 = PyErr_GetRaisedException(); PyException_SetContext(exc, exc2); PyErr_SetRaisedException(exc); } } assert(PyErr_Occurred()); + return NULL; } /* shared namespaces */ @@ -1603,7 +1801,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 +1835,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 +1906,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 +1945,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 +1966,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 +1983,24 @@ _capture_current_exception(_PyXI_session *session) // Finished! assert(!PyErr_Occurred()); - session->exc = exc; + session->error = err; } -void -_PyXI_ApplyCapturedException(_PyXI_session *session, PyObject *excwrapper) +PyObject * +_PyXI_ApplyCapturedException(_PyXI_session *session) { assert(!PyErr_Occurred()); - assert(session->exc != NULL); - _PyXI_ApplyExceptionInfo(session->exc, excwrapper); - assert(PyErr_Occurred()); - session->exc = NULL; + assert(session->error != NULL); + PyObject *res = _PyXI_ApplyError(session->error); + assert((res == NULL) != (PyErr_Occurred() == NULL)); + session->error = NULL; + return res; } int _PyXI_HasCapturedException(_PyXI_session *session) { - return session->exc != NULL; + return session->error != NULL; } int @@ -1814,7 +2012,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 +2062,7 @@ error: 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) { -- cgit v0.12