Skip to content

Commit b7a2c17

Browse files
[2.7] bpo-32137: The repr of deeply nested dict now raises a RuntimeError (GH-4570) (#5493)
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 b60f43a commit b7a2c17

7 files changed

Lines changed: 28 additions & 10 deletions

File tree

Lib/test/list_tests.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,11 @@ def test_repr(self):
4545
self.assertEqual(str(a2), "[0, 1, 2, [...], 3]")
4646
self.assertEqual(repr(a2), "[0, 1, 2, [...], 3]")
4747

48-
l0 = []
49-
for i in xrange(sys.getrecursionlimit() + 100):
50-
l0 = [l0]
51-
self.assertRaises(RuntimeError, repr, l0)
48+
def test_repr_deep(self):
49+
a = self.type2test([])
50+
for i in range(sys.getrecursionlimit() + 100):
51+
a = self.type2test([a])
52+
self.assertRaises(RuntimeError, repr, a)
5253

5354
def test_print(self):
5455
d = self.type2test(xrange(200))

Lib/test/mapping_tests.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import unittest
33
import UserDict
44
import test_support
5+
import sys
56

67

78
class BasicTestMappingProtocol(unittest.TestCase):
@@ -645,6 +646,14 @@ def __repr__(self):
645646
d = self._full_mapping({1: BadRepr()})
646647
self.assertRaises(Exc, repr, d)
647648

649+
def test_repr_deep(self):
650+
d = self._empty_mapping()
651+
for i in range(sys.getrecursionlimit() + 100):
652+
d0 = d
653+
d = self._empty_mapping()
654+
d[1] = d0
655+
self.assertRaises(RuntimeError, repr, d)
656+
648657
def test_le(self):
649658
self.assertTrue(not (self._empty_mapping() < self._empty_mapping()))
650659
self.assertTrue(not (self._full_mapping({1: 2}) < self._full_mapping({1L: 2L})))

Lib/test/test_dict.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import UserDict, random, string
55
import gc, weakref
6+
import sys
67

78

89
class DictTest(unittest.TestCase):
@@ -422,6 +423,12 @@ def __repr__(self):
422423
d = {1: BadRepr()}
423424
self.assertRaises(Exc, repr, d)
424425

426+
def test_repr_deep(self):
427+
d = {}
428+
for i in range(sys.getrecursionlimit() + 100):
429+
d = {1: d}
430+
self.assertRaises(RuntimeError, repr, d)
431+
425432
def test_le(self):
426433
self.assertFalse({} < {})
427434
self.assertFalse({1: 2} < {1L: 2L})
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
@@ -383,10 +383,7 @@ list_repr(PyListObject *v)
383383
so must refetch the list size on each iteration. */
384384
for (i = 0; i < Py_SIZE(v); ++i) {
385385
int status;
386-
if (Py_EnterRecursiveCall(" while getting the repr of a list"))
387-
goto Done;
388386
s = PyObject_Repr(v->ob_item[i]);
389-
Py_LeaveRecursiveCall();
390387
if (s == NULL)
391388
goto Done;
392389
status = PyList_Append(pieces, s);

Objects/object.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,12 @@ PyObject_Repr(PyObject *v)
378378
Py_TYPE(v)->tp_name, v);
379379
else {
380380
PyObject *res;
381+
/* It is possible for a type to have a tp_repr representation that
382+
loops infinitely. */
383+
if (Py_EnterRecursiveCall(" while getting the repr of an object"))
384+
return NULL;
381385
res = (*Py_TYPE(v)->tp_repr)(v);
386+
Py_LeaveRecursiveCall();
382387
if (res == NULL)
383388
return NULL;
384389
#ifdef Py_USING_UNICODE

Objects/tupleobject.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -288,10 +288,7 @@ tuplerepr(PyTupleObject *v)
288288

289289
/* Do repr() on each element. */
290290
for (i = 0; i < n; ++i) {
291-
if (Py_EnterRecursiveCall(" while getting the repr of a tuple"))
292-
goto Done;
293291
s = PyObject_Repr(v->ob_item[i]);
294-
Py_LeaveRecursiveCall();
295292
if (s == NULL)
296293
goto Done;
297294
PyTuple_SET_ITEM(pieces, i, s);

0 commit comments

Comments
 (0)