diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index a9e296891bb8705..1ceb5916bd5f9f5 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -947,6 +947,16 @@ def test_failure(self): with self.assertRaises(ExecutionFailed): interp.exec('raise Exception') + def test_failure_with_broken_exception_str(self): + interp = interpreters.create() + with self.assertRaises(interpreters.InterpreterError): + interp.exec(dedent(""" + class BrokenError(Exception): + def __str__(self): + raise ValueError + raise BrokenError + """)) + @force_not_colorized def test_display_preserved_exception(self): tempdir = self.temp_dir() diff --git a/Misc/NEWS.d/next/Library/2026-11-14-21-04-56.gh-issue-161996.35uL2d.rst b/Misc/NEWS.d/next/Library/2026-11-14-21-04-56.gh-issue-161996.35uL2d.rst new file mode 100644 index 000000000000000..561bb62d6537073 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-11-14-21-04-56.gh-issue-161996.35uL2d.rst @@ -0,0 +1,2 @@ +Fix a crash in :meth:`concurrent.interpreters.Interpreter.exec` when the +uncaught exception's ``__str__`` raises. diff --git a/Python/crossinterp.c b/Python/crossinterp.c index d189d7bcefe313e..a91b45ba2cbcf45 100644 --- a/Python/crossinterp.c +++ b/Python/crossinterp.c @@ -2018,6 +2018,14 @@ _PyXI_ApplyError(_PyXI_error *error, const char *failure) PyThreadState *tstate = PyThreadState_Get(); if (failure != NULL) { + if (error->override != NULL + && error->override->code == _PyXI_ERR_NO_MEMORY) + { + PyErr_NoMemory(); + } + else { + PyErr_SetString(PyExc_InterpreterError, failure); + } xi_error_clear(error); return NULL; }