Bug report
A str returned by an error handler is re-encoded by a recursive _PyUnicode_EncodeIconv() call with the same errors, so a replacement that is itself unencodable calls the handler again, and again.
>>> codecs.register_error('bad', lambda e: ('€', e.end))
>>> 'a😀b'.encode('ascii', 'bad')
UnicodeEncodeError: 'ascii' codec can't encode character '\U0001f600' in position 1: ordinal not in range(128)
>>> codecs.iconv_encode('ASCII', 'a😀b', 'bad')
RecursionError: Stack overflow (used 8120 kB)
The standard handlers are enough for a charset that lacks the characters they emit, which is 274 of the encodings glibc provides here: the ISO-646 national variants and the EBCDIC family.
>>> 'a😀b'.encode('iso646-de', 'backslashreplace') # 0x5c is Ö, no backslash
RecursionError: Stack overflow (used 8120 kB) while calling a Python object
>>> 'a😀b'.encode('iso646-fr', 'xmlcharrefreplace') # 0x23 is £, no '#'
RecursionError: Stack overflow (used 8120 kB) while calling a Python object
The code page codec and the charmap codec encode the replacement once and raise if it does not fit. Re-encoding with strict would do the same here, and still write the correct byte for '?' in EBCDIC.
Linked PRs
Bug report
A
strreturned by an error handler is re-encoded by a recursive_PyUnicode_EncodeIconv()call with the sameerrors, so a replacement that is itself unencodable calls the handler again, and again.The standard handlers are enough for a charset that lacks the characters they emit, which is 274 of the encodings glibc provides here: the ISO-646 national variants and the EBCDIC family.
The code page codec and the charmap codec encode the replacement once and raise if it does not fit. Re-encoding with
strictwould do the same here, and still write the correct byte for'?'in EBCDIC.Linked PRs