From e6fb522465f554efb55285e49514940ded7491c9 Mon Sep 17 00:00:00 2001 From: Saiyam Jain Date: Mon, 26 Oct 2026 02:44:26 +0530 Subject: [PATCH 1/2] gh-160309: Fix StreamReader.read() silently dropping incomplete final character When a byte stream ends with an incomplete multibyte sequence (e.g. a truncated UTF-8 character), StreamReader.read() would silently discard those bytes: the read loop exited on empty newdata while bytebuffer still held the fragment, and bytebuffer was never decoded. Fix by detecting leftover bytes in bytebuffer when the underlying stream is exhausted and decoding them immediately. The codec's stateless decode function (e.g. utf_8_decode with final=True) then applies the error handler: 'strict' raises UnicodeDecodeError, 'replace' emits the replacement character, and 'ignore' drops the bytes - matching the behaviour of bytes.decode() and io.TextIOWrapper. --- Lib/codecs.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Lib/codecs.py b/Lib/codecs.py index af6ab031157e790..eaa5da00630cd54 100644 --- a/Lib/codecs.py +++ b/Lib/codecs.py @@ -525,6 +525,15 @@ def read(self, size=-1, chars=-1, firstline=False): self.charbuffer += newchars # there was no data available if not newdata: + # The stream is exhausted. If there are leftover bytes in + # bytebuffer, they form an incomplete character. Decode them + # now so that the error handler is applied (strict raises, + # replace emits the replacement character, ignore drops them). + if self.bytebuffer: + newchars, decodedbytes = self.decode( + self.bytebuffer, self.errors) + self.bytebuffer = self.bytebuffer[decodedbytes:] + self.charbuffer += newchars break if chars < 0: # Return everything we've got From 51b3fda87983efa6fe209508bae24bb9d19b09b0 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 21:18:29 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2026-10-25-21-18-27.gh-issue-160309.wJyl0Q.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2026-10-25-21-18-27.gh-issue-160309.wJyl0Q.rst diff --git a/Misc/NEWS.d/next/Library/2026-10-25-21-18-27.gh-issue-160309.wJyl0Q.rst b/Misc/NEWS.d/next/Library/2026-10-25-21-18-27.gh-issue-160309.wJyl0Q.rst new file mode 100644 index 000000000000000..0c34090b71e44c1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-25-21-18-27.gh-issue-160309.wJyl0Q.rst @@ -0,0 +1 @@ +Fix codecs.StreamReader.read() silently dropping an incomplete character at the end of the stream.