Skip to content
26 changes: 26 additions & 0 deletions Lib/test/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2806,6 +2806,32 @@ def test_except_star_invalid_exception_type(self):
except (ValueError, 42):
pass

@cpython_only
@unittest.skipIf(_testcapi is None, "requires _testcapi")
def test_given_exception_matches_nested_tuple(self):
# Nested tuples are searched recursively.
self.assertTrue(
_testcapi.err_givenexceptionmatches(ValueError(), ((ValueError,),)))
self.assertFalse(
_testcapi.err_givenexceptionmatches(TypeError(), ((ValueError,),)))

@cpython_only
@unittest.skipIf(_testcapi is None, "requires _testcapi")
@support.skip_emscripten_stack_overflow()
@support.skip_wasi_stack_overflow()
@support.run_with_limited_c_stack(depth=500_000)
def test_given_exception_matches_deeply_nested_tuple(self):
# gh-156204: PyErr_GivenExceptionMatches() used to exhaust the C stack
# and crash the interpreter on deeply nested tuples of exception types.
tup = (ValueError,)
for _ in range(500_000):
tup = (tup,)

with support.catch_unraisable_exception() as cm:
self.assertFalse(
_testcapi.err_givenexceptionmatches(ValueError(), tup))
self.assertIsInstance(cm.unraisable.exc_value, RecursionError)


class PEP626Tests(unittest.TestCase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Fix a crash in :c:func:`PyErr_GivenExceptionMatches` when evaluating deeply
nested exception tuples. The recursion is now bounded, and exceeding the
limit is reported as an unraisable exception.
33 changes: 32 additions & 1 deletion Modules/_testcapi/clinic/exceptions.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions Modules/_testcapi/exceptions.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,26 @@ err_restore(PyObject *self, PyObject *args) {
return NULL;
}

/*[clinic input]
_testcapi.err_givenexceptionmatches
err: object
exc: object
/
Test PyErr_GivenExceptionMatches().
[clinic start generated code]*/

static PyObject *
_testcapi_err_givenexceptionmatches_impl(PyObject *module, PyObject *err,
PyObject *exc)
/*[clinic end generated code: output=e40994ab6dd75001 input=7b8ef542df07575b]*/
{
assert(!PyErr_Occurred());
int res = PyErr_GivenExceptionMatches(err, exc);
assert(!PyErr_Occurred());
return PyBool_FromLong(res);
}

/*[clinic input]
_testcapi.exception_print
exception as exc: object
Expand Down Expand Up @@ -552,6 +572,7 @@ static PyMethodDef test_methods[] = {
_TESTCAPI_MAKE_EXCEPTION_WITH_DOC_METHODDEF
_TESTCAPI_EXC_SET_OBJECT_METHODDEF
_TESTCAPI_EXC_SET_OBJECT_FETCH_METHODDEF
_TESTCAPI_ERR_GIVENEXCEPTIONMATCHES_METHODDEF
_TESTCAPI_ERR_SETSTRING_METHODDEF
_TESTCAPI_ERR_SETFROMERRNOWITHFILENAME_METHODDEF
_TESTCAPI_RAISE_EXCEPTION_METHODDEF
Expand Down
29 changes: 20 additions & 9 deletions Python/errors.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "Python.h"
#include "pycore_audit.h" // _PySys_Audit()
#include "pycore_call.h" // _PyObject_CallNoArgs()
#include "pycore_ceval.h" // _Py_ReachedRecursionLimitWithMargin()
#include "pycore_fileutils.h" // _PyFile_Flush
#include "pycore_initconfig.h" // _PyStatus_ERR()
#include "pycore_pyerrors.h" // _PyErr_Format()
Expand Down Expand Up @@ -337,17 +338,27 @@ PyErr_GivenExceptionMatches(PyObject *err, PyObject *exc)
return 0;
}
if (PyTuple_Check(exc)) {
Py_ssize_t i, n;
n = PyTuple_Size(exc);
for (i = 0; i < n; i++) {
PyThreadState *tstate = _PyThreadState_GET();
if (_Py_ReachedRecursionLimitWithMargin(tstate, 2)) {
Comment thread
ZeroIntensity marked this conversation as resolved.
PyObject *exc_value = _PyErr_GetRaisedException(tstate);
_PyErr_SetString(tstate, PyExc_RecursionError,
"maximum recursion depth exceeded while "
"checking exception tuple");
PyErr_FormatUnraisable("Exception ignored while "
"checking exception tuple");
_PyErr_SetRaisedException(tstate, exc_value);
return 0;
Comment thread
BHUVANSH855 marked this conversation as resolved.
Comment thread
ZeroIntensity marked this conversation as resolved.
}
int res = 0;
Py_ssize_t n = PyTuple_GET_SIZE(exc);
for (Py_ssize_t i = 0; i < n; i++) {
/* Test recursively */
if (PyErr_GivenExceptionMatches(
err, PyTuple_GET_ITEM(exc, i)))
{
return 1;
}
if (PyErr_GivenExceptionMatches(err, PyTuple_GET_ITEM(exc, i))) {
res = 1;
break;
}
}
return 0;
return res;
}
/* err might be an instance, so check its class. */
if (PyExceptionInstance_Check(err))
Expand Down
Loading