Skip to content

Commit 51940f7

Browse files
Re-encode carefully after any unencodable code point
Apple's iconv converts several code points at once and, when one of them is unencodable, restores the shift state of the whole batch, while the output written for the preceding code points keeps it. Re-running the conversion one code point at a time keeps the descriptor in sync with the output, so the shift state can be reset before the replacement. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 3a0fa7f commit 51940f7

1 file changed

Lines changed: 24 additions & 17 deletions

File tree

Objects/unicodeobject.c

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8526,25 +8526,15 @@ _PyUnicode_EncodeIconv(const char *encoding, PyObject *unicode,
85268526
}
85278527
/* A positive result counts nonreversible conversions: iconv()
85288528
substituted an unencodable character instead of failing with
8529-
EILSEQ (musl and *BSD citrus do this). Treat it as unencodable
8530-
and re-run one code point at a time to locate it. */
8529+
EILSEQ (musl and *BSD citrus do this). */
85318530
if (ret > 0) {
8532-
if (!careful) {
8533-
careful = 1;
8534-
probe_cd = iconv_open_or_set_error(encoding, source,
8535-
encoding);
8536-
if (probe_cd == (iconv_t)-1) {
8537-
goto done;
8538-
}
8539-
iconv(cd, NULL, NULL, NULL, NULL);
8540-
out = PyBytesWriter_GetData(writer);
8541-
outend = out + PyBytesWriter_GetSize(writer);
8542-
up = ustart;
8543-
continue;
8531+
if (careful) {
8532+
/* The probe reported the code point as encodable, but it
8533+
was substituted in the current shift state; drop it and
8534+
report it. */
8535+
out = out_before;
8536+
up -= unit;
85448537
}
8545-
/* This code point was substituted; drop it and report it. */
8546-
out = out_before;
8547-
up -= unit;
85488538
}
85498539
else if (careful && up < uend) {
85508540
continue;
@@ -8566,6 +8556,23 @@ _PyUnicode_EncodeIconv(const char *encoding, PyObject *unicode,
85668556
goto done;
85678557
}
85688558

8559+
if (!careful) {
8560+
/* iconv() can reset the shift state of cd on an unencodable code
8561+
point, while the output written for the preceding code points
8562+
stays in the shifted state. Re-run one code point at a time:
8563+
then nothing is written for the failed one. */
8564+
careful = 1;
8565+
probe_cd = iconv_open_or_set_error(encoding, source, encoding);
8566+
if (probe_cd == (iconv_t)-1) {
8567+
goto done;
8568+
}
8569+
iconv(cd, NULL, NULL, NULL, NULL);
8570+
out = PyBytesWriter_GetData(writer);
8571+
outend = out + PyBytesWriter_GetSize(writer);
8572+
up = ustart;
8573+
continue;
8574+
}
8575+
85698576
/* An unencodable code point at *up; one input unit is one code point. */
85708577
Py_ssize_t pos = (up - ustart) / unit;
85718578
Py_ssize_t newpos;

0 commit comments

Comments
 (0)