Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions Include/internal/pycore_crossinterp.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,7 @@ typedef struct _excinfo {
const char *module;
} type;
const char *msg;
const char *pickled;
Py_ssize_t pickled_len;
const char *errdisplay;
} _PyXI_excinfo;


Expand Down
2 changes: 1 addition & 1 deletion Lib/test/support/interpreters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def __init__(self, excinfo):

def __str__(self):
try:
formatted = ''.join(self.excinfo.tbexc.format()).rstrip()
formatted = self.excinfo.errdisplay
except Exception:
return super().__str__()
else:
Expand Down
239 changes: 88 additions & 151 deletions Python/crossinterp.c
Original file line number Diff line number Diff line change
Expand Up @@ -945,113 +945,105 @@ _xidregistry_fini(struct _xidregistry *registry)
/*************************/

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;
if (len <= 0) {
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);
Py_ssize_t size = -1;
const char *str = PyUnicode_AsUTF8AndSize(strobj, &size);
if (str == NULL) {
return NULL;
}

char *copied = PyMem_RawMalloc(strlen(str)+1);
char *copied = PyMem_RawMalloc(size+1);
if (copied == NULL) {
PyErr_NoMemory();
return NULL;
}
strcpy(copied, str);
if (p_size != NULL) {
*p_size = size;
}
return copied;
}


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 *picklemod = PyImport_ImportModule("_pickle");
if (picklemod == NULL) {
PyErr_Clear();
picklemod = PyImport_ImportModule("pickle");
if (picklemod == NULL) {
return -1;
}
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 *dumps = PyObject_GetAttrString(picklemod, "dumps");
Py_DECREF(picklemod);
if (dumps == NULL) {
PyObject *tbexc_type = PyObject_GetAttrString(tbmod, "TracebackException");
Py_DECREF(tbmod);
if (tbexc_type == NULL) {
return -1;
}
PyObject *pickledobj = PyObject_CallOneArg(dumps, obj);
Py_DECREF(dumps);
if (pickledobj == NULL) {
create = PyObject_GetAttrString(tbexc_type, "from_exception");
Py_DECREF(tbexc_type);
if (create == NULL) {
return -1;
}

char *pickled = NULL;
Py_ssize_t len = 0;
if (PyBytes_AsStringAndSize(pickledobj, &pickled, &len) < 0) {
Py_DECREF(pickledobj);
return -1;
args = PyTuple_Pack(1, exc);
if (args == NULL) {
goto error;
}
const char *copied = _copy_raw_string(pickled, len);
Py_DECREF(pickledobj);
if (copied == NULL) {
return -1;

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_pickled = copied;
*p_len = len;
*p_tbexc = tbexc;
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 *picklemod = PyImport_ImportModule("_pickle");
if (picklemod == NULL) {
PyErr_Clear();
picklemod = PyImport_ImportModule("pickle");
if (picklemod == NULL) {
return -1;
}
}
PyObject *loads = PyObject_GetAttrString(picklemod, "loads");
Py_DECREF(picklemod);
if (loads == NULL) {
return -1;
}
PyObject *pickledobj = PyBytes_FromStringAndSize(pickled, size);
if (pickledobj == NULL) {
Py_DECREF(loads);
return -1;
PyObject *lines = PyObject_CallMethod(tbexc, "format", NULL);
if (lines == NULL) {
return NULL;
}
PyObject *obj = PyObject_CallOneArg(loads, pickledobj);
Py_DECREF(loads);
Py_DECREF(pickledobj);
if (obj == NULL) {
return -1;
PyObject *formatted_obj = PyUnicode_Join(&_Py_STR(empty), lines);
Py_DECREF(lines);
if (formatted_obj == NULL) {
return NULL;
}
*p_obj = obj;
return 0;

Py_ssize_t size = -1;
const char *formatted = _copy_string_obj_raw(formatted_obj, &size);
Py_DECREF(formatted_obj);
// We remove trailing the newline added by TracebackException.format().
assert(formatted[size-1] == '\n');
((char *)formatted)[size-1] = '\0';
return formatted;
}


Expand Down Expand Up @@ -1101,7 +1093,7 @@ _excinfo_init_type(struct _excinfo_type *info, PyObject *exc)
if (strobj == NULL) {
return -1;
}
info->name = _copy_string_obj_raw(strobj);
info->name = _copy_string_obj_raw(strobj, NULL);
Py_DECREF(strobj);
if (info->name == NULL) {
return -1;
Expand All @@ -1112,7 +1104,7 @@ _excinfo_init_type(struct _excinfo_type *info, PyObject *exc)
if (strobj == NULL) {
return -1;
}
info->qualname = _copy_string_obj_raw(strobj);
info->qualname = _copy_string_obj_raw(strobj, NULL);
Py_DECREF(strobj);
if (info->name == NULL) {
return -1;
Expand All @@ -1123,7 +1115,7 @@ _excinfo_init_type(struct _excinfo_type *info, PyObject *exc)
if (strobj == NULL) {
return -1;
}
info->module = _copy_string_obj_raw(strobj);
info->module = _copy_string_obj_raw(strobj, NULL);
Py_DECREF(strobj);
if (info->name == NULL) {
return -1;
Expand Down Expand Up @@ -1188,8 +1180,8 @@ _PyXI_excinfo_Clear(_PyXI_excinfo *info)
if (info->msg != NULL) {
PyMem_RawFree((void *)info->msg);
}
if (info->pickled != NULL) {
PyMem_RawFree((void *)info->pickled);
if (info->errdisplay != NULL) {
PyMem_RawFree((void *)info->errdisplay);
}
*info = (_PyXI_excinfo){{NULL}};
}
Expand Down Expand Up @@ -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 *
_PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc)
{
Expand All @@ -1305,7 +1240,7 @@ _PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc)
failure = "error while formatting exception";
goto error;
}
info->msg = _copy_string_obj_raw(msgobj);
info->msg = _copy_string_obj_raw(msgobj, NULL);
Py_DECREF(msgobj);
if (info->msg == NULL) {
failure = "error while copying exception message";
Expand All @@ -1321,13 +1256,14 @@ _PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc)
PyErr_Clear();
}
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
PyErr_FormatUnraisable("Exception ignored while pickling TracebackException");
PyErr_FormatUnraisable("Exception ignored while formating TracebackException");
#endif
PyErr_Clear();
}
Py_DECREF(tbexc);
}

return NULL;
Expand All @@ -1342,8 +1278,9 @@ static void
_PyXI_excinfo_Apply(_PyXI_excinfo *info, PyObject *exctype)
{
PyObject *tbexc = NULL;
if (info->pickled != NULL) {
if (_unpickle_object(info->pickled, info->pickled_len, &tbexc) < 0) {
if (info->errdisplay != NULL) {
tbexc = PyUnicode_FromString(info->errdisplay);
if (tbexc == NULL) {
PyErr_Clear();
}
}
Expand All @@ -1354,9 +1291,9 @@ _PyXI_excinfo_Apply(_PyXI_excinfo *info, PyObject *exctype)

if (tbexc != NULL) {
PyObject *exc = PyErr_GetRaisedException();
if (PyObject_SetAttrString(exc, "_tbexc", tbexc) < 0) {
if (PyObject_SetAttrString(exc, "_errdisplay", tbexc) < 0) {
#ifdef Py_DEBUG
PyErr_FormatUnraisable("Exception ignored when setting _tbexc");
PyErr_FormatUnraisable("Exception ignored when setting _errdisplay");
#endif
PyErr_Clear();
}
Expand Down Expand Up @@ -1468,13 +1405,13 @@ _PyXI_excinfo_AsObject(_PyXI_excinfo *info)
goto error;
}

if (info->pickled != NULL) {
PyObject *tbexc = NULL;
if (_unpickle_object(info->pickled, info->pickled_len, &tbexc) < 0) {
if (info->errdisplay != NULL) {
PyObject *tbexc = PyUnicode_FromString(info->errdisplay);
if (tbexc == NULL) {
PyErr_Clear();
}
else {
res = PyObject_SetAttrString(ns, "tbexc", tbexc);
res = PyObject_SetAttrString(ns, "errdisplay", tbexc);
Py_DECREF(tbexc);
if (res < 0) {
goto error;
Expand Down Expand Up @@ -1646,7 +1583,7 @@ _sharednsitem_is_initialized(_PyXI_namespace_item *item)
static int
_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) {
assert(!_sharednsitem_is_initialized(item));
return -1;
Expand Down