Skip to content

Commit 08612ed

Browse files
bpo-25455: Fixed crashes in repr of recursive buffered file-like objects. (#514) (#727)
(cherry picked from commit a5af6e1)
1 parent bb67f10 commit 08612ed

6 files changed

Lines changed: 75 additions & 9 deletions

File tree

Lib/test/test_fileio.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
from weakref import proxy
1010
from functools import wraps
1111

12-
from test.support import TESTFN, check_warnings, run_unittest, make_bad_fd, cpython_only
12+
from test.support import (TESTFN, check_warnings, run_unittest,
13+
make_bad_fd, cpython_only, swap_attr)
1314
from collections import UserList
1415

1516
import _io # C implementation of io
@@ -175,6 +176,12 @@ def testReprNoCloseFD(self):
175176
finally:
176177
os.close(fd)
177178

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+
178185
def testErrors(self):
179186
f = self.f
180187
self.assertFalse(f.isatty())

Lib/test/test_io.py

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

984+
def test_recursive_repr(self):
985+
# Issue #25455
986+
raw = self.MockRawIO()
987+
b = self.tp(raw)
988+
with support.swap_attr(raw, 'name', b):
989+
try:
990+
repr(b) # Should not crash
991+
except RuntimeError:
992+
pass
993+
984994
def test_flush_error_on_close(self):
985995
# Test that buffered file is closed despite failed flush
986996
# and that flush() is called before file closed.
@@ -2391,6 +2401,16 @@ def test_repr(self):
23912401
t.buffer.detach()
23922402
repr(t) # Should not raise an exception
23932403

2404+
def test_recursive_repr(self):
2405+
# Issue #25455
2406+
raw = self.BytesIO()
2407+
t = self.TextIOWrapper(raw)
2408+
with support.swap_attr(raw, 'name', t):
2409+
try:
2410+
repr(t) # Should not crash
2411+
except RuntimeError:
2412+
pass
2413+
23942414
def test_line_buffering(self):
23952415
r = self.BytesIO()
23962416
b = self.BufferedWriter(r, 1000)

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ Extension Modules
4343
Library
4444
-------
4545

46+
- bpo-25455: Fixed crashes in repr of recursive buffered file-like objects.
47+
4648
- bpo-29800: Fix crashes in partial.__repr__ if the keys of partial.keywords
4749
are not strings. Patch by Michael Seifert.
4850

Modules/_io/bufferedio.c

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

Modules/_io/fileio.c

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,9 +1093,19 @@ fileio_repr(fileio *self)
10931093
self->fd, mode_string(self), self->closefd ? "True" : "False");
10941094
}
10951095
else {
1096-
res = PyUnicode_FromFormat(
1097-
"<_io.FileIO name=%R mode='%s' closefd=%s>",
1098-
nameobj, mode_string(self), self->closefd ? "True" : "False");
1096+
int status = Py_ReprEnter((PyObject *)self);
1097+
res = NULL;
1098+
if (status == 0) {
1099+
res = PyUnicode_FromFormat(
1100+
"<_io.FileIO name=%R mode='%s' closefd=%s>",
1101+
nameobj, mode_string(self), self->closefd ? "True" : "False");
1102+
Py_ReprLeave((PyObject *)self);
1103+
}
1104+
else if (status > 0) {
1105+
PyErr_Format(PyExc_RuntimeError,
1106+
"reentrant call inside %s.__repr__",
1107+
Py_TYPE(self)->tp_name);
1108+
}
10991109
Py_DECREF(nameobj);
11001110
}
11011111
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)