diff --git a/Include/pyerrors.h b/Include/pyerrors.h index fcaba85488e535..e9b730dcafc980 100644 --- a/Include/pyerrors.h +++ b/Include/pyerrors.h @@ -219,8 +219,6 @@ PyAPI_DATA(PyObject *) PyExc_IOError; PyAPI_DATA(PyObject *) PyExc_WindowsError; #endif -PyAPI_DATA(PyObject *) PyExc_RecursionErrorInst; - /* Predefined warning categories */ PyAPI_DATA(PyObject *) PyExc_Warning; PyAPI_DATA(PyObject *) PyExc_UserWarning; diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 3378ceb701c044..c21a7759626fc9 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -10,7 +10,7 @@ from test.support import (TESTFN, captured_stderr, check_impl_detail, check_warnings, cpython_only, gc_collect, run_unittest, - no_tracing, unlink, import_module) + no_tracing, unlink, import_module, script_helper) class NaiveException(Exception): def __init__(self, x): @@ -908,6 +908,60 @@ def g(): self.assertIsInstance(v, RecursionError, type(v)) self.assertIn("maximum recursion depth exceeded", str(v)) + @cpython_only + def test_recursion_normalizing_exception(self): + # Issue #22898. + # Test that an infinite recursion is not entered when recursion_depth + # is equal to recursion_limit in PyErr_NormalizeException(). Prior to + # #22898 a PyExc_RecursionErrorInst singleton was being used that held + # traceback data and locals indefinitely and would cause a segfault in + # _PyExc_Fini() upon finalization of these locals. + code = """if 1: + import sys + from _testcapi import get_recursion_depth + + class MyException(Exception): pass + + def setrecursionlimit(depth): + while 1: + try: + sys.setrecursionlimit(depth) + return depth + except RecursionError: + # sys.setrecursionlimit() raises a RecursionError if + # the new recursion limit is too low (issue #25274). + depth += 1 + + def recurse(cnt): + cnt -= 1 + if cnt: + recurse(cnt) + else: + generator.throw(MyException) + + def gen(): + f = open(%a, mode='rb', buffering=0) + yield + + generator = gen() + next(generator) + recursionlimit = sys.getrecursionlimit() + depth = get_recursion_depth() + try: + # Upon the last recursive invocation of recurse(), + # tstate->recursion_depth is equal to (recursion_limit - 1) + # and is equal to recursion_limit when _gen_throw() calls + # PyErr_NormalizeException(). + recurse(setrecursionlimit(depth + 2) - depth - 1) + finally: + sys.setrecursionlimit(recursionlimit) + print('Done.') + """ % __file__ + rc, out, err = script_helper.assert_python_failure("-c", code) + # Check that the program does not fail with SIGABRT. + self.assertEqual(rc, 1) + self.assertIn(b'RecursionError', err) + self.assertIn(b'Done.', out) @cpython_only def test_MemoryError(self): diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 858eff5fc26c34..b3faeb07b5a933 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2408,12 +2408,6 @@ SimpleExtendsException(PyExc_Warning, ResourceWarning, -/* Pre-computed RecursionError instance for when recursion depth is reached. - Meant to be used when normalizing the exception for exceeding the recursion - depth will cause its own infinite recursion. -*/ -PyObject *PyExc_RecursionErrorInst = NULL; - #define PRE_INIT(TYPE) \ if (!(_PyExc_ ## TYPE.tp_flags & Py_TPFLAGS_READY)) { \ if (PyType_Ready(&_PyExc_ ## TYPE) < 0) \ @@ -2673,37 +2667,11 @@ _PyExc_Init(PyObject *bltinmod) ADD_ERRNO(TimeoutError, ETIMEDOUT) preallocate_memerrors(); - - if (!PyExc_RecursionErrorInst) { - PyExc_RecursionErrorInst = BaseException_new(&_PyExc_RecursionError, NULL, NULL); - if (!PyExc_RecursionErrorInst) - Py_FatalError("Cannot pre-allocate RecursionError instance for " - "recursion errors"); - else { - PyBaseExceptionObject *err_inst = - (PyBaseExceptionObject *)PyExc_RecursionErrorInst; - PyObject *args_tuple; - PyObject *exc_message; - exc_message = PyUnicode_FromString("maximum recursion depth exceeded"); - if (!exc_message) - Py_FatalError("cannot allocate argument for RecursionError " - "pre-allocation"); - args_tuple = PyTuple_Pack(1, exc_message); - if (!args_tuple) - Py_FatalError("cannot allocate tuple for RecursionError " - "pre-allocation"); - Py_DECREF(exc_message); - if (BaseException_init(err_inst, args_tuple, NULL)) - Py_FatalError("init of pre-allocated RecursionError failed"); - Py_DECREF(args_tuple); - } - } } void _PyExc_Fini(void) { - Py_CLEAR(PyExc_RecursionErrorInst); free_preallocated_memerrors(); Py_CLEAR(errnomap); } diff --git a/PC/python3.def b/PC/python3.def index ad65294045f677..152f9d920176f3 100644 --- a/PC/python3.def +++ b/PC/python3.def @@ -224,7 +224,6 @@ EXPORTS PyExc_PermissionError=python37.PyExc_PermissionError DATA PyExc_ProcessLookupError=python37.PyExc_ProcessLookupError DATA PyExc_RecursionError=python37.PyExc_RecursionError DATA - PyExc_RecursionErrorInst=python37.PyExc_RecursionErrorInst DATA PyExc_ReferenceError=python37.PyExc_ReferenceError DATA PyExc_ResourceWarning=python37.PyExc_ResourceWarning DATA PyExc_RuntimeError=python37.PyExc_RuntimeError DATA diff --git a/Python/errors.c b/Python/errors.c index 3785e6981c6498..d18b7f4641ee25 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -306,16 +306,7 @@ PyErr_NormalizeException(PyObject **exc, PyObject **val, PyObject **tb) } /* normalize recursively */ tstate = PyThreadState_GET(); - if (++tstate->recursion_depth > Py_GetRecursionLimit()) { - --tstate->recursion_depth; - /* throw away the old exception and use the recursion error instead */ - Py_INCREF(PyExc_RecursionError); - Py_SETREF(*exc, PyExc_RecursionError); - Py_INCREF(PyExc_RecursionErrorInst); - Py_SETREF(*val, PyExc_RecursionErrorInst); - /* just keeping the old traceback */ - return; - } + ++tstate->recursion_depth; PyErr_NormalizeException(exc, val, tb); --tstate->recursion_depth; }