Skip to content

gh-160309: Fix StreamReader.read() silently dropping an incomplete final character - #160642

Open
saiyam-jain-dev wants to merge 1 commit into
python:mainfrom
saiyam-jain-dev:gh-160309-codecs-streamreader-truncation
Open

saiyam-jain-dev wants to merge 1 commit into
python:mainfrom
saiyam-jain-dev:gh-160309-codecs-streamreader-truncation

Conversation

@saiyam-jain-dev

@saiyam-jain-dev saiyam-jain-dev commented Oct 25, 2026 •

Copy link
Copy Markdown

Summary

Fixes #160309

codecs.StreamReader.read() silently drops bytes when the stream ends with an incomplete multibyte sequence. The read loop exits on empty
ewdata while �ytebuffer still holds the trailing bytes, and those bytes are never decoded.

Bug demonstration (before fix)

`python

import codecs, io
reader = codecs.getreader('utf-8')(io.BytesIO(b'ab\xe2'))
reader.read()
'ab' # \xe2 is silently dropped — no error raised
`

Every other decoding path raises or applies the error handler:

`python

b'ab\xe2'.decode('utf-8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe2 in position 2: unexpected end of data
codecs.getreader('utf-8')(io.BytesIO(b'ab\xe2'), 'replace').read()
'ab' # should be 'ab\ufffd' — replacement character is also silently dropped
`

Fix

When the underlying stream is exhausted and �ytebuffer is non-empty, decode the leftover bytes immediately before breaking out of the loop. The codec's stateless decode function applies inal=True semantics, so:

  • errors='strict' → raises UnicodeDecodeError (correct)
  • errors='replace' → emits the replacement character \ufffd (correct)
  • errors='ignore' → drops the bytes (correct)

This matches the behaviour of �ytes.decode() and io.TextIOWrapper.

… 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.
@python-cla-bot

Copy link
Copy Markdown

The following commit authors need to sign the Contributor License Agreement:

CLA not signed

@bedevere-app

bedevere-app Bot commented Oct 25, 2026

Copy link
Copy Markdown

Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool.

If this change has little impact on Python users, wait for a maintainer to apply the skip news label instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

codecs.StreamReader.read() silently drops an incomplete character at the end of the stream

2 participants