Issue #19235: Add new RecursionError exception. Patch by Georg Brandl.

This commit is contained in:
Yury Selivanov 2015-07-03 01:04:23 -04:00
parent 27be130ec7
commit f488fb422a
31 changed files with 101 additions and 69 deletions

View File

@ -683,12 +683,12 @@ recursion depth automatically).
sets a :exc:`MemoryError` and returns a nonzero value. sets a :exc:`MemoryError` and returns a nonzero value.
The function then checks if the recursion limit is reached. If this is the 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. Otherwise, zero is returned.
*where* should be a string such as ``" in instance check"`` to be *where* should be a string such as ``" in instance check"`` to be
concatenated to the :exc:`RuntimeError` message caused by the recursion depth concatenated to the :exc:`RecursionError` message caused by the recursion
limit. depth limit.
.. c:function:: void Py_LeaveRecursiveCall() .. c:function:: void Py_LeaveRecursiveCall()
@ -800,6 +800,8 @@ the variables:
+-----------------------------------------+---------------------------------+----------+ +-----------------------------------------+---------------------------------+----------+
| :c:data:`PyExc_ProcessLookupError` | :exc:`ProcessLookupError` | | | :c:data:`PyExc_ProcessLookupError` | :exc:`ProcessLookupError` | |
+-----------------------------------------+---------------------------------+----------+ +-----------------------------------------+---------------------------------+----------+
| :c:data:`PyExc_RecursionError` | :exc:`RecursionError` | |
+-----------------------------------------+---------------------------------+----------+
| :c:data:`PyExc_ReferenceError` | :exc:`ReferenceError` | \(2) | | :c:data:`PyExc_ReferenceError` | :exc:`ReferenceError` | \(2) |
+-----------------------------------------+---------------------------------+----------+ +-----------------------------------------+---------------------------------+----------+
| :c:data:`PyExc_RuntimeError` | :exc:`RuntimeError` | | | :c:data:`PyExc_RuntimeError` | :exc:`RuntimeError` | |
@ -829,6 +831,9 @@ the variables:
:c:data:`PyExc_PermissionError`, :c:data:`PyExc_ProcessLookupError` :c:data:`PyExc_PermissionError`, :c:data:`PyExc_ProcessLookupError`
and :c:data:`PyExc_TimeoutError` were introduced following :pep:`3151`. 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`: 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_OverflowError
single: PyExc_PermissionError single: PyExc_PermissionError
single: PyExc_ProcessLookupError single: PyExc_ProcessLookupError
single: PyExc_RecursionError
single: PyExc_ReferenceError single: PyExc_ReferenceError
single: PyExc_RuntimeError single: PyExc_RuntimeError
single: PyExc_SyntaxError single: PyExc_SyntaxError

View File

@ -282,6 +282,16 @@ The following exceptions are the exceptions that are usually raised.
handling in C, most floating point operations are not checked. 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 .. exception:: ReferenceError
This exception is raised when a weak reference proxy, created by the This exception is raised when a weak reference proxy, created by the

View File

@ -425,7 +425,7 @@ The following types can be pickled:
Attempts to pickle unpicklable objects will raise the :exc:`PicklingError` Attempts to pickle unpicklable objects will raise the :exc:`PicklingError`
exception; when this happens, an unspecified number of bytes may have already 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 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 raised in this case. You can carefully raise this limit with
:func:`sys.setrecursionlimit`. :func:`sys.setrecursionlimit`.

View File

@ -87,6 +87,8 @@ New built-in features:
* Generators have new ``gi_yieldfrom`` attribute, which returns the * Generators have new ``gi_yieldfrom`` attribute, which returns the
object being iterated by ``yield from`` expressions. (Contributed object being iterated by ``yield from`` expressions. (Contributed
by Benno Leslie and Yury Selivanov in :issue:`24450`.) by Benno Leslie and Yury Selivanov in :issue:`24450`.)
* New :exc:`RecursionError` exception. (Contributed by Georg Brandl
in :issue:`19235`.)
Implementation improvements: Implementation improvements:

View File

@ -48,16 +48,16 @@ PyAPI_FUNC(int) Py_MakePendingCalls(void);
In Python 3.0, this protection has two levels: In Python 3.0, this protection has two levels:
* normal anti-recursion protection is triggered when the recursion level * 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 the "overflowed" flag in the thread state structure. This flag
temporarily *disables* the normal protection; this allows cleanup code temporarily *disables* the normal protection; this allows cleanup code
to potentially outgrow the recursion limit while processing the to potentially outgrow the recursion limit while processing the
RuntimeError. RecursionError.
* "last chance" anti-recursion protection is triggered when the recursion * "last chance" anti-recursion protection is triggered when the recursion
level exceeds "current recursion limit + 50". By construction, this level exceeds "current recursion limit + 50". By construction, this
protection can only be triggered when the "overflowed" flag is set. It 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 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. triggered, the interpreter aborts with a Fatal Error.
In addition, the "overflowed" flag is automatically reset when the In addition, the "overflowed" flag is automatically reset when the

View File

@ -167,6 +167,7 @@ PyAPI_DATA(PyObject *) PyExc_MemoryError;
PyAPI_DATA(PyObject *) PyExc_NameError; PyAPI_DATA(PyObject *) PyExc_NameError;
PyAPI_DATA(PyObject *) PyExc_OverflowError; PyAPI_DATA(PyObject *) PyExc_OverflowError;
PyAPI_DATA(PyObject *) PyExc_RuntimeError; PyAPI_DATA(PyObject *) PyExc_RuntimeError;
PyAPI_DATA(PyObject *) PyExc_RecursionError;
PyAPI_DATA(PyObject *) PyExc_NotImplementedError; PyAPI_DATA(PyObject *) PyExc_NotImplementedError;
PyAPI_DATA(PyObject *) PyExc_SyntaxError; PyAPI_DATA(PyObject *) PyExc_SyntaxError;
PyAPI_DATA(PyObject *) PyExc_IndentationError; PyAPI_DATA(PyObject *) PyExc_IndentationError;

View File

@ -194,7 +194,7 @@ class BasicWrapTestCase(unittest.TestCase):
a = A() a = A()
a._as_parameter_ = a a._as_parameter_ = a
with self.assertRaises(RuntimeError): with self.assertRaises(RecursionError):
c_int.from_param(a) c_int.from_param(a)

View File

@ -39,6 +39,7 @@ BaseException
+-- ReferenceError +-- ReferenceError
+-- RuntimeError +-- RuntimeError
| +-- NotImplementedError | +-- NotImplementedError
| +-- RecursionError
+-- SyntaxError +-- SyntaxError
| +-- IndentationError | +-- IndentationError
| +-- TabError | +-- TabError

View File

@ -56,7 +56,7 @@ class CommonTest(seq_tests.CommonTest):
l0 = [] l0 = []
for i in range(sys.getrecursionlimit() + 100): for i in range(sys.getrecursionlimit() + 100):
l0 = [l0] l0 = [l0]
self.assertRaises(RuntimeError, repr, l0) self.assertRaises(RecursionError, repr, l0)
def test_print(self): def test_print(self):
d = self.type2test(range(200)) d = self.type2test(range(200))

View File

@ -500,10 +500,10 @@ class ClassTests(unittest.TestCase):
try: try:
a() # This should not segfault a() # This should not segfault
except RuntimeError: except RecursionError:
pass pass
else: else:
self.fail("Failed to raise RuntimeError") self.fail("Failed to raise RecursionError")
def testForExceptionsRaisedInInstanceGetattr2(self): def testForExceptionsRaisedInInstanceGetattr2(self):
# Tests for exceptions raised in instance_getattr2(). # Tests for exceptions raised in instance_getattr2().

View File

@ -534,7 +534,7 @@ if 1:
broken = prefix + repeated * fail_depth broken = prefix + repeated * fail_depth
details = "Compiling ({!r} + {!r} * {})".format( details = "Compiling ({!r} + {!r} * {})".format(
prefix, repeated, fail_depth) prefix, repeated, fail_depth)
with self.assertRaises(RuntimeError, msg=details): with self.assertRaises(RecursionError, msg=details):
self.compile_single(broken) self.compile_single(broken)
check_limit("a", "()") check_limit("a", "()")

View File

@ -327,7 +327,7 @@ class TestCopy(unittest.TestCase):
x.append(x) x.append(x)
y = copy.deepcopy(x) y = copy.deepcopy(x)
for op in comparisons: for op in comparisons:
self.assertRaises(RuntimeError, op, y, x) self.assertRaises(RecursionError, op, y, x)
self.assertIsNot(y, x) self.assertIsNot(y, x)
self.assertIs(y[0], y) self.assertIs(y[0], y)
self.assertEqual(len(y), 1) self.assertEqual(len(y), 1)
@ -354,7 +354,7 @@ class TestCopy(unittest.TestCase):
x[0].append(x) x[0].append(x)
y = copy.deepcopy(x) y = copy.deepcopy(x)
for op in comparisons: for op in comparisons:
self.assertRaises(RuntimeError, op, y, x) self.assertRaises(RecursionError, op, y, x)
self.assertIsNot(y, x) self.assertIsNot(y, x)
self.assertIsNot(y[0], x[0]) self.assertIsNot(y[0], x[0])
self.assertIs(y[0][0], y) self.assertIs(y[0][0], y)
@ -373,7 +373,7 @@ class TestCopy(unittest.TestCase):
for op in order_comparisons: for op in order_comparisons:
self.assertRaises(TypeError, op, y, x) self.assertRaises(TypeError, op, y, x)
for op in equality_comparisons: for op in equality_comparisons:
self.assertRaises(RuntimeError, op, y, x) self.assertRaises(RecursionError, op, y, x)
self.assertIsNot(y, x) self.assertIsNot(y, x)
self.assertIs(y['foo'], y) self.assertIs(y['foo'], y)
self.assertEqual(len(y), 1) self.assertEqual(len(y), 1)

View File

@ -3342,7 +3342,7 @@ order (MRO) for bases """
A.__call__ = A() A.__call__ = A()
try: try:
A()() A()()
except RuntimeError: except RecursionError:
pass pass
else: else:
self.fail("Recursion limit should have been reached for __call__()") self.fail("Recursion limit should have been reached for __call__()")
@ -4317,8 +4317,8 @@ order (MRO) for bases """
pass pass
Foo.__repr__ = Foo.__str__ Foo.__repr__ = Foo.__str__
foo = Foo() foo = Foo()
self.assertRaises(RuntimeError, str, foo) self.assertRaises(RecursionError, str, foo)
self.assertRaises(RuntimeError, repr, foo) self.assertRaises(RecursionError, repr, foo)
def test_mixing_slot_wrappers(self): def test_mixing_slot_wrappers(self):
class X(dict): class X(dict):

View File

@ -195,7 +195,7 @@ class DictSetTest(unittest.TestCase):
def test_recursive_repr(self): def test_recursive_repr(self):
d = {} d = {}
d[42] = d.values() d[42] = d.values()
self.assertRaises(RuntimeError, repr, d) self.assertRaises(RecursionError, repr, d)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -84,6 +84,7 @@ class ExceptionTests(unittest.TestCase):
x += x # this simply shouldn't blow up x += x # this simply shouldn't blow up
self.raise_catch(RuntimeError, "RuntimeError") self.raise_catch(RuntimeError, "RuntimeError")
self.raise_catch(RecursionError, "RecursionError")
self.raise_catch(SyntaxError, "SyntaxError") self.raise_catch(SyntaxError, "SyntaxError")
try: exec('/\n') try: exec('/\n')
@ -474,14 +475,14 @@ class ExceptionTests(unittest.TestCase):
def testInfiniteRecursion(self): def testInfiniteRecursion(self):
def f(): def f():
return f() return f()
self.assertRaises(RuntimeError, f) self.assertRaises(RecursionError, f)
def g(): def g():
try: try:
return g() return g()
except ValueError: except ValueError:
return -1 return -1
self.assertRaises(RuntimeError, g) self.assertRaises(RecursionError, g)
def test_str(self): def test_str(self):
# Make sure both instances and classes have a str representation. # Make sure both instances and classes have a str representation.
@ -887,10 +888,10 @@ class ExceptionTests(unittest.TestCase):
def g(): def g():
try: try:
return g() return g()
except RuntimeError: except RecursionError:
return sys.exc_info() return sys.exc_info()
e, v, tb = g() 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)) 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 # We cannot use assertRaises since it manually deletes the traceback
try: try:
inner() inner()
except RuntimeError as e: except RecursionError as e:
self.assertNotEqual(wr(), None) self.assertNotEqual(wr(), None)
else: else:
self.fail("RuntimeError not raised") self.fail("RecursionError not raised")
self.assertEqual(wr(), None) self.assertEqual(wr(), None)
def test_errno_ENOTDIR(self): def test_errno_ENOTDIR(self):

View File

@ -258,18 +258,18 @@ class TestIsInstanceIsSubclass(unittest.TestCase):
self.assertEqual(True, issubclass(str, (str, (Child, NewChild, str)))) self.assertEqual(True, issubclass(str, (str, (Child, NewChild, str))))
def test_subclass_recursion_limit(self): 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 # blown
self.assertRaises(RuntimeError, blowstack, issubclass, str, str) self.assertRaises(RecursionError, blowstack, issubclass, str, str)
def test_isinstance_recursion_limit(self): 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 # blown
self.assertRaises(RuntimeError, blowstack, isinstance, '', str) self.assertRaises(RecursionError, blowstack, isinstance, '', str)
def blowstack(fxn, arg, compare_to): def blowstack(fxn, arg, compare_to):
# Make sure that calling isinstance with a deeply nested tuple for its # 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,) tuple_arg = (compare_to,)
for cnt in range(sys.getrecursionlimit()+5): for cnt in range(sys.getrecursionlimit()+5):
tuple_arg = (tuple_arg,) tuple_arg = (tuple_arg,)

View File

@ -68,11 +68,11 @@ class TestRecursion:
def test_highly_nested_objects_decoding(self): def test_highly_nested_objects_decoding(self):
# test that loading highly-nested objects doesn't segfault when C # test that loading highly-nested objects doesn't segfault when C
# accelerations are used. See #12017 # accelerations are used. See #12017
with self.assertRaises(RuntimeError): with self.assertRaises(RecursionError):
self.loads('{"a":' * 100000 + '1' + '}' * 100000) self.loads('{"a":' * 100000 + '1' + '}' * 100000)
with self.assertRaises(RuntimeError): with self.assertRaises(RecursionError):
self.loads('{"a":' * 100000 + '[1]' + '}' * 100000) self.loads('{"a":' * 100000 + '[1]' + '}' * 100000)
with self.assertRaises(RuntimeError): with self.assertRaises(RecursionError):
self.loads('[' * 100000 + '1' + ']' * 100000) self.loads('[' * 100000 + '1' + ']' * 100000)
def test_highly_nested_objects_encoding(self): def test_highly_nested_objects_encoding(self):
@ -80,9 +80,9 @@ class TestRecursion:
l, d = [], {} l, d = [], {}
for x in range(100000): for x in range(100000):
l, d = [l], {'k':d} l, d = [l], {'k':d}
with self.assertRaises(RuntimeError): with self.assertRaises(RecursionError):
self.dumps(l) self.dumps(l)
with self.assertRaises(RuntimeError): with self.assertRaises(RecursionError):
self.dumps(d) self.dumps(d)
def test_endless_recursion(self): def test_endless_recursion(self):
@ -92,7 +92,7 @@ class TestRecursion:
"""If check_circular is False, this will keep adding another list.""" """If check_circular is False, this will keep adding another list."""
return [o] return [o]
with self.assertRaises(RuntimeError): with self.assertRaises(RecursionError):
EndlessJSONEncoder(check_circular=False).encode(5j) EndlessJSONEncoder(check_circular=False).encode(5j)

View File

@ -353,7 +353,8 @@ class CompatPickleTests(unittest.TestCase):
with self.subTest(name): with self.subTest(name):
if exc in (BlockingIOError, if exc in (BlockingIOError,
ResourceWarning, ResourceWarning,
StopAsyncIteration): StopAsyncIteration,
RecursionError):
continue continue
if exc is not OSError and issubclass(exc, OSError): if exc is not OSError and issubclass(exc, OSError):
self.assertEqual(reverse_mapping('builtins', name), self.assertEqual(reverse_mapping('builtins', name),

View File

@ -228,25 +228,25 @@ class MiscTest(unittest.TestCase):
b = UserList() b = UserList()
a.append(b) a.append(b)
b.append(a) b.append(a)
self.assertRaises(RuntimeError, operator.eq, a, b) self.assertRaises(RecursionError, operator.eq, a, b)
self.assertRaises(RuntimeError, operator.ne, a, b) self.assertRaises(RecursionError, operator.ne, a, b)
self.assertRaises(RuntimeError, operator.lt, a, b) self.assertRaises(RecursionError, operator.lt, a, b)
self.assertRaises(RuntimeError, operator.le, a, b) self.assertRaises(RecursionError, operator.le, a, b)
self.assertRaises(RuntimeError, operator.gt, a, b) self.assertRaises(RecursionError, operator.gt, a, b)
self.assertRaises(RuntimeError, operator.ge, a, b) self.assertRaises(RecursionError, operator.ge, a, b)
b.append(17) b.append(17)
# Even recursive lists of different lengths are different, # Even recursive lists of different lengths are different,
# but they cannot be ordered # but they cannot be ordered
self.assertTrue(not (a == b)) self.assertTrue(not (a == b))
self.assertTrue(a != b) self.assertTrue(a != b)
self.assertRaises(RuntimeError, operator.lt, a, b) self.assertRaises(RecursionError, operator.lt, a, b)
self.assertRaises(RuntimeError, operator.le, a, b) self.assertRaises(RecursionError, operator.le, a, b)
self.assertRaises(RuntimeError, operator.gt, a, b) self.assertRaises(RecursionError, operator.gt, a, b)
self.assertRaises(RuntimeError, operator.ge, a, b) self.assertRaises(RecursionError, operator.ge, a, b)
a.append(17) a.append(17)
self.assertRaises(RuntimeError, operator.eq, a, b) self.assertRaises(RecursionError, operator.eq, a, b)
self.assertRaises(RuntimeError, operator.ne, a, b) self.assertRaises(RecursionError, operator.ne, a, b)
a.insert(0, 11) a.insert(0, 11)
b.insert(0, 12) b.insert(0, 12)
self.assertTrue(not (a == b)) self.assertTrue(not (a == b))

View File

@ -673,7 +673,7 @@ class RunPathTestCase(unittest.TestCase, CodeExecutionMixin):
script_name = self._make_test_script(script_dir, mod_name, source) script_name = self._make_test_script(script_dir, mod_name, source)
zip_name, fname = make_zip_script(script_dir, 'test_zip', script_name) zip_name, fname = make_zip_script(script_dir, 'test_zip', script_name)
msg = "recursion depth exceeded" msg = "recursion depth exceeded"
self.assertRaisesRegex(RuntimeError, msg, run_path, zip_name) self.assertRaisesRegex(RecursionError, msg, run_path, zip_name)
def test_encoding(self): def test_encoding(self):
with temp_dir() as script_dir: with temp_dir() as script_dir:

View File

@ -211,8 +211,8 @@ class SysModuleTest(unittest.TestCase):
for i in (50, 1000): for i in (50, 1000):
# Issue #5392: stack overflow after hitting recursion limit twice # Issue #5392: stack overflow after hitting recursion limit twice
sys.setrecursionlimit(i) sys.setrecursionlimit(i)
self.assertRaises(RuntimeError, f) self.assertRaises(RecursionError, f)
self.assertRaises(RuntimeError, f) self.assertRaises(RecursionError, f)
finally: finally:
sys.setrecursionlimit(oldlimit) sys.setrecursionlimit(oldlimit)
@ -225,7 +225,7 @@ class SysModuleTest(unittest.TestCase):
def f(): def f():
try: try:
f() f()
except RuntimeError: except RecursionError:
f() f()
sys.setrecursionlimit(%d) sys.setrecursionlimit(%d)

View File

@ -945,7 +945,7 @@ class ThreadingExceptionTests(BaseTestCase):
def outer(): def outer():
try: try:
recurse() recurse()
except RuntimeError: except RecursionError:
pass pass
w = threading.Thread(target=outer) w = threading.Thread(target=outer)

View File

@ -30,6 +30,8 @@ Core and Builtins
- Issue #24450: Add gi_yieldfrom to generators and cr_await to coroutines. - Issue #24450: Add gi_yieldfrom to generators and cr_await to coroutines.
Contributed by Benno Leslie and Yury Selivanov. Contributed by Benno Leslie and Yury Selivanov.
- Issue #19235: Add new RecursionError exception. Patch by Georg Brandl.
Library Library
------- -------

View File

@ -3622,7 +3622,7 @@ save_reduce(PicklerObject *self, PyObject *args, PyObject *obj)
>>> pickle.dumps(1+2j) >>> pickle.dumps(1+2j)
Traceback (most recent call last): 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 Removing the complex class from copyreg.dispatch_table made the
__reduce_ex__() method emit another complex object: __reduce_ex__() method emit another complex object:

View File

@ -500,8 +500,9 @@ pattern_error(Py_ssize_t status)
{ {
switch (status) { switch (status) {
case SRE_ERROR_RECURSION_LIMIT: case SRE_ERROR_RECURSION_LIMIT:
/* This error code seems to be unused. */
PyErr_SetString( PyErr_SetString(
PyExc_RuntimeError, PyExc_RecursionError,
"maximum recursion limit exceeded" "maximum recursion limit exceeded"
); );
break; break;

View File

@ -1231,6 +1231,11 @@ SimpleExtendsException(PyExc_Exception, EOFError,
SimpleExtendsException(PyExc_Exception, RuntimeError, SimpleExtendsException(PyExc_Exception, RuntimeError,
"Unspecified run-time error."); "Unspecified run-time error.");
/*
* RecursionError extends RuntimeError
*/
SimpleExtendsException(PyExc_RuntimeError, RecursionError,
"Recursion limit exceeded.");
/* /*
* NotImplementedError extends RuntimeError * 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 Meant to be used when normalizing the exception for exceeding the recursion
depth will cause its own infinite recursion. depth will cause its own infinite recursion.
*/ */
@ -2484,6 +2489,7 @@ _PyExc_Init(PyObject *bltinmod)
PRE_INIT(OSError) PRE_INIT(OSError)
PRE_INIT(EOFError) PRE_INIT(EOFError)
PRE_INIT(RuntimeError) PRE_INIT(RuntimeError)
PRE_INIT(RecursionError)
PRE_INIT(NotImplementedError) PRE_INIT(NotImplementedError)
PRE_INIT(NameError) PRE_INIT(NameError)
PRE_INIT(UnboundLocalError) PRE_INIT(UnboundLocalError)
@ -2560,6 +2566,7 @@ _PyExc_Init(PyObject *bltinmod)
#endif #endif
POST_INIT(EOFError) POST_INIT(EOFError)
POST_INIT(RuntimeError) POST_INIT(RuntimeError)
POST_INIT(RecursionError)
POST_INIT(NotImplementedError) POST_INIT(NotImplementedError)
POST_INIT(NameError) POST_INIT(NameError)
POST_INIT(UnboundLocalError) POST_INIT(UnboundLocalError)
@ -2643,9 +2650,9 @@ _PyExc_Init(PyObject *bltinmod)
preallocate_memerrors(); preallocate_memerrors();
if (!PyExc_RecursionErrorInst) { if (!PyExc_RecursionErrorInst) {
PyExc_RecursionErrorInst = BaseException_new(&_PyExc_RuntimeError, NULL, NULL); PyExc_RecursionErrorInst = BaseException_new(&_PyExc_RecursionError, NULL, NULL);
if (!PyExc_RecursionErrorInst) if (!PyExc_RecursionErrorInst)
Py_FatalError("Cannot pre-allocate RuntimeError instance for " Py_FatalError("Cannot pre-allocate RecursionError instance for "
"recursion errors"); "recursion errors");
else { else {
PyBaseExceptionObject *err_inst = PyBaseExceptionObject *err_inst =
@ -2654,15 +2661,15 @@ _PyExc_Init(PyObject *bltinmod)
PyObject *exc_message; PyObject *exc_message;
exc_message = PyUnicode_FromString("maximum recursion depth exceeded"); exc_message = PyUnicode_FromString("maximum recursion depth exceeded");
if (!exc_message) if (!exc_message)
Py_FatalError("cannot allocate argument for RuntimeError " Py_FatalError("cannot allocate argument for RecursionError "
"pre-allocation"); "pre-allocation");
args_tuple = PyTuple_Pack(1, exc_message); args_tuple = PyTuple_Pack(1, exc_message);
if (!args_tuple) if (!args_tuple)
Py_FatalError("cannot allocate tuple for RuntimeError " Py_FatalError("cannot allocate tuple for RecursionError "
"pre-allocation"); "pre-allocation");
Py_DECREF(exc_message); Py_DECREF(exc_message);
if (BaseException_init(err_inst, args_tuple, NULL)) 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); Py_DECREF(args_tuple);
} }
} }

View File

@ -4142,7 +4142,7 @@ reduce_newobj(PyObject *obj, int proto)
* were implemented in the same function: * were implemented in the same function:
* - trying to pickle an object with a custom __reduce__ method that * - trying to pickle an object with a custom __reduce__ method that
* fell back to object.__reduce__ in certain circumstances led to * 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 * - Pickling objects that lied about their type by overwriting the
* __class__ descriptor could lead to infinite recursion at C level * __class__ descriptor could lead to infinite recursion at C level
* and eventual segfault. * and eventual segfault.

View File

@ -737,7 +737,7 @@ _Py_CheckRecursiveCall(const char *where)
if (tstate->recursion_depth > recursion_limit) { if (tstate->recursion_depth > recursion_limit) {
--tstate->recursion_depth; --tstate->recursion_depth;
tstate->overflowed = 1; tstate->overflowed = 1;
PyErr_Format(PyExc_RuntimeError, PyErr_Format(PyExc_RecursionError,
"maximum recursion depth exceeded%s", "maximum recursion depth exceeded%s",
where); where);
return -1; return -1;

View File

@ -319,7 +319,7 @@ finally:
Py_DECREF(*exc); Py_DECREF(*exc);
Py_DECREF(*val); Py_DECREF(*val);
/* ... and use the recursion error instead */ /* ... and use the recursion error instead */
*exc = PyExc_RuntimeError; *exc = PyExc_RecursionError;
*val = PyExc_RecursionErrorInst; *val = PyExc_RecursionErrorInst;
Py_INCREF(*exc); Py_INCREF(*exc);
Py_INCREF(*val); Py_INCREF(*val);

View File

@ -1135,7 +1135,7 @@ static int
symtable_visit_stmt(struct symtable *st, stmt_ty s) symtable_visit_stmt(struct symtable *st, stmt_ty s)
{ {
if (++st->recursion_depth > st->recursion_limit) { if (++st->recursion_depth > st->recursion_limit) {
PyErr_SetString(PyExc_RuntimeError, PyErr_SetString(PyExc_RecursionError,
"maximum recursion depth exceeded during compilation"); "maximum recursion depth exceeded during compilation");
VISIT_QUIT(st, 0); VISIT_QUIT(st, 0);
} }
@ -1357,7 +1357,7 @@ static int
symtable_visit_expr(struct symtable *st, expr_ty e) symtable_visit_expr(struct symtable *st, expr_ty e)
{ {
if (++st->recursion_depth > st->recursion_limit) { if (++st->recursion_depth > st->recursion_limit) {
PyErr_SetString(PyExc_RuntimeError, PyErr_SetString(PyExc_RecursionError,
"maximum recursion depth exceeded during compilation"); "maximum recursion depth exceeded during compilation");
VISIT_QUIT(st, 0); VISIT_QUIT(st, 0);
} }

View File

@ -92,7 +92,7 @@ def test_cpickle(_cache={}):
def test_compiler_recursion(): def test_compiler_recursion():
# The compiler uses a scaling factor to support additional levels # The compiler uses a scaling factor to support additional levels
# of recursion. This is a sanity check of that scaling to ensure # 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") compile("()" * (10 * sys.getrecursionlimit()), "<single>", "single")
def check_limit(n, test_func_name): 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() # AttributeError can be raised because of the way e.g. PyDict_GetItem()
# silences all exceptions and returns NULL, which is usually interpreted # silences all exceptions and returns NULL, which is usually interpreted
# as "missing attribute". # as "missing attribute".
except (RuntimeError, AttributeError): except (RecursionError, AttributeError):
pass pass
else: else:
print("Yikes!") print("Yikes!")