GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — `euc_jp` and `gb18030` incremental decoders drop input after an incomplete trailing sequence under `surrogateescape` · Issue #160307 · python/cpython

euc_jp and gb18030 incremental decoders drop input after an incomplete trailing sequence under surrogateescape #160307

New issue
Copy link
New issue
Copy link
Open
#160629
Open
euc_jp and gb18030 incremental decoders drop input after an incomplete trailing sequence under surrogateescape#160307
#160629
Copy link
Labels
extension-modulesC modules in the Modules dirC modules in the Modules dirtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

@pganssle
pganssle
opened on Oct 22, 2026
Issue body actions

Bug description

With errors='surrogateescape', the incremental decoders of the CJK codecs that have sequences longer than two bytes (euc_jp, euc_jis_2004, euc_jisx0213, gb18030) lose data when the input ends in an incomplete sequence: the first byte is escaped, and whatever followed it is gone. The stateless decoder escapes the byte and keeps going:

>>> import codecs
>>> b'\x8f\x00'.decode('euc_jp', 'surrogateescape')
'\udc8f\x00'
>>> codecs.getincrementaldecoder('euc_jp')('surrogateescape').decode(b'\x8f\x00', final=True)
'\udc8f'
>>> b'\x806'.decode('gb18030', 'surrogateescape')
'\udc806'
>>> codecs.getincrementaldecoder('gb18030')('surrogateescape').decode(b'\x806', final=True)
'\udc80'

The 6 and the \x00 are plain ASCII; they are simply never looked at again. TextIOWrapper uses the incremental decoder, so this is reachable through ordinary file reading:

>>> import io
>>> io.TextIOWrapper(io.BytesIO(b'\xa8\x8fl'), encoding='euc_jp', errors='surrogateescape').read()
'\udca8\udc8f'

The other handlers do not lose anything because they consume the whole trailing range in one go (replace gives one � for it, backslashreplace escapes every byte). surrogateescape escapes a single byte and returns a resume position before the end of the input, and the final-flush path in _multibytecodec does not go back to the decoder from there: after the one multibytecodec_decerror(..., MBERR_TOOFEW) call it finishes up with whatever has been written so far, Modules/cjkcodecs/multibytecodec.c lines 1211-1218. The stateless entry point in the same file loops until the input is consumed, which is why it gets this right. That is from reading the code rather than tracing it, so treat the mechanism as a best guess. It also explains why the two-byte codecs (big5, euc_kr, gbk, shift_jis, ...) are not affected: with one pending byte there is nothing left to drop after escaping it.

Found by the Hypothesis tests in #160155, where it is pinned as MultibyteSurrogateescapeBug.test_trailing_bytes_after_escape under expectedFailure; the chunked-decoding tests there leave surrogateescape out for these four codecs until it is fixed.

CPython versions tested on

CPython main (6e1bd24)

Operating systems tested on

Linux

Linked PRs

  • gh-160307: Complete final multibyte incremental decoding #160629

Activity

  1. pganssle
    added
    type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
    extension-modulesC modules in the Modules dirC modules in the Modules dir
    on Oct 22, 2026
  2. pganssle
    added 2 commits that reference this issue on Oct 22, 2026

    pythongh-107862: Reference pythongh-160307 from the surrogateescape e…

    f34cbe0

    pythongh-107862: Reference pythongh-160307 from the surrogateescape e…

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    extension-modulesC modules in the Modules dirC modules in the Modules dirtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.