From 5480675e0599fe1f6a94605e5055db8beb8732a1 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 20 Oct 2026 15:18:38 -0700 Subject: [PATCH] gh-159739: Respect readline limits after a short read Backport to 3.15 and 3.14. Both branches use a bytearray instead of PyBytesWriter; the test lives in Lib/test/test_io.py on 3.14. --- Lib/_pyio.py | 2 +- Lib/test/test_io/test_general.py | 16 ++++++++++++++++ ...026-10-20-00-00-00.gh-issue-159739.4c9783.rst | 2 ++ Modules/_io/iobase.c | 3 ++- 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-20-00-00-00.gh-issue-159739.4c9783.rst diff --git a/Lib/_pyio.py b/Lib/_pyio.py index cf4ef04f37d26c..370b518f1e5e5a 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -528,7 +528,7 @@ def nreadahead(): return 1 n = (readahead.find(b"\n") + 1) or len(readahead) if size >= 0: - n = min(n, size) + n = min(n, size - len(res)) return n else: def nreadahead(): diff --git a/Lib/test/test_io/test_general.py b/Lib/test/test_io/test_general.py index 085ed3ea6a95ee..d12c5535b263ef 100644 --- a/Lib/test/test_io/test_general.py +++ b/Lib/test/test_io/test_general.py @@ -319,6 +319,22 @@ def test_readline(self): with self.open(os_helper.TESTFN, "r", encoding="utf-8") as f: self.assertRaises(TypeError, f.readline, 5.3) + def test_readline_limit_with_short_read(self): + class Reader(self.IOBase): + data = b"abcdef" + + def peek(self, size): + return self.data + + def read(self, size): + data = self.data[:min(size, 2)] + self.data = self.data[len(data):] + return data + + reader = Reader() + self.assertEqual(reader.readline(3), b"abc") + self.assertEqual(reader.data, b"def") + def test_readline_nonsizeable(self): # Issue #30061 # Crash when readline() returns an object without __len__ diff --git a/Misc/NEWS.d/next/Library/2026-10-20-00-00-00.gh-issue-159739.4c9783.rst b/Misc/NEWS.d/next/Library/2026-10-20-00-00-00.gh-issue-159739.4c9783.rst new file mode 100644 index 00000000000000..b65ac4cede106b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-20-00-00-00.gh-issue-159739.4c9783.rst @@ -0,0 +1,2 @@ +Fix :meth:`io.IOBase.readline` returning more bytes than requested when a +stream supports :meth:`!peek` and returns short reads. diff --git a/Modules/_io/iobase.c b/Modules/_io/iobase.c index 5b9b9c856431b0..2b482687e85c5b 100644 --- a/Modules/_io/iobase.c +++ b/Modules/_io/iobase.c @@ -653,8 +653,9 @@ _io__IOBase_readline_impl(PyObject *self, Py_ssize_t limit) Py_ssize_t n = 0; const char *buf = PyBytes_AS_STRING(readahead); if (limit >= 0) { + Py_ssize_t remaining = limit - PyBytesWriter_GetSize(writer); do { - if (n >= PyBytes_GET_SIZE(readahead) || n >= limit) + if (n >= PyBytes_GET_SIZE(readahead) || n >= remaining) break; if (buf[n++] == '\n') break;