]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-155006: Encode an error handler's replacement strictly (GH-155008)
authorSerhiy Storchaka <storchaka@gmail.com>
Sun, 2 Aug 2026 15:20:48 +0000 (18:20 +0300)
committerGitHub <noreply@github.com>
Sun, 2 Aug 2026 15:20:48 +0000 (18:20 +0300)
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) <noreply@anthropic.com>
Lib/test/test_codecs.py
Objects/unicodeobject.c

index f6f8089a065059ba89d3ffe3852422e5318af428..cefa38fd7518abcfd6280b978c322c5411bb4238 100644 (file)
@@ -1,3 +1,4 @@
+import _codecs
 import codecs
 import contextlib
 import copy
@@ -3735,6 +3736,17 @@ class IconvTest(unittest.TestCase):
         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'
index 4b4f7178ec9faf6c14f6ab9cf57428cd3bde9987..45d61c8b8b765a6f08f1e668badedd46cf2156d3 100644 (file)
@@ -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);