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.
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

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.
.. 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

View File

@ -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`.

View File

@ -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:

View File

@ -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

View File

@ -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;

View File

@ -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)

View File

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

View File

@ -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))

View File

@ -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().

View File

@ -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", "()")

View File

@ -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)

View File

@ -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):

View File

@ -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__":

View File

@ -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):

View File

@ -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,)

View File

@ -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)

View File

@ -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),

View File

@ -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))

View File

@ -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:

View File

@ -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)

View File

@ -945,7 +945,7 @@ class ThreadingExceptionTests(BaseTestCase):
def outer():
try:
recurse()
except RuntimeError:
except RecursionError:
pass
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.
Contributed by Benno Leslie and Yury Selivanov.
- Issue #19235: Add new RecursionError exception. Patch by Georg Brandl.
Library
-------

View File

@ -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:

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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;

View File

@ -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);

View File

@ -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);
}

View File

@ -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!")