From c66b7cebd6abbc88ae99e10b78f59e65f6680970 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 31 Jul 2026 23:00:18 +0300 Subject: [PATCH] gh-155006: Encode an error handler's replacement strictly Encoding it with the same error handler could never terminate: a replacement that is itself unencodable calls the handler again. A replacement that does not fit is now reported against the input character. Co-Authored-By: Claude Opus 5 (1M context) --- Lib/test/test_codecs.py | 12 ++++++++++++ Objects/unicodeobject.c | 12 ++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py index f6f8089a065059b..cefa38fd7518abc 100644 --- a/Lib/test/test_codecs.py +++ b/Lib/test/test_codecs.py @@ -1,3 +1,4 @@ +import _codecs import codecs import contextlib import copy @@ -3735,6 +3736,17 @@ def test_encode_errors(self): self.assertEqual(codecs.iconv_encode(enc, 'a€b', 'xmlcharrefreplace')[0], b'a€b') + def test_encode_errors_unencodable_replacement(self): + # Encoding the replacement must not call the error handler again. + enc = self.require('ASCII') + codecs.register_error('test.iconv', lambda exc: ('€', exc.end)) + self.addCleanup(_codecs._unregister_error, 'test.iconv') + with self.assertRaises(UnicodeEncodeError) as cm: + codecs.iconv_encode(enc, 'a€b', 'test.iconv') + self.assertEqual((cm.exception.start, cm.exception.end), (1, 2)) + self.assertEqual(cm.exception.reason, + 'unable to encode error handler result') + def test_decode_errors(self): enc = self.require('ASCII') bad = b'a\xffb' diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index eec02f662e79059..5f60a57156c8520 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -8520,11 +8520,19 @@ _PyUnicode_EncodeIconv(const char *encoding, PyObject *unicode, replen = PyBytes_GET_SIZE(rep); } else { - /* A str replacement is encoded through the same codec. */ + /* A str replacement is encoded through the same codec, but + strictly: handling its errors in turn could never terminate. */ assert(PyUnicode_Check(rep)); - repbytes = _PyUnicode_EncodeIconv(encoding, rep, errors); + repbytes = _PyUnicode_EncodeIconv(encoding, rep, NULL); Py_DECREF(rep); if (repbytes == NULL) { + if (PyErr_ExceptionMatches(PyExc_UnicodeEncodeError)) { + /* Report the input the caller knows about, not the + replacement. */ + PyErr_Clear(); + raise_encode_exception(&exc, encoding, unicode, pos, pos + 1, + "unable to encode error handler result"); + } goto done; } repdata = PyBytes_AS_STRING(repbytes);