https://github.com/python/cpython/commit/685ad027478d3c16e9843bb20ddec4711da3fcd0
commit: 685ad027478d3c16e9843bb20ddec4711da3fcd0
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-02T18:20:48+03:00
summary:

gh-155006: Encode an error handler's replacement strictly (GH-155008)

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) <[email protected]>

files:
M Lib/test/test_codecs.py
M Objects/unicodeobject.c

diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py
index f6f8089a065059..cefa38fd7518ab 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&#8364;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 4b4f7178ec9faf..45d61c8b8b765a 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);

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to