gh-76785: Return an "excinfo" Object From Interpreter.run() (gh-111573)

This commit is contained in:
Eric Snow 2023-11-22 17:55:00 -07:00 committed by GitHub
parent 14e539f097
commit 9e56eedd01
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 433 additions and 258 deletions

View file

@ -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) {