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

Ouverte Adaptée aux débutants
#161,518 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Les mainteneurs répondent en général sous 1 jour

Personne n'a encore pris cette issue.

Évaluation

Difficulté
2/5
Temps estimé
1-3 heures
Accessibilité débutants
75/100
Type d'issue
Bug
Clarté
Clairement spécifiée
Activité
Active
Stack technique
c, python
Domaine
backend, testing

Piste de recherche

Lis _io_TextIOWrapper_write_impl dans Modules/_io/textio.c, en particulier la vérification liée dans le rapport, et examine les tests TextIOWrapper à proximité concernant les types de retour de l’encodeur. Reproduis le cas de l’encodeur passthrough de l’issue, puis ajoute un test de régression et vérifie que l’entrée non ASCII déclenche le TypeError indiqué tandis que le chemin rapide ASCII fonctionne toujours.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Description

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

Langage dominant
Python
Étoiles
77.9k
Forks
37.1k
Merge moyen
17 h 43 min
PR mergées (30 j)
675

Préparer son environnement

Ouvrir dans Codespaces

Lance le conteneur de développement du projet dans votre navigateur, avec votre propre compte GitHub.

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de python/cpython

Toutes les issues de python/cpython

Issues similaires

Plus d'issues Python

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.