summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/c-api/exceptions.rst12
-rw-r--r--Doc/library/exceptions.rst10
-rw-r--r--Doc/library/pickle.rst2
-rw-r--r--Doc/whatsnew/3.5.rst2
-rw-r--r--Include/ceval.h6
-rw-r--r--Include/pyerrors.h1
-rw-r--r--Lib/ctypes/test/test_as_parameter.py2
-rw-r--r--Lib/test/exception_hierarchy.txt1
-rw-r--r--Lib/test/list_tests.py2
-rw-r--r--Lib/test/test_class.py4
-rw-r--r--Lib/test/test_compile.py2
-rw-r--r--Lib/test/test_copy.py6
-rw-r--r--Lib/test/test_descr.py6
-rw-r--r--Lib/test/test_dictviews.py2
-rw-r--r--Lib/test/test_exceptions.py13
-rw-r--r--Lib/test/test_isinstance.py10
-rw-r--r--Lib/test/test_json/test_recursion.py12
-rw-r--r--Lib/test/test_pickle.py3
-rw-r--r--Lib/test/test_richcmp.py24
-rw-r--r--Lib/test/test_runpy.py2
-rw-r--r--Lib/test/test_sys.py6
-rw-r--r--Lib/test/test_threading.py2
-rw-r--r--Misc/NEWS2
-rw-r--r--Modules/_pickle.c2
-rw-r--r--Modules/_sre.c3
-rw-r--r--Objects/exceptions.c19
-rw-r--r--Objects/typeobject.c2
-rw-r--r--Python/ceval.c2
-rw-r--r--Python/errors.c2
-rw-r--r--Python/symtable.c4
-rwxr-xr-xTools/scripts/find_recursionlimit.py4
31 files changed, 101 insertions, 69 deletions
diff --git a/Doc/c-api/exceptions.rst b/Doc/c-api/exceptions.rst
index 6ae723b..3fd69ba 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 1a9d029..0a422b2 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 4ce4d34..e34f2b3 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 bfefb2f..b73c80d 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 2472ae6..eb1ee43 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 a019865..35aedb7 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 948b463..2a3484b 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 6632826..0513765 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 9069337..1adfc75 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 6036e36..4d554a3 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 f5e4576..db821be 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 4c19746..b9eaddd 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 80a526d..0ef1a31 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 280353a..8d33801 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 80d4f1a..3bfb582 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 e087ac0..e63d59b 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 1a76254..877dc44 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 a9853c1..ba92de9 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 94185a4..1582caa 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 5a799bd..4bae949 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 494a53b..83549bc 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 ddafba2..3b11bf6 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 a1563df..75e717f 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 44f840d..3ad9a97 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 4016a45..4f47393 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 d494995..a275997 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 82c8710..1beed72 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 12df0fe..e68ae33 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 1172c59..aed2bdc 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 3677e59..354b799 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 1171146..b2842a6 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>", "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!")