Skip to content

Commit 1d25b75

Browse files
authored
pythongh-140650: Fix write(), flush() and close() methods of io.BufferedWriter (pythonGH-140653)
They could raise SystemError or crash when getting the "closed" attribute or converting it to boolean raises an exception.
1 parent 3cb1ab0 commit 1d25b75

File tree

3 files changed

+43
-6
lines changed

3 files changed

+43
-6
lines changed

Lib/test/test_io/test_bufferedio.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,27 @@ def test_args_error(self):
962962
with self.assertRaisesRegex(TypeError, "BufferedWriter"):
963963
self.tp(self.BytesIO(), 1024, 1024, 1024)
964964

965+
def test_non_boolean_closed_attr(self):
966+
# gh-140650: check TypeError is raised
967+
class MockRawIOWithoutClosed(self.MockRawIO):
968+
closed = NotImplemented
969+
970+
bufio = self.tp(MockRawIOWithoutClosed())
971+
self.assertRaises(TypeError, bufio.write, b"")
972+
self.assertRaises(TypeError, bufio.flush)
973+
self.assertRaises(TypeError, bufio.close)
974+
975+
def test_closed_attr_raises(self):
976+
class MockRawIOClosedRaises(self.MockRawIO):
977+
@property
978+
def closed(self):
979+
raise ValueError("test")
980+
981+
bufio = self.tp(MockRawIOClosedRaises())
982+
self.assertRaisesRegex(ValueError, "test", bufio.write, b"")
983+
self.assertRaisesRegex(ValueError, "test", bufio.flush)
984+
self.assertRaisesRegex(ValueError, "test", bufio.close)
985+
965986

966987
class PyBufferedWriterTest(BufferedWriterTest, PyTestCase):
967988
tp = pyio.BufferedWriter
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix an issue where closing :class:`io.BufferedWriter` could crash if
2+
the closed attribute raised an exception on access or could not be
3+
converted to a boolean.

Modules/_io/bufferedio.c

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -362,16 +362,24 @@ _enter_buffered_busy(buffered *self)
362362
}
363363

364364
#define IS_CLOSED(self) \
365-
(!self->buffer || \
365+
(!self->buffer ? 1 : \
366366
(self->fast_closed_checks \
367367
? _PyFileIO_closed(self->raw) \
368368
: buffered_closed(self)))
369369

370370
#define CHECK_CLOSED(self, error_msg) \
371-
if (IS_CLOSED(self) && (Py_SAFE_DOWNCAST(READAHEAD(self), Py_off_t, Py_ssize_t) == 0)) { \
372-
PyErr_SetString(PyExc_ValueError, error_msg); \
373-
return NULL; \
374-
} \
371+
do { \
372+
int _closed = IS_CLOSED(self); \
373+
if (_closed < 0) { \
374+
return NULL; \
375+
} \
376+
if (_closed && \
377+
(Py_SAFE_DOWNCAST(READAHEAD(self), Py_off_t, Py_ssize_t) == 0)) \
378+
{ \
379+
PyErr_SetString(PyExc_ValueError, error_msg); \
380+
return NULL; \
381+
} \
382+
} while (0);
375383

376384
#define VALID_READ_BUFFER(self) \
377385
(self->readable && self->read_end != -1)
@@ -2079,6 +2087,7 @@ _io_BufferedWriter_write_impl(buffered *self, Py_buffer *buffer)
20792087
PyObject *res = NULL;
20802088
Py_ssize_t written, avail, remaining;
20812089
Py_off_t offset;
2090+
int r;
20822091

20832092
CHECK_INITIALIZED(self)
20842093

@@ -2087,7 +2096,11 @@ _io_BufferedWriter_write_impl(buffered *self, Py_buffer *buffer)
20872096

20882097
/* Issue #31976: Check for closed file after acquiring the lock. Another
20892098
thread could be holding the lock while closing the file. */
2090-
if (IS_CLOSED(self)) {
2099+
r = IS_CLOSED(self);
2100+
if (r < 0) {
2101+
goto error;
2102+
}
2103+
if (r > 0) {
20912104
PyErr_SetString(PyExc_ValueError, "write to closed file");
20922105
goto error;
20932106
}

0 commit comments

Comments
 (0)