Skip to content

Commit 688b6de

Browse files
miss-islingtonserhiy-storchaka
authored andcommitted
bpo-32137: The repr of deeply nested dict now raises a RecursionError (GH-4570) (GH-4689)
instead of crashing due to a stack overflow. This perhaps will fix similar problems in other extension types. (cherry picked from commit 1fb72d2)
1 parent 581ce25 commit 688b6de

7 files changed

Lines changed: 26 additions & 9 deletions

File tree

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
@@ -366,10 +366,7 @@ list_repr(PyListObject *v)
366366
goto error;
367367
}
368368

369-
if (Py_EnterRecursiveCall(" while getting the repr of a list"))
370-
goto error;
371369
s = PyObject_Repr(v->ob_item[i]);
372-
Py_LeaveRecursiveCall();
373370
if (s == NULL)
374371
goto error;
375372

Objects/object.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,12 @@ PyObject_Repr(PyObject *v)
482482
assert(!PyErr_Occurred());
483483
#endif
484484

485+
/* It is possible for a type to have a tp_repr representation that loops
486+
infinitely. */
487+
if (Py_EnterRecursiveCall(" while getting the repr of an object"))
488+
return NULL;
485489
res = (*v->ob_type->tp_repr)(v);
490+
Py_LeaveRecursiveCall();
486491
if (res == NULL)
487492
return NULL;
488493
if (!PyUnicode_Check(res)) {

Objects/tupleobject.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -300,10 +300,7 @@ tuplerepr(PyTupleObject *v)
300300
goto error;
301301
}
302302

303-
if (Py_EnterRecursiveCall(" while getting the repr of a tuple"))
304-
goto error;
305303
s = PyObject_Repr(v->ob_item[i]);
306-
Py_LeaveRecursiveCall();
307304
if (s == NULL)
308305
goto error;
309306

0 commit comments

Comments
 (0)