Skip to content

Commit 1fb72d2

Browse files
bpo-32137: The repr of deeply nested dict now raises a RecursionError (#4570)
instead of crashing due to a stack overflow. This perhaps will fix similar problems in other extension types.
1 parent eea3cc1 commit 1fb72d2

File tree

7 files changed

+26
-9
lines changed

7 files changed

+26
-9
lines changed

Lib/test/list_tests.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,11 @@ def test_repr(self):
5353
self.assertEqual(str(a2), "[0, 1, 2, [...], 3]")
5454
self.assertEqual(repr(a2), "[0, 1, 2, [...], 3]")
5555

56-
l0 = []
56+
def test_repr_deep(self):
57+
a = self.type2test([])
5758
for i in range(sys.getrecursionlimit() + 100):
58-
l0 = [l0]
59-
self.assertRaises(RecursionError, repr, l0)
59+
a = self.type2test([a])
60+
self.assertRaises(RecursionError, repr, a)
6061

6162
def test_print(self):
6263
d = self.type2test(range(200))

Lib/test/mapping_tests.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# tests common to dict and UserDict
22
import unittest
33
import collections
4+
import sys
45

56

67
class BasicTestMappingProtocol(unittest.TestCase):
@@ -619,6 +620,14 @@ def __repr__(self):
619620
d = self._full_mapping({1: BadRepr()})
620621
self.assertRaises(Exc, repr, d)
621622

623+
def test_repr_deep(self):
624+
d = self._empty_mapping()
625+
for i in range(sys.getrecursionlimit() + 100):
626+
d0 = d
627+
d = self._empty_mapping()
628+
d[1] = d0
629+
self.assertRaises(RecursionError, repr, d)
630+
622631
def test_eq(self):
623632
self.assertEqual(self._empty_mapping(), self._empty_mapping())
624633
self.assertEqual(self._full_mapping({1: 2}),

Lib/test/test_dict.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,12 @@ def __repr__(self):
468468
d = {1: BadRepr()}
469469
self.assertRaises(Exc, repr, d)
470470

471+
def test_repr_deep(self):
472+
d = {}
473+
for i in range(sys.getrecursionlimit() + 100):
474+
d = {1: d}
475+
self.assertRaises(RecursionError, repr, d)
476+
471477
def test_eq(self):
472478
self.assertEqual({}, {})
473479
self.assertEqual({1: 2}, {1: 2})
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The repr of deeply nested dict now raises a RecursionError instead of
2+
crashing due to a stack overflow.

Objects/listobject.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -364,10 +364,7 @@ list_repr(PyListObject *v)
364364
goto error;
365365
}
366366

367-
if (Py_EnterRecursiveCall(" while getting the repr of a list"))
368-
goto error;
369367
s = PyObject_Repr(v->ob_item[i]);
370-
Py_LeaveRecursiveCall();
371368
if (s == NULL)
372369
goto error;
373370

Objects/object.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,12 @@ PyObject_Repr(PyObject *v)
463463
assert(!PyErr_Occurred());
464464
#endif
465465

466+
/* It is possible for a type to have a tp_repr representation that loops
467+
infinitely. */
468+
if (Py_EnterRecursiveCall(" while getting the repr of an object"))
469+
return NULL;
466470
res = (*v->ob_type->tp_repr)(v);
471+
Py_LeaveRecursiveCall();
467472
if (res == NULL)
468473
return NULL;
469474
if (!PyUnicode_Check(res)) {

Objects/tupleobject.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -303,10 +303,7 @@ tuplerepr(PyTupleObject *v)
303303
goto error;
304304
}
305305

306-
if (Py_EnterRecursiveCall(" while getting the repr of a tuple"))
307-
goto error;
308306
s = PyObject_Repr(v->ob_item[i]);
309-
Py_LeaveRecursiveCall();
310307
if (s == NULL)
311308
goto error;
312309

0 commit comments

Comments
 (0)