`codecs.StreamReader.read()` silently drops an incomplete character at the end of the stream
Maintainer antworten meist innerhalb von 1 Tag
Bewertung
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Anfängerfreundlichkeit
- 56/100
Rechercherichtung
Start with Lib/codecs.py around the StreamReader.read() implementation and the linked expected-failure test StreamReaderTruncationBug.test_read_reports_truncated_input from PR #160155. Compare its end-of-stream behavior with IncrementalDecoder.decode(b'', final=True) and check the reported UTF-8, UTF-16, UTF-32, and escape-codec cases. Done means truncated input raises or uses the selected error handler instead of being silently discarded.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
Bug description
StreamReader.read() decodes with the stateless codec function and keeps any undecoded trailing bytes for the next call. When the stream is exhausted there is no next call, so an incomplete final character is neither reported nor replaced; it just disappears:
>>> import codecs, io
>>> reader = codecs.getreader('utf-8')(io.BytesIO(b'ab\xe2'))
>>> reader.read()
'ab'
>>> reader.read()
''
Every other way of decoding the same bytes either raises or applies the error handler:
>>> b'ab\xe2'.decode('utf-8')
Traceback (most recent call last):
File "<python-input-3>", line 1, in <module>
b'ab\xe2'.decode('utf-8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe2 in position 2: unexpected end of data
>>> io.TextIOWrapper(io.BytesIO(b'ab\xe2'), encoding='utf-8').read()
Traceback (most recent call last):
...
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'
>>> b'ab\xe2'.decode('utf-8', 'replace')
'ab�'
So with errors='strict' a truncated file reads as if it were well-formed, and with 'replace' the replacement character that would mark the damage is missing. The same happens with utf-16, utf-32 and the escape codecs (unicode_escape with a trailing backslash); the CJK codecs are not affected, because their stateless decoders raise on an incomplete sequence themselves.
The cause is in read(): self.decode is the codec's stateless function with its default final=False (Lib/codecs.py line 512), and the loop exits on empty newdata without ever telling the decoder that the input is complete; self.bytebuffer keeps the fragment forever. Fixing it would mean a final decode pass once the underlying stream returns nothing, and raising (or applying the handler) if bytes remain, which is what IncrementalDecoder.decode(b'', final=True) does. The incremental decoders did not exist when StreamReader was written, which I assume is how this came about.
I realise codecs.open() is deprecated since 3.14 and StreamReader is on its way out with it, so feel free to say this is not worth touching; but it is silent data loss in the strict mode, which seemed worth having on record.
This is the same mechanism as #56717 (bpo-12508, 2011), which describes it for UTF-8 with errors='replace' and has had no activity since; I am filing it anyway because it applies to every codec with multi-byte characters and the strict-mode data loss is the part that matters, but feel free to fold this into that one.
Found by the Hypothesis tests in #160155, where it is pinned as StreamReaderTruncationBug.test_read_reports_truncated_input under expectedFailure.
CPython versions tested on
CPython main (6e1bd24e4d8)
Operating systems tested on
Linux
- Vorherrschende Sprache
- Python
- Sterne
- 77.8k
- Forks
- 37.7k
- Ø Merge
- 12 Std. 27 Min.
- Gemergte PRs (30 T.)
- 710
Entwicklungsumgebung
Startet den Dev-Container des Projekts im Browser, mit Ihrem eigenen GitHub-Konto.
- Kein Dockerfile und keine Docker-Compose-Datei
- Hat eine Pull-Request-Vorlage
- Beitragsleitfaden lesen
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus python/cpython
-
3.16 build type-bug
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 62/100
python/cpython#160829 · 4 Kommentare ·
Maintainer antworten meist innerhalb von 1 Tag
-
dependencies infra topic-ensurepip
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 66/100
Maintainer antworten meist innerhalb von 1 Tag
-
symtable: repr() of a Symbol raises TypeError under -X lazy_imports=allEvtl. vergeben Ein verknüpfter Pull Request ist offen oder bereits gemergt. Offenstdlib topic-lazy-imports type-bug
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 72/100
python/cpython#160810 · 3 Kommentare ·
Maintainer antworten meist innerhalb von 1 Tag
-
tarfile: extractfile(member).read() of a sparse member is slow due to repeated bytes concatenationOffenperformance stdlib triaged type-bug
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100
python/cpython#160774 · 2 Kommentare ·
Maintainer antworten meist innerhalb von 1 Tag
-
tests topic-profiling type-bug
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
python/cpython#160334 · 1 Kommentar ·
Maintainer antworten meist innerhalb von 1 Tag
Ähnliche Issues
-
Context/namespace resources fail in-cluster: "Invalid kube-config file. No configuration found."Offen
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 76/100
-
bug documentation
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 88/100
awslabs/visual-asset-management-system#880 ·
Maintainer antworten meist innerhalb von 1 Tag
-
netbox status: needs owner type: documentation
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 74/100
netbox-community/netbox#23533 ·
Maintainer antworten meist innerhalb von 1 Tag
-
app: scripts status: backlog type: documentation
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 86/100
netbox-community/netbox-scripts#68 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 91/100
marin-community/marin#10765 ·
Maintainer antworten meist innerhalb von 1 Tag