From ad2d5ec97e12063b7265faa8b0654bcf82a33545 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Mon, 8 Jan 2024 12:00:30 +0100 Subject: [3.12] gh-80109: Fix io.TextIOWrapper dropping the internal buffer during write() (GH-22535) (GH-113808) io.TextIOWrapper was dropping the internal decoding buffer during read() and write() calls. (cherry picked from commit 73c93265634257b1488262097e024c1727260cfd) Co-authored-by: Zackery Spytz --- Lib/_pyio.py | 10 ++++++---- Lib/test/test_io.py | 8 ++++++++ .../next/Library/2020-10-03-23-47-28.bpo-35928.E0iPAa.rst | 2 ++ Modules/_io/textio.c | 12 ++++++++---- 4 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2020-10-03-23-47-28.bpo-35928.E0iPAa.rst diff --git a/Lib/_pyio.py b/Lib/_pyio.py index 7f247ff..9641d43 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -2210,8 +2210,9 @@ class TextIOWrapper(TextIOBase): self.buffer.write(b) if self._line_buffering and (haslf or "\r" in s): self.flush() - self._set_decoded_chars('') - self._snapshot = None + if self._snapshot is not None: + self._set_decoded_chars('') + self._snapshot = None if self._decoder: self._decoder.reset() return length @@ -2525,8 +2526,9 @@ class TextIOWrapper(TextIOBase): # Read everything. result = (self._get_decoded_chars() + decoder.decode(self.buffer.read(), final=True)) - self._set_decoded_chars('') - self._snapshot = None + if self._snapshot is not None: + self._set_decoded_chars('') + self._snapshot = None return result else: # Keep reading chunks until we have size characters to return. diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 196b7d2..cceaed8 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -3891,6 +3891,14 @@ class TextIOWrapperTest(unittest.TestCase): t.write('x') t.tell() + def test_issue35928(self): + p = self.BufferedRWPair(self.BytesIO(b'foo\nbar\n'), self.BytesIO()) + f = self.TextIOWrapper(p) + res = f.readline() + self.assertEqual(res, 'foo\n') + f.write(res) + self.assertEqual(res + f.readline(), 'foo\nbar\n') + class MemviewBytesIO(io.BytesIO): '''A BytesIO object whose read method returns memoryviews diff --git a/Misc/NEWS.d/next/Library/2020-10-03-23-47-28.bpo-35928.E0iPAa.rst b/Misc/NEWS.d/next/Library/2020-10-03-23-47-28.bpo-35928.E0iPAa.rst new file mode 100644 index 0000000..c63e616 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-10-03-23-47-28.bpo-35928.E0iPAa.rst @@ -0,0 +1,2 @@ +:class:`io.TextIOWrapper` now correctly handles the decoding buffer after +``read()`` and ``write()``. diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c index efada7f..14dd19d 100644 --- a/Modules/_io/textio.c +++ b/Modules/_io/textio.c @@ -1767,8 +1767,10 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text) Py_DECREF(ret); } - textiowrapper_set_decoded_chars(self, NULL); - Py_CLEAR(self->snapshot); + if (self->snapshot != NULL) { + textiowrapper_set_decoded_chars(self, NULL); + Py_CLEAR(self->snapshot); + } if (self->decoder) { ret = PyObject_CallMethodNoArgs(self->decoder, &_Py_ID(reset)); @@ -2003,8 +2005,10 @@ _io_TextIOWrapper_read_impl(textio *self, Py_ssize_t n) if (result == NULL) goto fail; - textiowrapper_set_decoded_chars(self, NULL); - Py_CLEAR(self->snapshot); + if (self->snapshot != NULL) { + textiowrapper_set_decoded_chars(self, NULL); + Py_CLEAR(self->snapshot); + } return result; } else { -- cgit v0.12