io: TextIOWrapper silently corrupts output when encoder is a no-op

未关闭 适合新手
#161,518 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
2/5
预计耗时
1-3 小时
新手友好度
75/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
c, python
领域
backend, testing

调研方向

阅读 Modules/_io/textio.c 中的 _io_TextIOWrapper_write_impl,尤其是报告中链接的检查,并查看附近关于编码器返回类型的 TextIOWrapper 测试。重现 issue 中的直通编码器情形,然后添加回归测试并验证:非 ASCII 输入会引发所述的 TypeError,而 ASCII 快速路径仍然有效。

由索引模型根据 Issue 内容生成。

描述

type-bug
Bug report

Summary

When an encoder returns the input str itself, TextIOWrapper.write treats it as ASCII and writes the str's internal storage to the file. For non-ASCII text the file is silently corrupted.

Expected: TypeError: encoder should return a bytes object, not 'str'.

Reproducer

We use a 44-character string that ends in an emoji, so CPython internally stores it as four bytes per character.

import codecs

class Enc(codecs.IncrementalEncoder):
    def encode(self, text, final=False):
        return text

info = codecs.CodecInfo(None, None, incrementalencoder=Enc,
                        incrementaldecoder=codecs.getincrementaldecoder("utf-8"),
                        name="passthrough")
codecs.register(lambda n: info if n == "passthrough" else None)

with open("out.txt", "w", encoding="passthrough") as f:
    f.write("The quick brown fox jumps over the lazy dog🐍")

print(open("out.txt").read())
The quick b

Only the first 44 bytes (out of 44*4 = 176) were written to the file.

Root cause

Modules/_io/textio.c, in _io_TextIOWrapper_write_impl:

https://github.com/python/cpython/blob/d900c8f9aa7e71cca78536845340b19f9706e871/Modules/_io/textio.c#L1834

The exemption for b == text is meant for the ASCII fast path, which sets b = text only for ASCII text.

A Python encoder that returns its input also satisfies it, with any text.

Suggested fix

Tie the exemption to what the fast path relies on, that the text is ASCII:

if (!PyBytes_Check(b) && !(b == text && PyUnicode_IS_ASCII(text))) {

With this the reproducer raises the TypeError above.

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

主要语言
Python
星标
77.9k
派生
37.1k
平均合并
17 小时 43 分钟
30 天内合并 PR
675

环境准备

在 Codespaces 中打开

在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

python/cpython 的其他 Issue

查看 python/cpython 的全部 Issue

相似的 Issue

更多 Python Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。