From 84be0c441d899c93959059d6dd68ce903e466553 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 15 Sep 2026 22:01:40 +0200 Subject: [PATCH 1/2] gh-155742: Use PyBytesWriter in decode_unicode_with_escapes() PyWriterWriter_Finish() is not called, the writer is always discarded. It's just used as a temporary buffer. Add non-ASCII tests to test_string_literals. --- Lib/test/test_string_literals.py | 17 ++++++++++++++++- Parser/string_parser.c | 26 +++++++++++++------------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_string_literals.py b/Lib/test/test_string_literals.py index 1800b17e1df3d72..d4114a49cbb360b 100644 --- a/Lib/test/test_string_literals.py +++ b/Lib/test/test_string_literals.py @@ -54,6 +54,10 @@ assert ord(h) == 0x1d120 i = r'\U0001d120' assert list(map(ord, i)) == [92, 85, 48, 48, 48, 49, 100, 49, 50, 48] +j = 'ä' +assert list(map(ord, j)) == [228] +k = '\ä' +assert list(map(ord, k)) == [92, 228] """ @@ -76,7 +80,7 @@ def test_template(self): # Check that the template doesn't contain any non-printables # except for \n. for c in TEMPLATE: - assert c == '\n' or ' ' <= c <= '~', repr(c) + assert c == '\n' or ' ' <= c <= '~' or c == 'ä', repr(c) def test_eval_str_normal(self): self.assertEqual(eval(""" 'x' """), 'x') @@ -89,6 +93,17 @@ def test_eval_str_normal(self): self.assertEqual(eval(r""" '\U0001d120' """), chr(0x1d120)) self.assertEqual(eval(""" '\U0001d120' """), chr(0x1d120)) + def test_eval_str_unicode(self): + for s in ( + 'ϼўТλФЙ', + 'A͏B ﬖ̳AA̝', + '\U00100000\U0010ffff\U0010fffd', + 'ä', + '\\ä', + ): + with self.subTest(s=s): + self.assertEqual(eval(f""" '{s}' """), s) + def test_eval_str_incomplete(self): self.assertRaises(SyntaxError, eval, r""" '\x' """) self.assertRaises(SyntaxError, eval, r""" '\x0' """) diff --git a/Parser/string_parser.c b/Parser/string_parser.c index e57460cb1fa6483..7d5a2b656914594 100644 --- a/Parser/string_parser.c +++ b/Parser/string_parser.c @@ -135,22 +135,22 @@ static PyObject * decode_unicode_with_escapes(Parser *parser, const char *s, size_t len, Token *t) { PyObject *v; - PyObject *u; char *buf; char *p; const char *end; /* check for integer overflow */ - if (len > (size_t)PY_SSIZE_T_MAX / 6) { + if (len > ((size_t)PY_SSIZE_T_MAX - 1) / 6) { return NULL; } - /* "ä" (2 bytes) may become "\U000000E4" (10 bytes), or 1:5 - "\ä" (3 bytes) may become "\u005c\U000000E4" (16 bytes), or ~1:6 */ - u = PyBytes_FromStringAndSize((char *)NULL, (Py_ssize_t)len * 6); - if (u == NULL) { + /* "ä" (2 bytes) may become "\U000000E4" (10 bytes), or 1:5. + * "\ä" (3 bytes) may become "\u005c\U000000E4" (16 bytes), or ~1:6. + * Add +1 to allow writing a trailing null byte (for strcpy/sprintf). */ + PyBytesWriter *writer = PyBytesWriter_Create((Py_ssize_t)len * 6 + 1); + if (writer == NULL) { return NULL; } - p = buf = PyBytes_AsString(u); + p = buf = PyBytesWriter_GetData(writer); if (p == NULL) { return NULL; } @@ -174,7 +174,7 @@ decode_unicode_with_escapes(Parser *parser, const char *s, size_t len, Token *t) Py_ssize_t i; w = decode_utf8(&s, end); if (w == NULL) { - Py_DECREF(u); + PyBytesWriter_Discard(writer); return NULL; } kind = PyUnicode_KIND(w); @@ -186,7 +186,7 @@ decode_unicode_with_escapes(Parser *parser, const char *s, size_t len, Token *t) p += 10; } /* Should be impossible to overflow */ - assert(p - buf <= PyBytes_GET_SIZE(u)); + assert(p - buf <= PyBytesWriter_GetSize(writer)); Py_DECREF(w); } else { @@ -206,14 +206,14 @@ decode_unicode_with_escapes(Parser *parser, const char *s, size_t len, Token *t) // when we are decoding the string but we preserve the line numbers. if (v != NULL && first_invalid_escape_ptr != NULL && t != NULL) { if (warn_invalid_escape_sequence(parser, s, first_invalid_escape_ptr, t) < 0) { - /* We have not decref u before because first_invalid_escape_ptr - points inside u. */ - Py_XDECREF(u); + /* We have not discarded the writer before because + * first_invalid_escape_ptr points inside the writer buffer. */ + PyBytesWriter_Discard(writer); Py_DECREF(v); return NULL; } } - Py_XDECREF(u); + PyBytesWriter_Discard(writer); return v; } From 6ba004544af1cc6a3fd623b847231df3b41097b2 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 16 Sep 2026 18:33:47 +0200 Subject: [PATCH 2/2] Add two more tests --- Lib/test/test_string_literals.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_string_literals.py b/Lib/test/test_string_literals.py index d4114a49cbb360b..5fd7520077642c4 100644 --- a/Lib/test/test_string_literals.py +++ b/Lib/test/test_string_literals.py @@ -100,9 +100,11 @@ def test_eval_str_unicode(self): '\U00100000\U0010ffff\U0010fffd', 'ä', '\\ä', + "\\П", + "áàäéèęöő.\\n", ): with self.subTest(s=s): - self.assertEqual(eval(f""" '{s}' """), s) + self.assertEqual(eval(f"{s!r}"), s) def test_eval_str_incomplete(self): self.assertRaises(SyntaxError, eval, r""" '\x' """)