Skip to content

Commit 31e88bc

Browse files
authored
[3.15] gh-158451: Add _PyUnicodeWriter_WriteLatin1String() test (#158464) (#159551)
gh-158451: Add _PyUnicodeWriter_WriteLatin1String() test (#158464) Fix _PyUnicodeWriter_WriteLatin1String() when the writer buffer kind is UCS-2 or UCS-4. (cherry picked from commit 26d3f5d)
1 parent c1508fb commit 31e88bc

4 files changed

Lines changed: 81 additions & 57 deletions

File tree

‎Lib/test/test_capi/test_unicode.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1965,6 +1965,24 @@ def test_ascii(self):
19651965
writer.write_ascii(b"Python! <truncated>", 6)
19661966
self.assertEqual(writer.finish(), "Hello Python")
19671967

1968+
def test_write_latin1(self):
1969+
# Test _PyUnicodeWriter_WriteLatin1String()
1970+
writer = self.create_writer(0)
1971+
# Start with ASCII buffer
1972+
writer.write_latin1(b"abc IGNORED", 3)
1973+
writer.write_latin1(b"IGNORED", 0)
1974+
# Change buffer kind to UCS-1
1975+
writer.write_latin1(b"\xe9", 1)
1976+
# Change buffer kind to UCS-2
1977+
writer.write_str('[\u20ac]')
1978+
writer.write_latin1(b"def\xa0", 4)
1979+
# Change buffer kind to UCS-4
1980+
writer.write_str('[\U0010ffff]')
1981+
writer.write_latin1(b"ghi\xff.", 5)
1982+
writer.write_latin1(b"IGNORED", 0)
1983+
self.assertEqual(writer.finish(),
1984+
"abc\xe9[\u20ac]def\xa0[\U0010ffff]ghi\xff.")
1985+
19681986
def test_invalid_utf8(self):
19691987
writer = self.create_writer(0)
19701988
with self.assertRaises(UnicodeDecodeError):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :c:func:`!_PyUnicodeWriter_WriteLatin1String` when the writer buffer
2+
kind is UCS-2 or UCS-4. Patch by Victor Stinner.

‎Modules/_testcapi/unicode.c‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,32 @@ writer_write_substring(PyObject *self_raw, PyObject *args)
685685
}
686686

687687

688+
static PyObject*
689+
writer_write_latin1(PyObject *self_raw, PyObject *args)
690+
{
691+
WriterObject *self = (WriterObject *)self_raw;
692+
if (writer_check(self) < 0) {
693+
return NULL;
694+
}
695+
696+
const char *str;
697+
Py_ssize_t bsize, size;
698+
if (!PyArg_ParseTuple(args, "z#n", &str, &bsize, &size)) {
699+
return NULL;
700+
}
701+
702+
_PyUnicodeWriter *writer = (_PyUnicodeWriter*)self->writer;
703+
_Py_COMP_DIAG_PUSH
704+
_Py_COMP_DIAG_IGNORE_DEPR_DECLS
705+
if (_PyUnicodeWriter_WriteLatin1String(writer, str, size) < 0) {
706+
return NULL;
707+
}
708+
_Py_COMP_DIAG_POP
709+
710+
Py_RETURN_NONE;
711+
}
712+
713+
688714
static PyObject*
689715
writer_decodeutf8stateful(PyObject *self_raw, PyObject *args)
690716
{
@@ -753,6 +779,7 @@ static PyMethodDef writer_methods[] = {
753779
{"write_str", _PyCFunction_CAST(writer_write_str), METH_O},
754780
{"write_repr", _PyCFunction_CAST(writer_write_repr), METH_O},
755781
{"write_substring", _PyCFunction_CAST(writer_write_substring), METH_VARARGS},
782+
{"write_latin1", _PyCFunction_CAST(writer_write_latin1), METH_VARARGS},
756783
{"decodeutf8stateful", _PyCFunction_CAST(writer_decodeutf8stateful), METH_VARARGS},
757784
{"get_pointer", _PyCFunction_CAST(writer_get_pointer), METH_VARARGS},
758785
{"finish", _PyCFunction_CAST(writer_finish), METH_NOARGS},

‎Objects/unicode_writer.c‎

Lines changed: 34 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -62,59 +62,6 @@ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
6262
#include "stringlib/undef.h"
6363

6464

65-
/* Copy an ASCII or latin1 char* string into a Python Unicode string.
66-
67-
WARNING: The function doesn't copy the terminating null character and
68-
doesn't check the maximum character (may write a latin1 character in an
69-
ASCII string). */
70-
static void
71-
unicode_write_cstr(PyObject *unicode, Py_ssize_t index,
72-
const char *str, Py_ssize_t len)
73-
{
74-
int kind = PyUnicode_KIND(unicode);
75-
const void *data = PyUnicode_DATA(unicode);
76-
const char *end = str + len;
77-
78-
assert(index + len <= PyUnicode_GET_LENGTH(unicode));
79-
switch (kind) {
80-
case PyUnicode_1BYTE_KIND: {
81-
#ifdef Py_DEBUG
82-
if (PyUnicode_IS_ASCII(unicode)) {
83-
Py_UCS4 maxchar = ucs1lib_find_max_char(
84-
(const Py_UCS1*)str,
85-
(const Py_UCS1*)str + len);
86-
assert(maxchar < 128);
87-
}
88-
#endif
89-
memcpy((char *) data + index, str, len);
90-
break;
91-
}
92-
case PyUnicode_2BYTE_KIND: {
93-
Py_UCS2 *start = (Py_UCS2 *)data + index;
94-
Py_UCS2 *ucs2 = start;
95-
96-
for (; str < end; ++ucs2, ++str)
97-
*ucs2 = (Py_UCS2)*str;
98-
99-
assert((ucs2 - start) <= PyUnicode_GET_LENGTH(unicode));
100-
break;
101-
}
102-
case PyUnicode_4BYTE_KIND: {
103-
Py_UCS4 *start = (Py_UCS4 *)data + index;
104-
Py_UCS4 *ucs4 = start;
105-
106-
for (; str < end; ++ucs4, ++str)
107-
*ucs4 = (Py_UCS4)*str;
108-
109-
assert((ucs4 - start) <= PyUnicode_GET_LENGTH(unicode));
110-
break;
111-
}
112-
default:
113-
Py_UNREACHABLE();
114-
}
115-
}
116-
117-
11865
static inline void
11966
_PyUnicodeWriter_Update(_PyUnicodeWriter *writer)
12067
{
@@ -594,13 +541,43 @@ int
594541
_PyUnicodeWriter_WriteLatin1String(_PyUnicodeWriter *writer,
595542
const char *str, Py_ssize_t len)
596543
{
597-
Py_UCS4 maxchar;
544+
if (len == 0) {
545+
return 0;
546+
}
598547

599-
maxchar = ucs1lib_find_max_char((const Py_UCS1*)str, (const Py_UCS1*)str + len);
600-
if (_PyUnicodeWriter_Prepare(writer, len, maxchar) == -1)
548+
const Py_UCS1 *ucs1 = (const Py_UCS1 *)str;
549+
Py_UCS4 maxchar = ucs1lib_find_max_char(ucs1, ucs1 + len);
550+
if (_PyUnicodeWriter_Prepare(writer, len, maxchar) < 0) {
601551
return -1;
552+
}
602553
assert(_PyUnicodeWriter_CanWrite(writer));
603-
unicode_write_cstr(writer->buffer, writer->pos, str, len);
554+
555+
Py_ssize_t index = writer->pos;
556+
switch (writer->kind) {
557+
case PyUnicode_1BYTE_KIND: {
558+
memcpy((Py_UCS1 *)writer->data + index, ucs1, len);
559+
break;
560+
}
561+
case PyUnicode_2BYTE_KIND: {
562+
Py_UCS2 *ucs2 = (Py_UCS2 *)writer->data + index;
563+
const Py_UCS1 *end = ucs1 + len;
564+
for (; ucs1 < end; ++ucs2, ++ucs1) {
565+
*ucs2 = (Py_UCS2)*ucs1;
566+
}
567+
break;
568+
}
569+
case PyUnicode_4BYTE_KIND: {
570+
Py_UCS4 *ucs4 = (Py_UCS4 *)writer->data + index;
571+
const Py_UCS1 *end = ucs1 + len;
572+
for (; ucs1 < end; ++ucs4, ++ucs1) {
573+
*ucs4 = (Py_UCS4)*ucs1;
574+
}
575+
break;
576+
}
577+
default:
578+
Py_UNREACHABLE();
579+
}
580+
604581
writer->pos += len;
605582
return 0;
606583
}

0 commit comments

Comments
 (0)