gh-76785: Avoid Pickled TracebackException for Propagated Subinterpreter Exceptions (gh-113036)

We need the TracebackException of uncaught exceptions for a single purpose: the error display.  Thus we only need to pass the formatted error display between interpreters.  Passing a pickled TracebackException is overkill.
This commit is contained in:
Eric Snow 2023-12-12 17:31:30 -07:00 committed by GitHub
parent 7e2d93f30b
commit c6e614fd81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 92 additions and 156 deletions

View File

@ -188,8 +188,7 @@ typedef struct _excinfo {
const char *module; const char *module;
} type; } type;
const char *msg; const char *msg;
const char *pickled; const char *errdisplay;
Py_ssize_t pickled_len;
} _PyXI_excinfo; } _PyXI_excinfo;

View File

@ -56,7 +56,7 @@ class ExecFailure(RuntimeError):
def __str__(self): def __str__(self):
try: try:
formatted = ''.join(self.excinfo.tbexc.format()).rstrip() formatted = self.excinfo.errdisplay
except Exception: except Exception:
return super().__str__() return super().__str__()
else: else:

View File

@ -945,113 +945,105 @@ _xidregistry_fini(struct _xidregistry *registry)
/*************************/ /*************************/
static const char * static const char *
_copy_raw_string(const char *str, Py_ssize_t len) _copy_string_obj_raw(PyObject *strobj, Py_ssize_t *p_size)
{ {
size_t size = len + 1; Py_ssize_t size = -1;
if (len <= 0) { const char *str = PyUnicode_AsUTF8AndSize(strobj, &size);
size = strlen(str) + 1;
}
char *copied = PyMem_RawMalloc(size);
if (copied == NULL) {
return NULL;
}
if (len <= 0) {
strcpy(copied, str);
}
else {
memcpy(copied, str, size);
}
return copied;
}
static const char *
_copy_string_obj_raw(PyObject *strobj)
{
const char *str = PyUnicode_AsUTF8(strobj);
if (str == NULL) { if (str == NULL) {
return NULL; return NULL;
} }
char *copied = PyMem_RawMalloc(strlen(str)+1); char *copied = PyMem_RawMalloc(size+1);
if (copied == NULL) { if (copied == NULL) {
PyErr_NoMemory(); PyErr_NoMemory();
return NULL; return NULL;
} }
strcpy(copied, str); strcpy(copied, str);
if (p_size != NULL) {
*p_size = size;
}
return copied; return copied;
} }
static int static int
_pickle_object(PyObject *obj, const char **p_pickled, Py_ssize_t *p_len) _convert_exc_to_TracebackException(PyObject *exc, PyObject **p_tbexc)
{ {
assert(!PyErr_Occurred()); PyObject *args = NULL;
PyObject *picklemod = PyImport_ImportModule("_pickle"); PyObject *kwargs = NULL;
if (picklemod == NULL) { PyObject *create = NULL;
PyErr_Clear();
picklemod = PyImport_ImportModule("pickle"); // This is inspired by _PyErr_Display().
if (picklemod == NULL) { PyObject *tbmod = PyImport_ImportModule("traceback");
return -1; if (tbmod == NULL) {
}
}
PyObject *dumps = PyObject_GetAttrString(picklemod, "dumps");
Py_DECREF(picklemod);
if (dumps == NULL) {
return -1; return -1;
} }
PyObject *pickledobj = PyObject_CallOneArg(dumps, obj); PyObject *tbexc_type = PyObject_GetAttrString(tbmod, "TracebackException");
Py_DECREF(dumps); Py_DECREF(tbmod);
if (pickledobj == NULL) { if (tbexc_type == NULL) {
return -1;
}
create = PyObject_GetAttrString(tbexc_type, "from_exception");
Py_DECREF(tbexc_type);
if (create == NULL) {
return -1; return -1;
} }
char *pickled = NULL; args = PyTuple_Pack(1, exc);
Py_ssize_t len = 0; if (args == NULL) {
if (PyBytes_AsStringAndSize(pickledobj, &pickled, &len) < 0) { goto error;
Py_DECREF(pickledobj);
return -1;
}
const char *copied = _copy_raw_string(pickled, len);
Py_DECREF(pickledobj);
if (copied == NULL) {
return -1;
} }
*p_pickled = copied; kwargs = PyDict_New();
*p_len = len; if (kwargs == NULL) {
goto error;
}
if (PyDict_SetItemString(kwargs, "save_exc_type", Py_False) < 0) {
goto error;
}
if (PyDict_SetItemString(kwargs, "lookup_lines", Py_False) < 0) {
goto error;
}
PyObject *tbexc = PyObject_Call(create, args, kwargs);
Py_DECREF(args);
Py_DECREF(kwargs);
Py_DECREF(create);
if (tbexc == NULL) {
goto error;
}
*p_tbexc = tbexc;
return 0; return 0;
error:
Py_XDECREF(args);
Py_XDECREF(kwargs);
Py_XDECREF(create);
return -1;
} }
static int
_unpickle_object(const char *pickled, Py_ssize_t size, PyObject **p_obj) static const char *
_format_TracebackException(PyObject *tbexc)
{ {
assert(!PyErr_Occurred()); PyObject *lines = PyObject_CallMethod(tbexc, "format", NULL);
PyObject *picklemod = PyImport_ImportModule("_pickle"); if (lines == NULL) {
if (picklemod == NULL) { return NULL;
PyErr_Clear();
picklemod = PyImport_ImportModule("pickle");
if (picklemod == NULL) {
return -1;
}
} }
PyObject *loads = PyObject_GetAttrString(picklemod, "loads"); PyObject *formatted_obj = PyUnicode_Join(&_Py_STR(empty), lines);
Py_DECREF(picklemod); Py_DECREF(lines);
if (loads == NULL) { if (formatted_obj == NULL) {
return -1; return NULL;
} }
PyObject *pickledobj = PyBytes_FromStringAndSize(pickled, size);
if (pickledobj == NULL) { Py_ssize_t size = -1;
Py_DECREF(loads); const char *formatted = _copy_string_obj_raw(formatted_obj, &size);
return -1; Py_DECREF(formatted_obj);
} // We remove trailing the newline added by TracebackException.format().
PyObject *obj = PyObject_CallOneArg(loads, pickledobj); assert(formatted[size-1] == '\n');
Py_DECREF(loads); ((char *)formatted)[size-1] = '\0';
Py_DECREF(pickledobj); return formatted;
if (obj == NULL) {
return -1;
}
*p_obj = obj;
return 0;
} }
@ -1101,7 +1093,7 @@ _excinfo_init_type(struct _excinfo_type *info, PyObject *exc)
if (strobj == NULL) { if (strobj == NULL) {
return -1; return -1;
} }
info->name = _copy_string_obj_raw(strobj); info->name = _copy_string_obj_raw(strobj, NULL);
Py_DECREF(strobj); Py_DECREF(strobj);
if (info->name == NULL) { if (info->name == NULL) {
return -1; return -1;
@ -1112,7 +1104,7 @@ _excinfo_init_type(struct _excinfo_type *info, PyObject *exc)
if (strobj == NULL) { if (strobj == NULL) {
return -1; return -1;
} }
info->qualname = _copy_string_obj_raw(strobj); info->qualname = _copy_string_obj_raw(strobj, NULL);
Py_DECREF(strobj); Py_DECREF(strobj);
if (info->name == NULL) { if (info->name == NULL) {
return -1; return -1;
@ -1123,7 +1115,7 @@ _excinfo_init_type(struct _excinfo_type *info, PyObject *exc)
if (strobj == NULL) { if (strobj == NULL) {
return -1; return -1;
} }
info->module = _copy_string_obj_raw(strobj); info->module = _copy_string_obj_raw(strobj, NULL);
Py_DECREF(strobj); Py_DECREF(strobj);
if (info->name == NULL) { if (info->name == NULL) {
return -1; return -1;
@ -1188,8 +1180,8 @@ _PyXI_excinfo_Clear(_PyXI_excinfo *info)
if (info->msg != NULL) { if (info->msg != NULL) {
PyMem_RawFree((void *)info->msg); PyMem_RawFree((void *)info->msg);
} }
if (info->pickled != NULL) { if (info->errdisplay != NULL) {
PyMem_RawFree((void *)info->pickled); PyMem_RawFree((void *)info->errdisplay);
} }
*info = (_PyXI_excinfo){{NULL}}; *info = (_PyXI_excinfo){{NULL}};
} }
@ -1226,63 +1218,6 @@ _PyXI_excinfo_format(_PyXI_excinfo *info)
} }
} }
static int
_convert_exc_to_TracebackException(PyObject *exc, PyObject **p_tbexc)
{
PyObject *args = NULL;
PyObject *kwargs = NULL;
PyObject *create = NULL;
// This is inspired by _PyErr_Display().
PyObject *tbmod = PyImport_ImportModule("traceback");
if (tbmod == NULL) {
return -1;
}
PyObject *tbexc_type = PyObject_GetAttrString(tbmod, "TracebackException");
Py_DECREF(tbmod);
if (tbexc_type == NULL) {
return -1;
}
create = PyObject_GetAttrString(tbexc_type, "from_exception");
Py_DECREF(tbexc_type);
if (create == NULL) {
return -1;
}
args = PyTuple_Pack(1, exc);
if (args == NULL) {
goto error;
}
kwargs = PyDict_New();
if (kwargs == NULL) {
goto error;
}
if (PyDict_SetItemString(kwargs, "save_exc_type", Py_False) < 0) {
goto error;
}
if (PyDict_SetItemString(kwargs, "lookup_lines", Py_False) < 0) {
goto error;
}
PyObject *tbexc = PyObject_Call(create, args, kwargs);
Py_DECREF(args);
Py_DECREF(kwargs);
Py_DECREF(create);
if (tbexc == NULL) {
goto error;
}
*p_tbexc = tbexc;
return 0;
error:
Py_XDECREF(args);
Py_XDECREF(kwargs);
Py_XDECREF(create);
return -1;
}
static const char * static const char *
_PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc) _PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc)
{ {
@ -1305,7 +1240,7 @@ _PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc)
failure = "error while formatting exception"; failure = "error while formatting exception";
goto error; goto error;
} }
info->msg = _copy_string_obj_raw(msgobj); info->msg = _copy_string_obj_raw(msgobj, NULL);
Py_DECREF(msgobj); Py_DECREF(msgobj);
if (info->msg == NULL) { if (info->msg == NULL) {
failure = "error while copying exception message"; failure = "error while copying exception message";
@ -1321,13 +1256,14 @@ _PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc)
PyErr_Clear(); PyErr_Clear();
} }
else { else {
if (_pickle_object(tbexc, &info->pickled, &info->pickled_len) < 0) { info->errdisplay = _format_TracebackException(tbexc);
Py_DECREF(tbexc);
if (info->errdisplay == NULL) {
#ifdef Py_DEBUG #ifdef Py_DEBUG
PyErr_FormatUnraisable("Exception ignored while pickling TracebackException"); PyErr_FormatUnraisable("Exception ignored while formating TracebackException");
#endif #endif
PyErr_Clear(); PyErr_Clear();
} }
Py_DECREF(tbexc);
} }
return NULL; return NULL;
@ -1342,8 +1278,9 @@ static void
_PyXI_excinfo_Apply(_PyXI_excinfo *info, PyObject *exctype) _PyXI_excinfo_Apply(_PyXI_excinfo *info, PyObject *exctype)
{ {
PyObject *tbexc = NULL; PyObject *tbexc = NULL;
if (info->pickled != NULL) { if (info->errdisplay != NULL) {
if (_unpickle_object(info->pickled, info->pickled_len, &tbexc) < 0) { tbexc = PyUnicode_FromString(info->errdisplay);
if (tbexc == NULL) {
PyErr_Clear(); PyErr_Clear();
} }
} }
@ -1354,9 +1291,9 @@ _PyXI_excinfo_Apply(_PyXI_excinfo *info, PyObject *exctype)
if (tbexc != NULL) { if (tbexc != NULL) {
PyObject *exc = PyErr_GetRaisedException(); PyObject *exc = PyErr_GetRaisedException();
if (PyObject_SetAttrString(exc, "_tbexc", tbexc) < 0) { if (PyObject_SetAttrString(exc, "_errdisplay", tbexc) < 0) {
#ifdef Py_DEBUG #ifdef Py_DEBUG
PyErr_FormatUnraisable("Exception ignored when setting _tbexc"); PyErr_FormatUnraisable("Exception ignored when setting _errdisplay");
#endif #endif
PyErr_Clear(); PyErr_Clear();
} }
@ -1468,13 +1405,13 @@ _PyXI_excinfo_AsObject(_PyXI_excinfo *info)
goto error; goto error;
} }
if (info->pickled != NULL) { if (info->errdisplay != NULL) {
PyObject *tbexc = NULL; PyObject *tbexc = PyUnicode_FromString(info->errdisplay);
if (_unpickle_object(info->pickled, info->pickled_len, &tbexc) < 0) { if (tbexc == NULL) {
PyErr_Clear(); PyErr_Clear();
} }
else { else {
res = PyObject_SetAttrString(ns, "tbexc", tbexc); res = PyObject_SetAttrString(ns, "errdisplay", tbexc);
Py_DECREF(tbexc); Py_DECREF(tbexc);
if (res < 0) { if (res < 0) {
goto error; goto error;
@ -1646,7 +1583,7 @@ _sharednsitem_is_initialized(_PyXI_namespace_item *item)
static int static int
_sharednsitem_init(_PyXI_namespace_item *item, PyObject *key) _sharednsitem_init(_PyXI_namespace_item *item, PyObject *key)
{ {
item->name = _copy_string_obj_raw(key); item->name = _copy_string_obj_raw(key, NULL);
if (item->name == NULL) { if (item->name == NULL) {
assert(!_sharednsitem_is_initialized(item)); assert(!_sharednsitem_is_initialized(item));
return -1; return -1;