mirror of https://github.com/python/cpython
gh-76785: Return an "excinfo" Object From Interpreter.run() (gh-111573)
This commit is contained in:
parent
14e539f097
commit
9e56eedd01
|
@ -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);
|
||||
|
||||
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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="")')
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
@ -898,17 +916,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,117 +951,311 @@ _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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
/* 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;
|
||||
|
||||
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";
|
||||
PyTypeObject *type = Py_TYPE(exc);
|
||||
if (type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
|
||||
assert(_Py_IsImmortal((PyObject *)type));
|
||||
info->builtin = type;
|
||||
}
|
||||
else {
|
||||
// Only builtin types are preserved.
|
||||
info->builtin = NULL;
|
||||
}
|
||||
|
||||
// __name__
|
||||
strobj = PyType_GetName(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->name = _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";
|
||||
|
||||
// __qualname__
|
||||
strobj = PyType_GetQualName(type);
|
||||
if (strobj == NULL) {
|
||||
return -1;
|
||||
}
|
||||
*p_msg = msg;
|
||||
info->qualname = _copy_string_obj_raw(strobj);
|
||||
Py_DECREF(strobj);
|
||||
if (info->name == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// __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;
|
||||
}
|
||||
|
||||
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 const char *
|
||||
_Py_excinfo_InitFromException(_Py_excinfo *info, PyObject *exc)
|
||||
static PyObject *
|
||||
_PyXI_excinfo_format(_PyXI_excinfo *info)
|
||||
{
|
||||
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);
|
||||
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 {
|
||||
PyErr_SetString(exctype, info->type);
|
||||
if (info->msg != NULL) {
|
||||
return PyUnicode_FromFormat("%s: %s", qualname, info->msg);
|
||||
}
|
||||
else {
|
||||
return PyUnicode_FromString(qualname);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (info->msg != NULL) {
|
||||
PyErr_SetString(exctype, info->msg);
|
||||
return PyUnicode_FromString(info->msg);
|
||||
}
|
||||
else {
|
||||
PyErr_SetNone(exctype);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *
|
||||
_PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc)
|
||||
{
|
||||
assert(exc != NULL);
|
||||
|
||||
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.
|
||||
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;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
|
||||
error:
|
||||
assert(failure != NULL);
|
||||
_PyXI_excinfo_Clear(info);
|
||||
return failure;
|
||||
}
|
||||
|
||||
static void
|
||||
_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)
|
||||
{
|
||||
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 */
|
||||
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue