Skip to content

Commit a5af6e1

Browse files
bpo-25455: Fixed crashes in repr of recursive buffered file-like objects. (#514)
1 parent 77ed115 commit a5af6e1

File tree

6 files changed

+74
-9
lines changed

6 files changed

+74
-9
lines changed

Lib/test/test_fileio.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from functools import wraps
1111

1212
from test.support import (TESTFN, TESTFN_UNICODE, check_warnings, run_unittest,
13-
make_bad_fd, cpython_only)
13+
make_bad_fd, cpython_only, swap_attr)
1414
from collections import UserList
1515

1616
import _io # C implementation of io
@@ -176,6 +176,12 @@ def testReprNoCloseFD(self):
176176
finally:
177177
os.close(fd)
178178

179+
def testRecursiveRepr(self):
180+
# Issue #25455
181+
with swap_attr(self.f, 'name', self.f):
182+
with self.assertRaises(RuntimeError):
183+
repr(self.f) # Should not crash
184+
179185
def testErrors(self):
180186
f = self.f
181187
self.assertFalse(f.isatty())

Lib/test/test_io.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,6 +1014,16 @@ def test_repr(self):
10141014
raw.name = b"dummy"
10151015
self.assertEqual(repr(b), "<%s name=b'dummy'>" % clsname)
10161016

1017+
def test_recursive_repr(self):
1018+
# Issue #25455
1019+
raw = self.MockRawIO()
1020+
b = self.tp(raw)
1021+
with support.swap_attr(raw, 'name', b):
1022+
try:
1023+
repr(b) # Should not crash
1024+
except RuntimeError:
1025+
pass
1026+
10171027
def test_flush_error_on_close(self):
10181028
# Test that buffered file is closed despite failed flush
10191029
# and that flush() is called before file closed.
@@ -2435,6 +2445,16 @@ def test_repr(self):
24352445
t.buffer.detach()
24362446
repr(t) # Should not raise an exception
24372447

2448+
def test_recursive_repr(self):
2449+
# Issue #25455
2450+
raw = self.BytesIO()
2451+
t = self.TextIOWrapper(raw)
2452+
with support.swap_attr(raw, 'name', t):
2453+
try:
2454+
repr(t) # Should not crash
2455+
except RuntimeError:
2456+
pass
2457+
24382458
def test_line_buffering(self):
24392459
r = self.BytesIO()
24402460
b = self.BufferedWriter(r, 1000)

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,8 @@ Extension Modules
281281
Library
282282
-------
283283

284+
- bpo-25455: Fixed crashes in repr of recursive buffered file-like objects.
285+
284286
- bpo-29800: Fix crashes in partial.__repr__ if the keys of partial.keywords
285287
are not strings. Patch by Michael Seifert.
286288

Modules/_io/bufferedio.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,8 +1415,18 @@ buffered_repr(buffered *self)
14151415
res = PyUnicode_FromFormat("<%s>", Py_TYPE(self)->tp_name);
14161416
}
14171417
else {
1418-
res = PyUnicode_FromFormat("<%s name=%R>",
1419-
Py_TYPE(self)->tp_name, nameobj);
1418+
int status = Py_ReprEnter((PyObject *)self);
1419+
res = NULL;
1420+
if (status == 0) {
1421+
res = PyUnicode_FromFormat("<%s name=%R>",
1422+
Py_TYPE(self)->tp_name, nameobj);
1423+
Py_ReprLeave((PyObject *)self);
1424+
}
1425+
else if (status > 0) {
1426+
PyErr_Format(PyExc_RuntimeError,
1427+
"reentrant call inside %s.__repr__",
1428+
Py_TYPE(self)->tp_name);
1429+
}
14201430
Py_DECREF(nameobj);
14211431
}
14221432
return res;

Modules/_io/fileio.c

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,9 +1082,19 @@ fileio_repr(fileio *self)
10821082
self->fd, mode_string(self), self->closefd ? "True" : "False");
10831083
}
10841084
else {
1085-
res = PyUnicode_FromFormat(
1086-
"<_io.FileIO name=%R mode='%s' closefd=%s>",
1087-
nameobj, mode_string(self), self->closefd ? "True" : "False");
1085+
int status = Py_ReprEnter((PyObject *)self);
1086+
res = NULL;
1087+
if (status == 0) {
1088+
res = PyUnicode_FromFormat(
1089+
"<_io.FileIO name=%R mode='%s' closefd=%s>",
1090+
nameobj, mode_string(self), self->closefd ? "True" : "False");
1091+
Py_ReprLeave((PyObject *)self);
1092+
}
1093+
else if (status > 0) {
1094+
PyErr_Format(PyExc_RuntimeError,
1095+
"reentrant call inside %s.__repr__",
1096+
Py_TYPE(self)->tp_name);
1097+
}
10881098
Py_DECREF(nameobj);
10891099
}
10901100
return res;

Modules/_io/textio.c

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2483,13 +2483,23 @@ static PyObject *
24832483
textiowrapper_repr(textio *self)
24842484
{
24852485
PyObject *nameobj, *modeobj, *res, *s;
2486+
int status;
24862487

24872488
CHECK_INITIALIZED(self);
24882489

24892490
res = PyUnicode_FromString("<_io.TextIOWrapper");
24902491
if (res == NULL)
24912492
return NULL;
24922493

2494+
status = Py_ReprEnter((PyObject *)self);
2495+
if (status != 0) {
2496+
if (status > 0) {
2497+
PyErr_Format(PyExc_RuntimeError,
2498+
"reentrant call inside %s.__repr__",
2499+
Py_TYPE(self)->tp_name);
2500+
}
2501+
goto error;
2502+
}
24932503
nameobj = _PyObject_GetAttrId((PyObject *) self, &PyId_name);
24942504
if (nameobj == NULL) {
24952505
if (PyErr_ExceptionMatches(PyExc_Exception))
@@ -2504,7 +2514,7 @@ textiowrapper_repr(textio *self)
25042514
goto error;
25052515
PyUnicode_AppendAndDel(&res, s);
25062516
if (res == NULL)
2507-
return NULL;
2517+
goto error;
25082518
}
25092519
modeobj = _PyObject_GetAttrId((PyObject *) self, &PyId_mode);
25102520
if (modeobj == NULL) {
@@ -2520,14 +2530,21 @@ textiowrapper_repr(textio *self)
25202530
goto error;
25212531
PyUnicode_AppendAndDel(&res, s);
25222532
if (res == NULL)
2523-
return NULL;
2533+
goto error;
25242534
}
25252535
s = PyUnicode_FromFormat("%U encoding=%R>",
25262536
res, self->encoding);
25272537
Py_DECREF(res);
2538+
if (status == 0) {
2539+
Py_ReprLeave((PyObject *)self);
2540+
}
25282541
return s;
2529-
error:
2542+
2543+
error:
25302544
Py_XDECREF(res);
2545+
if (status == 0) {
2546+
Py_ReprLeave((PyObject *)self);
2547+
}
25312548
return NULL;
25322549
}
25332550

0 commit comments

Comments
 (0)