From f488fb422a641aa7c38eb63c09f459e4baff7bc4 Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Fri, 3 Jul 2015 01:04:23 -0400 Subject: [PATCH] Issue #19235: Add new RecursionError exception. Patch by Georg Brandl. --- Doc/c-api/exceptions.rst | 12 +++++++++--- Doc/library/exceptions.rst | 10 ++++++++++ Doc/library/pickle.rst | 2 +- Doc/whatsnew/3.5.rst | 2 ++ Include/ceval.h | 6 +++--- Include/pyerrors.h | 1 + Lib/ctypes/test/test_as_parameter.py | 2 +- Lib/test/exception_hierarchy.txt | 1 + Lib/test/list_tests.py | 2 +- Lib/test/test_class.py | 4 ++-- Lib/test/test_compile.py | 2 +- Lib/test/test_copy.py | 6 +++--- Lib/test/test_descr.py | 6 +++--- Lib/test/test_dictviews.py | 2 +- Lib/test/test_exceptions.py | 13 +++++++------ Lib/test/test_isinstance.py | 10 +++++----- Lib/test/test_json/test_recursion.py | 12 ++++++------ Lib/test/test_pickle.py | 3 ++- Lib/test/test_richcmp.py | 24 ++++++++++++------------ Lib/test/test_runpy.py | 2 +- Lib/test/test_sys.py | 6 +++--- Lib/test/test_threading.py | 2 +- Misc/NEWS | 2 ++ Modules/_pickle.c | 2 +- Modules/_sre.c | 3 ++- Objects/exceptions.c | 19 +++++++++++++------ Objects/typeobject.c | 2 +- Python/ceval.c | 2 +- Python/errors.c | 2 +- Python/symtable.c | 4 ++-- Tools/scripts/find_recursionlimit.py | 4 ++-- 31 files changed, 101 insertions(+), 69 deletions(-) diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst index 6ae723b8d9c..3fd69ba80fa 100644 --- a/Doc/c-api/exceptions.rst +++ b/Doc/c-api/exceptions.rst @@ -683,12 +683,12 @@ recursion depth automatically). sets a :exc:`MemoryError` and returns a nonzero value. The function then checks if the recursion limit is reached. If this is the - case, a :exc:`RuntimeError` is set and a nonzero value is returned. + case, a :exc:`RecursionError` is set and a nonzero value is returned. Otherwise, zero is returned. *where* should be a string such as ``" in instance check"`` to be - concatenated to the :exc:`RuntimeError` message caused by the recursion depth - limit. + concatenated to the :exc:`RecursionError` message caused by the recursion + depth limit. .. c:function:: void Py_LeaveRecursiveCall() @@ -800,6 +800,8 @@ the variables: +-----------------------------------------+---------------------------------+----------+ | :c:data:`PyExc_ProcessLookupError` | :exc:`ProcessLookupError` | | +-----------------------------------------+---------------------------------+----------+ +| :c:data:`PyExc_RecursionError` | :exc:`RecursionError` | | ++-----------------------------------------+---------------------------------+----------+ | :c:data:`PyExc_ReferenceError` | :exc:`ReferenceError` | \(2) | +-----------------------------------------+---------------------------------+----------+ | :c:data:`PyExc_RuntimeError` | :exc:`RuntimeError` | | @@ -829,6 +831,9 @@ the variables: :c:data:`PyExc_PermissionError`, :c:data:`PyExc_ProcessLookupError` and :c:data:`PyExc_TimeoutError` were introduced following :pep:`3151`. +.. versionadded:: 3.5 + :c:data:`PyExc_RecursionError`. + These are compatibility aliases to :c:data:`PyExc_OSError`: @@ -877,6 +882,7 @@ These are compatibility aliases to :c:data:`PyExc_OSError`: single: PyExc_OverflowError single: PyExc_PermissionError single: PyExc_ProcessLookupError + single: PyExc_RecursionError single: PyExc_ReferenceError single: PyExc_RuntimeError single: PyExc_SyntaxError diff --git a/Doc/library/exceptions.rst b/Doc/library/exceptions.rst index 1a9d0299d5f..0a422b238d1 100644 --- a/Doc/library/exceptions.rst +++ b/Doc/library/exceptions.rst @@ -282,6 +282,16 @@ The following exceptions are the exceptions that are usually raised. handling in C, most floating point operations are not checked. +.. exception:: RecursionError + + This exception is derived from :exc:`RuntimeError`. It is raised when the + interpreter detects that the maximum recursion depth (see + :func:`sys.getrecursionlimit`) is exceeded. + + .. versionadded:: 3.5 + Previously, a plain :exc:`RuntimeError` was raised. + + .. exception:: ReferenceError This exception is raised when a weak reference proxy, created by the diff --git a/Doc/library/pickle.rst b/Doc/library/pickle.rst index 4ce4d345af4..e34f2b39936 100644 --- a/Doc/library/pickle.rst +++ b/Doc/library/pickle.rst @@ -425,7 +425,7 @@ The following types can be pickled: Attempts to pickle unpicklable objects will raise the :exc:`PicklingError` exception; when this happens, an unspecified number of bytes may have already been written to the underlying file. Trying to pickle a highly recursive data -structure may exceed the maximum recursion depth, a :exc:`RuntimeError` will be +structure may exceed the maximum recursion depth, a :exc:`RecursionError` will be raised in this case. You can carefully raise this limit with :func:`sys.setrecursionlimit`. diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst index bfefb2fe0d3..b73c80df3d4 100644 --- a/Doc/whatsnew/3.5.rst +++ b/Doc/whatsnew/3.5.rst @@ -87,6 +87,8 @@ New built-in features: * Generators have new ``gi_yieldfrom`` attribute, which returns the object being iterated by ``yield from`` expressions. (Contributed by Benno Leslie and Yury Selivanov in :issue:`24450`.) +* New :exc:`RecursionError` exception. (Contributed by Georg Brandl + in :issue:`19235`.) Implementation improvements: diff --git a/Include/ceval.h b/Include/ceval.h index 2472ae6492f..eb1ee43497c 100644 --- a/Include/ceval.h +++ b/Include/ceval.h @@ -48,16 +48,16 @@ PyAPI_FUNC(int) Py_MakePendingCalls(void); In Python 3.0, this protection has two levels: * normal anti-recursion protection is triggered when the recursion level - exceeds the current recursion limit. It raises a RuntimeError, and sets + exceeds the current recursion limit. It raises a RecursionError, and sets the "overflowed" flag in the thread state structure. This flag temporarily *disables* the normal protection; this allows cleanup code to potentially outgrow the recursion limit while processing the - RuntimeError. + RecursionError. * "last chance" anti-recursion protection is triggered when the recursion level exceeds "current recursion limit + 50". By construction, this protection can only be triggered when the "overflowed" flag is set. It means the cleanup code has itself gone into an infinite loop, or the - RuntimeError has been mistakingly ignored. When this protection is + RecursionError has been mistakingly ignored. When this protection is triggered, the interpreter aborts with a Fatal Error. In addition, the "overflowed" flag is automatically reset when the diff --git a/Include/pyerrors.h b/Include/pyerrors.h index a0198659a1a..35aedb73492 100644 --- a/Include/pyerrors.h +++ b/Include/pyerrors.h @@ -167,6 +167,7 @@ PyAPI_DATA(PyObject *) PyExc_MemoryError; PyAPI_DATA(PyObject *) PyExc_NameError; PyAPI_DATA(PyObject *) PyExc_OverflowError; PyAPI_DATA(PyObject *) PyExc_RuntimeError; +PyAPI_DATA(PyObject *) PyExc_RecursionError; PyAPI_DATA(PyObject *) PyExc_NotImplementedError; PyAPI_DATA(PyObject *) PyExc_SyntaxError; PyAPI_DATA(PyObject *) PyExc_IndentationError; diff --git a/Lib/ctypes/test/test_as_parameter.py b/Lib/ctypes/test/test_as_parameter.py index 948b4632154..2a3484bec01 100644 --- a/Lib/ctypes/test/test_as_parameter.py +++ b/Lib/ctypes/test/test_as_parameter.py @@ -194,7 +194,7 @@ class BasicWrapTestCase(unittest.TestCase): a = A() a._as_parameter_ = a - with self.assertRaises(RuntimeError): + with self.assertRaises(RecursionError): c_int.from_param(a) diff --git a/Lib/test/exception_hierarchy.txt b/Lib/test/exception_hierarchy.txt index 6632826afaa..05137654de6 100644 --- a/Lib/test/exception_hierarchy.txt +++ b/Lib/test/exception_hierarchy.txt @@ -39,6 +39,7 @@ BaseException +-- ReferenceError +-- RuntimeError | +-- NotImplementedError + | +-- RecursionError +-- SyntaxError | +-- IndentationError | +-- TabError diff --git a/Lib/test/list_tests.py b/Lib/test/list_tests.py index 906933796d2..1adfc75b77a 100644 --- a/Lib/test/list_tests.py +++ b/Lib/test/list_tests.py @@ -56,7 +56,7 @@ class CommonTest(seq_tests.CommonTest): l0 = [] for i in range(sys.getrecursionlimit() + 100): l0 = [l0] - self.assertRaises(RuntimeError, repr, l0) + self.assertRaises(RecursionError, repr, l0) def test_print(self): d = self.type2test(range(200)) diff --git a/Lib/test/test_class.py b/Lib/test/test_class.py index 6036e360322..4d554a397b4 100644 --- a/Lib/test/test_class.py +++ b/Lib/test/test_class.py @@ -500,10 +500,10 @@ class ClassTests(unittest.TestCase): try: a() # This should not segfault - except RuntimeError: + except RecursionError: pass else: - self.fail("Failed to raise RuntimeError") + self.fail("Failed to raise RecursionError") def testForExceptionsRaisedInInstanceGetattr2(self): # Tests for exceptions raised in instance_getattr2(). diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index f5e45766728..db821be031c 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -534,7 +534,7 @@ if 1: broken = prefix + repeated * fail_depth details = "Compiling ({!r} + {!r} * {})".format( prefix, repeated, fail_depth) - with self.assertRaises(RuntimeError, msg=details): + with self.assertRaises(RecursionError, msg=details): self.compile_single(broken) check_limit("a", "()") diff --git a/Lib/test/test_copy.py b/Lib/test/test_copy.py index 4c197467e9c..b9eadddbf26 100644 --- a/Lib/test/test_copy.py +++ b/Lib/test/test_copy.py @@ -327,7 +327,7 @@ class TestCopy(unittest.TestCase): x.append(x) y = copy.deepcopy(x) for op in comparisons: - self.assertRaises(RuntimeError, op, y, x) + self.assertRaises(RecursionError, op, y, x) self.assertIsNot(y, x) self.assertIs(y[0], y) self.assertEqual(len(y), 1) @@ -354,7 +354,7 @@ class TestCopy(unittest.TestCase): x[0].append(x) y = copy.deepcopy(x) for op in comparisons: - self.assertRaises(RuntimeError, op, y, x) + self.assertRaises(RecursionError, op, y, x) self.assertIsNot(y, x) self.assertIsNot(y[0], x[0]) self.assertIs(y[0][0], y) @@ -373,7 +373,7 @@ class TestCopy(unittest.TestCase): for op in order_comparisons: self.assertRaises(TypeError, op, y, x) for op in equality_comparisons: - self.assertRaises(RuntimeError, op, y, x) + self.assertRaises(RecursionError, op, y, x) self.assertIsNot(y, x) self.assertIs(y['foo'], y) self.assertEqual(len(y), 1) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 80a526d78cc..0ef1a31e0f4 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -3342,7 +3342,7 @@ order (MRO) for bases """ A.__call__ = A() try: A()() - except RuntimeError: + except RecursionError: pass else: self.fail("Recursion limit should have been reached for __call__()") @@ -4317,8 +4317,8 @@ order (MRO) for bases """ pass Foo.__repr__ = Foo.__str__ foo = Foo() - self.assertRaises(RuntimeError, str, foo) - self.assertRaises(RuntimeError, repr, foo) + self.assertRaises(RecursionError, str, foo) + self.assertRaises(RecursionError, repr, foo) def test_mixing_slot_wrappers(self): class X(dict): diff --git a/Lib/test/test_dictviews.py b/Lib/test/test_dictviews.py index 280353a3a2b..8d33801ca09 100644 --- a/Lib/test/test_dictviews.py +++ b/Lib/test/test_dictviews.py @@ -195,7 +195,7 @@ class DictSetTest(unittest.TestCase): def test_recursive_repr(self): d = {} d[42] = d.values() - self.assertRaises(RuntimeError, repr, d) + self.assertRaises(RecursionError, repr, d) if __name__ == "__main__": diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py index 80d4f1aeb5a..3bfb582cb69 100644 --- a/Lib/test/test_exceptions.py +++ b/Lib/test/test_exceptions.py @@ -84,6 +84,7 @@ class ExceptionTests(unittest.TestCase): x += x # this simply shouldn't blow up self.raise_catch(RuntimeError, "RuntimeError") + self.raise_catch(RecursionError, "RecursionError") self.raise_catch(SyntaxError, "SyntaxError") try: exec('/\n') @@ -474,14 +475,14 @@ class ExceptionTests(unittest.TestCase): def testInfiniteRecursion(self): def f(): return f() - self.assertRaises(RuntimeError, f) + self.assertRaises(RecursionError, f) def g(): try: return g() except ValueError: return -1 - self.assertRaises(RuntimeError, g) + self.assertRaises(RecursionError, g) def test_str(self): # Make sure both instances and classes have a str representation. @@ -887,10 +888,10 @@ class ExceptionTests(unittest.TestCase): def g(): try: return g() - except RuntimeError: + except RecursionError: return sys.exc_info() e, v, tb = g() - self.assertTrue(isinstance(v, RuntimeError), type(v)) + self.assertTrue(isinstance(v, RecursionError), type(v)) self.assertIn("maximum recursion depth exceeded", str(v)) @@ -989,10 +990,10 @@ class ExceptionTests(unittest.TestCase): # We cannot use assertRaises since it manually deletes the traceback try: inner() - except RuntimeError as e: + except RecursionError as e: self.assertNotEqual(wr(), None) else: - self.fail("RuntimeError not raised") + self.fail("RecursionError not raised") self.assertEqual(wr(), None) def test_errno_ENOTDIR(self): diff --git a/Lib/test/test_isinstance.py b/Lib/test/test_isinstance.py index e087ac0bad5..e63d59b346a 100644 --- a/Lib/test/test_isinstance.py +++ b/Lib/test/test_isinstance.py @@ -258,18 +258,18 @@ class TestIsInstanceIsSubclass(unittest.TestCase): self.assertEqual(True, issubclass(str, (str, (Child, NewChild, str)))) def test_subclass_recursion_limit(self): - # make sure that issubclass raises RuntimeError before the C stack is + # make sure that issubclass raises RecursionError before the C stack is # blown - self.assertRaises(RuntimeError, blowstack, issubclass, str, str) + self.assertRaises(RecursionError, blowstack, issubclass, str, str) def test_isinstance_recursion_limit(self): - # make sure that issubclass raises RuntimeError before the C stack is + # make sure that issubclass raises RecursionError before the C stack is # blown - self.assertRaises(RuntimeError, blowstack, isinstance, '', str) + self.assertRaises(RecursionError, blowstack, isinstance, '', str) def blowstack(fxn, arg, compare_to): # Make sure that calling isinstance with a deeply nested tuple for its - # argument will raise RuntimeError eventually. + # argument will raise RecursionError eventually. tuple_arg = (compare_to,) for cnt in range(sys.getrecursionlimit()+5): tuple_arg = (tuple_arg,) diff --git a/Lib/test/test_json/test_recursion.py b/Lib/test/test_json/test_recursion.py index 1a76254d01b..877dc448b14 100644 --- a/Lib/test/test_json/test_recursion.py +++ b/Lib/test/test_json/test_recursion.py @@ -68,11 +68,11 @@ class TestRecursion: def test_highly_nested_objects_decoding(self): # test that loading highly-nested objects doesn't segfault when C # accelerations are used. See #12017 - with self.assertRaises(RuntimeError): + with self.assertRaises(RecursionError): self.loads('{"a":' * 100000 + '1' + '}' * 100000) - with self.assertRaises(RuntimeError): + with self.assertRaises(RecursionError): self.loads('{"a":' * 100000 + '[1]' + '}' * 100000) - with self.assertRaises(RuntimeError): + with self.assertRaises(RecursionError): self.loads('[' * 100000 + '1' + ']' * 100000) def test_highly_nested_objects_encoding(self): @@ -80,9 +80,9 @@ class TestRecursion: l, d = [], {} for x in range(100000): l, d = [l], {'k':d} - with self.assertRaises(RuntimeError): + with self.assertRaises(RecursionError): self.dumps(l) - with self.assertRaises(RuntimeError): + with self.assertRaises(RecursionError): self.dumps(d) def test_endless_recursion(self): @@ -92,7 +92,7 @@ class TestRecursion: """If check_circular is False, this will keep adding another list.""" return [o] - with self.assertRaises(RuntimeError): + with self.assertRaises(RecursionError): EndlessJSONEncoder(check_circular=False).encode(5j) diff --git a/Lib/test/test_pickle.py b/Lib/test/test_pickle.py index a9853c1dab6..ba92de94c59 100644 --- a/Lib/test/test_pickle.py +++ b/Lib/test/test_pickle.py @@ -353,7 +353,8 @@ class CompatPickleTests(unittest.TestCase): with self.subTest(name): if exc in (BlockingIOError, ResourceWarning, - StopAsyncIteration): + StopAsyncIteration, + RecursionError): continue if exc is not OSError and issubclass(exc, OSError): self.assertEqual(reverse_mapping('builtins', name), diff --git a/Lib/test/test_richcmp.py b/Lib/test/test_richcmp.py index 94185a4aabd..1582caad974 100644 --- a/Lib/test/test_richcmp.py +++ b/Lib/test/test_richcmp.py @@ -228,25 +228,25 @@ class MiscTest(unittest.TestCase): b = UserList() a.append(b) b.append(a) - self.assertRaises(RuntimeError, operator.eq, a, b) - self.assertRaises(RuntimeError, operator.ne, a, b) - self.assertRaises(RuntimeError, operator.lt, a, b) - self.assertRaises(RuntimeError, operator.le, a, b) - self.assertRaises(RuntimeError, operator.gt, a, b) - self.assertRaises(RuntimeError, operator.ge, a, b) + self.assertRaises(RecursionError, operator.eq, a, b) + self.assertRaises(RecursionError, operator.ne, a, b) + self.assertRaises(RecursionError, operator.lt, a, b) + self.assertRaises(RecursionError, operator.le, a, b) + self.assertRaises(RecursionError, operator.gt, a, b) + self.assertRaises(RecursionError, operator.ge, a, b) b.append(17) # Even recursive lists of different lengths are different, # but they cannot be ordered self.assertTrue(not (a == b)) self.assertTrue(a != b) - self.assertRaises(RuntimeError, operator.lt, a, b) - self.assertRaises(RuntimeError, operator.le, a, b) - self.assertRaises(RuntimeError, operator.gt, a, b) - self.assertRaises(RuntimeError, operator.ge, a, b) + self.assertRaises(RecursionError, operator.lt, a, b) + self.assertRaises(RecursionError, operator.le, a, b) + self.assertRaises(RecursionError, operator.gt, a, b) + self.assertRaises(RecursionError, operator.ge, a, b) a.append(17) - self.assertRaises(RuntimeError, operator.eq, a, b) - self.assertRaises(RuntimeError, operator.ne, a, b) + self.assertRaises(RecursionError, operator.eq, a, b) + self.assertRaises(RecursionError, operator.ne, a, b) a.insert(0, 11) b.insert(0, 12) self.assertTrue(not (a == b)) diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py index 5a799bdcf77..4bae949d21f 100644 --- a/Lib/test/test_runpy.py +++ b/Lib/test/test_runpy.py @@ -673,7 +673,7 @@ class RunPathTestCase(unittest.TestCase, CodeExecutionMixin): script_name = self._make_test_script(script_dir, mod_name, source) zip_name, fname = make_zip_script(script_dir, 'test_zip', script_name) msg = "recursion depth exceeded" - self.assertRaisesRegex(RuntimeError, msg, run_path, zip_name) + self.assertRaisesRegex(RecursionError, msg, run_path, zip_name) def test_encoding(self): with temp_dir() as script_dir: diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 494a53bf24d..83549bc5376 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -211,8 +211,8 @@ class SysModuleTest(unittest.TestCase): for i in (50, 1000): # Issue #5392: stack overflow after hitting recursion limit twice sys.setrecursionlimit(i) - self.assertRaises(RuntimeError, f) - self.assertRaises(RuntimeError, f) + self.assertRaises(RecursionError, f) + self.assertRaises(RecursionError, f) finally: sys.setrecursionlimit(oldlimit) @@ -225,7 +225,7 @@ class SysModuleTest(unittest.TestCase): def f(): try: f() - except RuntimeError: + except RecursionError: f() sys.setrecursionlimit(%d) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index ddafba24b13..3b11bf65080 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -945,7 +945,7 @@ class ThreadingExceptionTests(BaseTestCase): def outer(): try: recurse() - except RuntimeError: + except RecursionError: pass w = threading.Thread(target=outer) diff --git a/Misc/NEWS b/Misc/NEWS index a1563df5e26..75e717f8115 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -30,6 +30,8 @@ Core and Builtins - Issue #24450: Add gi_yieldfrom to generators and cr_await to coroutines. Contributed by Benno Leslie and Yury Selivanov. +- Issue #19235: Add new RecursionError exception. Patch by Georg Brandl. + Library ------- diff --git a/Modules/_pickle.c b/Modules/_pickle.c index 44f840d1998..3ad9a976410 100644 --- a/Modules/_pickle.c +++ b/Modules/_pickle.c @@ -3622,7 +3622,7 @@ save_reduce(PicklerObject *self, PyObject *args, PyObject *obj) >>> pickle.dumps(1+2j) Traceback (most recent call last): ... - RuntimeError: maximum recursion depth exceeded + RecursionError: maximum recursion depth exceeded Removing the complex class from copyreg.dispatch_table made the __reduce_ex__() method emit another complex object: diff --git a/Modules/_sre.c b/Modules/_sre.c index 4016a4533e0..4f47393aed9 100644 --- a/Modules/_sre.c +++ b/Modules/_sre.c @@ -500,8 +500,9 @@ pattern_error(Py_ssize_t status) { switch (status) { case SRE_ERROR_RECURSION_LIMIT: + /* This error code seems to be unused. */ PyErr_SetString( - PyExc_RuntimeError, + PyExc_RecursionError, "maximum recursion limit exceeded" ); break; diff --git a/Objects/exceptions.c b/Objects/exceptions.c index d494995ddef..a2759978107 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -1231,6 +1231,11 @@ SimpleExtendsException(PyExc_Exception, EOFError, SimpleExtendsException(PyExc_Exception, RuntimeError, "Unspecified run-time error."); +/* + * RecursionError extends RuntimeError + */ +SimpleExtendsException(PyExc_RuntimeError, RecursionError, + "Recursion limit exceeded."); /* * NotImplementedError extends RuntimeError @@ -2380,7 +2385,7 @@ SimpleExtendsException(PyExc_Warning, ResourceWarning, -/* Pre-computed RuntimeError instance for when recursion depth is reached. +/* 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. */ @@ -2484,6 +2489,7 @@ _PyExc_Init(PyObject *bltinmod) PRE_INIT(OSError) PRE_INIT(EOFError) PRE_INIT(RuntimeError) + PRE_INIT(RecursionError) PRE_INIT(NotImplementedError) PRE_INIT(NameError) PRE_INIT(UnboundLocalError) @@ -2560,6 +2566,7 @@ _PyExc_Init(PyObject *bltinmod) #endif POST_INIT(EOFError) POST_INIT(RuntimeError) + POST_INIT(RecursionError) POST_INIT(NotImplementedError) POST_INIT(NameError) POST_INIT(UnboundLocalError) @@ -2643,9 +2650,9 @@ _PyExc_Init(PyObject *bltinmod) preallocate_memerrors(); if (!PyExc_RecursionErrorInst) { - PyExc_RecursionErrorInst = BaseException_new(&_PyExc_RuntimeError, NULL, NULL); + PyExc_RecursionErrorInst = BaseException_new(&_PyExc_RecursionError, NULL, NULL); if (!PyExc_RecursionErrorInst) - Py_FatalError("Cannot pre-allocate RuntimeError instance for " + Py_FatalError("Cannot pre-allocate RecursionError instance for " "recursion errors"); else { PyBaseExceptionObject *err_inst = @@ -2654,15 +2661,15 @@ _PyExc_Init(PyObject *bltinmod) PyObject *exc_message; exc_message = PyUnicode_FromString("maximum recursion depth exceeded"); if (!exc_message) - Py_FatalError("cannot allocate argument for RuntimeError " + 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 RuntimeError " + 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 RuntimeError failed"); + Py_FatalError("init of pre-allocated RecursionError failed"); Py_DECREF(args_tuple); } } diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 82c8710824d..1beed724587 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4142,7 +4142,7 @@ reduce_newobj(PyObject *obj, int proto) * were implemented in the same function: * - trying to pickle an object with a custom __reduce__ method that * fell back to object.__reduce__ in certain circumstances led to - * infinite recursion at Python level and eventual RuntimeError. + * infinite recursion at Python level and eventual RecursionError. * - Pickling objects that lied about their type by overwriting the * __class__ descriptor could lead to infinite recursion at C level * and eventual segfault. diff --git a/Python/ceval.c b/Python/ceval.c index 12df0fe3fc5..e68ae33bfd7 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -737,7 +737,7 @@ _Py_CheckRecursiveCall(const char *where) if (tstate->recursion_depth > recursion_limit) { --tstate->recursion_depth; tstate->overflowed = 1; - PyErr_Format(PyExc_RuntimeError, + PyErr_Format(PyExc_RecursionError, "maximum recursion depth exceeded%s", where); return -1; diff --git a/Python/errors.c b/Python/errors.c index 1172c590474..aed2bdc12d6 100644 --- a/Python/errors.c +++ b/Python/errors.c @@ -319,7 +319,7 @@ finally: Py_DECREF(*exc); Py_DECREF(*val); /* ... and use the recursion error instead */ - *exc = PyExc_RuntimeError; + *exc = PyExc_RecursionError; *val = PyExc_RecursionErrorInst; Py_INCREF(*exc); Py_INCREF(*val); diff --git a/Python/symtable.c b/Python/symtable.c index 3677e597482..354b7996786 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -1135,7 +1135,7 @@ static int symtable_visit_stmt(struct symtable *st, stmt_ty s) { if (++st->recursion_depth > st->recursion_limit) { - PyErr_SetString(PyExc_RuntimeError, + PyErr_SetString(PyExc_RecursionError, "maximum recursion depth exceeded during compilation"); VISIT_QUIT(st, 0); } @@ -1357,7 +1357,7 @@ static int symtable_visit_expr(struct symtable *st, expr_ty e) { if (++st->recursion_depth > st->recursion_limit) { - PyErr_SetString(PyExc_RuntimeError, + PyErr_SetString(PyExc_RecursionError, "maximum recursion depth exceeded during compilation"); VISIT_QUIT(st, 0); } diff --git a/Tools/scripts/find_recursionlimit.py b/Tools/scripts/find_recursionlimit.py index 11711464214..b2842a62efd 100755 --- a/Tools/scripts/find_recursionlimit.py +++ b/Tools/scripts/find_recursionlimit.py @@ -92,7 +92,7 @@ def test_cpickle(_cache={}): def test_compiler_recursion(): # The compiler uses a scaling factor to support additional levels # of recursion. This is a sanity check of that scaling to ensure - # it still raises RuntimeError even at higher recursion limits + # it still raises RecursionError even at higher recursion limits compile("()" * (10 * sys.getrecursionlimit()), "", "single") def check_limit(n, test_func_name): @@ -107,7 +107,7 @@ def check_limit(n, test_func_name): # AttributeError can be raised because of the way e.g. PyDict_GetItem() # silences all exceptions and returns NULL, which is usually interpreted # as "missing attribute". - except (RuntimeError, AttributeError): + except (RecursionError, AttributeError): pass else: print("Yikes!")