| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2806,6 +2806,32 @@ def test_except_star_invalid_exception_type(self): | |||
| 2806 | 2806 | except (ValueError, 42): | |
| 2807 | 2807 | pass | |
| 2808 | 2808 | ||
| 2809 | + @cpython_only | ||
| 2810 | + @unittest.skipIf(_testcapi is None, "requires _testcapi") | ||
| 2811 | + def test_given_exception_matches_nested_tuple(self): | ||
| 2812 | + # Nested tuples are searched recursively. | ||
| 2813 | + self.assertTrue( | ||
| 2814 | + _testcapi.err_givenexceptionmatches(ValueError(), ((ValueError,),))) | ||
| 2815 | + self.assertFalse( | ||
| 2816 | + _testcapi.err_givenexceptionmatches(TypeError(), ((ValueError,),))) | ||
| 2817 | + | ||
| 2818 | + @cpython_only | ||
| 2819 | + @unittest.skipIf(_testcapi is None, "requires _testcapi") | ||
| 2820 | + @support.skip_emscripten_stack_overflow() | ||
| 2821 | + @support.skip_wasi_stack_overflow() | ||
| 2822 | + @support.run_with_limited_c_stack(depth=500_000) | ||
| 2823 | + def test_given_exception_matches_deeply_nested_tuple(self): | ||
| 2824 | + # gh-156204: PyErr_GivenExceptionMatches() used to exhaust the C stack | ||
| 2825 | + # and crash the interpreter on deeply nested tuples of exception types. | ||
| 2826 | + tup = (ValueError,) | ||
| 2827 | + for _ in range(500_000): | ||
| 2828 | + tup = (tup,) | ||
| 2829 | + | ||
| 2830 | + with support.catch_unraisable_exception() as cm: | ||
| 2831 | + self.assertFalse( | ||
| 2832 | + _testcapi.err_givenexceptionmatches(ValueError(), tup)) | ||
| 2833 | + self.assertIsInstance(cm.unraisable.exc_value, RecursionError) | ||
| 2834 | + | ||
| 2809 | 2835 | ||
| 2810 | 2836 | class PEP626Tests(unittest.TestCase): | |
| 2811 | 2837 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,3 @@ | |||
| 1 | + Fix a crash in :c:func:`PyErr_GivenExceptionMatches` when evaluating deeply | ||
| 2 | + nested exception tuples. The recursion is now bounded, and exceeding the | ||
| 3 | + limit is reported as an unraisable exception. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -54,6 +54,26 @@ err_restore(PyObject *self, PyObject *args) { | |||
| 54 | 54 | return NULL; | |
| 55 | 55 | } | |
| 56 | 56 | ||
| 57 | + /*[clinic input] | ||
| 58 | + _testcapi.err_givenexceptionmatches | ||
| 59 | + err: object | ||
| 60 | + exc: object | ||
| 61 | + / | ||
| 62 | + | ||
| 63 | + Test PyErr_GivenExceptionMatches(). | ||
| 64 | + [clinic start generated code]*/ | ||
| 65 | + | ||
| 66 | + static PyObject * | ||
| 67 | + _testcapi_err_givenexceptionmatches_impl(PyObject *module, PyObject *err, | ||
| 68 | + PyObject *exc) | ||
| 69 | + /*[clinic end generated code: output=e40994ab6dd75001 input=7b8ef542df07575b]*/ | ||
| 70 | + { | ||
| 71 | + assert(!PyErr_Occurred()); | ||
| 72 | + int res = PyErr_GivenExceptionMatches(err, exc); | ||
| 73 | + assert(!PyErr_Occurred()); | ||
| 74 | + return PyBool_FromLong(res); | ||
| 75 | + } | ||
| 76 | + | ||
| 57 | 77 | /*[clinic input] | |
| 58 | 78 | _testcapi.exception_print | |
| 59 | 79 | exception as exc: object | |
@@ -552,6 +572,7 @@ static PyMethodDef test_methods[] = { | |||
| 552 | 572 | _TESTCAPI_MAKE_EXCEPTION_WITH_DOC_METHODDEF | |
| 553 | 573 | _TESTCAPI_EXC_SET_OBJECT_METHODDEF | |
| 554 | 574 | _TESTCAPI_EXC_SET_OBJECT_FETCH_METHODDEF | |
| 575 | + _TESTCAPI_ERR_GIVENEXCEPTIONMATCHES_METHODDEF | ||
| 555 | 576 | _TESTCAPI_ERR_SETSTRING_METHODDEF | |
| 556 | 577 | _TESTCAPI_ERR_SETFROMERRNOWITHFILENAME_METHODDEF | |
| 557 | 578 | _TESTCAPI_RAISE_EXCEPTION_METHODDEF | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,6 +4,7 @@ | |||
| 4 | 4 | #include "Python.h" | |
| 5 | 5 | #include "pycore_audit.h" // _PySys_Audit() | |
| 6 | 6 | #include "pycore_call.h" // _PyObject_CallNoArgs() | |
| 7 | + #include "pycore_ceval.h" // _Py_ReachedRecursionLimitWithMargin() | ||
| 7 | 8 | #include "pycore_fileutils.h" // _PyFile_Flush | |
| 8 | 9 | #include "pycore_initconfig.h" // _PyStatus_ERR() | |
| 9 | 10 | #include "pycore_pyerrors.h" // _PyErr_Format() | |
@@ -337,17 +338,27 @@ PyErr_GivenExceptionMatches(PyObject *err, PyObject *exc) | |||
| 337 | 338 | return 0; | |
| 338 | 339 | } | |
| 339 | 340 | if (PyTuple_Check(exc)) { | |
| 340 | - Py_ssize_t i, n; | ||
| 341 | - n = PyTuple_Size(exc); | ||
| 342 | - for (i = 0; i < n; i++) { | ||
| 341 | + PyThreadState *tstate = _PyThreadState_GET(); | ||
| 342 | + if (_Py_ReachedRecursionLimitWithMargin(tstate, 2)) { | ||
| 343 | + PyObject *exc_value = _PyErr_GetRaisedException(tstate); | ||
| 344 | + _PyErr_SetString(tstate, PyExc_RecursionError, | ||
| 345 | + "maximum recursion depth exceeded while " | ||
| 346 | + "checking exception tuple"); | ||
| 347 | + PyErr_FormatUnraisable("Exception ignored while " | ||
| 348 | + "checking exception tuple"); | ||
| 349 | + _PyErr_SetRaisedException(tstate, exc_value); | ||
| 350 | + return 0; | ||
| 351 | + } | ||
| 352 | + int res = 0; | ||
| 353 | + Py_ssize_t n = PyTuple_GET_SIZE(exc); | ||
| 354 | + for (Py_ssize_t i = 0; i < n; i++) { | ||
| 343 | 355 | /* Test recursively */ | |
| 344 | - if (PyErr_GivenExceptionMatches( | ||
| 345 | - err, PyTuple_GET_ITEM(exc, i))) | ||
| 346 | - { | ||
| 347 | - return 1; | ||
| 348 | - } | ||
| 356 | + if (PyErr_GivenExceptionMatches(err, PyTuple_GET_ITEM(exc, i))) { | ||
| 357 | + res = 1; | ||
| 358 | + break; | ||
| 359 | + } | ||
| 349 | 360 | } | |
| 350 | - return 0; | ||
| 361 | + return res; | ||
| 351 | 362 | } | |
| 352 | 363 | /* err might be an instance, so check its class. */ | |
| 353 | 364 | if (PyExceptionInstance_Check(err)) | |
| Back | FazBrowse Home | New Git URL |
0 commit comments