Skip to content

Commit b8b3e6a

Browse files
[3.11] gh-99612: Fix PyUnicode_DecodeUTF8Stateful() for ASCII-only data (GH-99613) (GH-107224)
Previously *consumed was not set in this case. (cherry picked from commit f08e52c)
1 parent 058741c commit b8b3e6a

File tree

4 files changed

+95
-1
lines changed

4 files changed

+95
-1
lines changed

Lib/test/test_capi/test_codecs.py

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import unittest
2+
from test.support import import_helper
3+
4+
_testcapi = import_helper.import_module('_testcapi')
5+
6+
7+
class CAPITest(unittest.TestCase):
8+
9+
def test_decodeutf8(self):
10+
"""Test PyUnicode_DecodeUTF8()"""
11+
decodeutf8 = _testcapi.unicode_decodeutf8
12+
13+
for s in ['abc', '\xa1\xa2', '\u4f60\u597d', 'a\U0001f600']:
14+
b = s.encode('utf-8')
15+
self.assertEqual(decodeutf8(b), s)
16+
self.assertEqual(decodeutf8(b, 'strict'), s)
17+
18+
self.assertRaises(UnicodeDecodeError, decodeutf8, b'\x80')
19+
self.assertRaises(UnicodeDecodeError, decodeutf8, b'\xc0')
20+
self.assertRaises(UnicodeDecodeError, decodeutf8, b'\xff')
21+
self.assertRaises(UnicodeDecodeError, decodeutf8, b'a\xf0\x9f')
22+
self.assertEqual(decodeutf8(b'a\xf0\x9f', 'replace'), 'a\ufffd')
23+
self.assertEqual(decodeutf8(b'a\xf0\x9fb', 'replace'), 'a\ufffdb')
24+
25+
self.assertRaises(LookupError, decodeutf8, b'a\x80', 'foo')
26+
# TODO: Test PyUnicode_DecodeUTF8() with NULL as data and
27+
# negative size.
28+
29+
def test_decodeutf8stateful(self):
30+
"""Test PyUnicode_DecodeUTF8Stateful()"""
31+
decodeutf8stateful = _testcapi.unicode_decodeutf8stateful
32+
33+
for s in ['abc', '\xa1\xa2', '\u4f60\u597d', 'a\U0001f600']:
34+
b = s.encode('utf-8')
35+
self.assertEqual(decodeutf8stateful(b), (s, len(b)))
36+
self.assertEqual(decodeutf8stateful(b, 'strict'), (s, len(b)))
37+
38+
self.assertRaises(UnicodeDecodeError, decodeutf8stateful, b'\x80')
39+
self.assertRaises(UnicodeDecodeError, decodeutf8stateful, b'\xc0')
40+
self.assertRaises(UnicodeDecodeError, decodeutf8stateful, b'\xff')
41+
self.assertEqual(decodeutf8stateful(b'a\xf0\x9f'), ('a', 1))
42+
self.assertEqual(decodeutf8stateful(b'a\xf0\x9f', 'replace'), ('a', 1))
43+
self.assertRaises(UnicodeDecodeError, decodeutf8stateful, b'a\xf0\x9fb')
44+
self.assertEqual(decodeutf8stateful(b'a\xf0\x9fb', 'replace'), ('a\ufffdb', 4))
45+
46+
self.assertRaises(LookupError, decodeutf8stateful, b'a\x80', 'foo')
47+
# TODO: Test PyUnicode_DecodeUTF8Stateful() with NULL as data and
48+
# negative size.
49+
# TODO: Test PyUnicode_DecodeUTF8Stateful() with NULL as the address of
50+
# "consumed".
51+
52+
53+
if __name__ == "__main__":
54+
unittest.main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :c:func:`PyUnicode_DecodeUTF8Stateful` for ASCII-only data:
2+
``*consumed`` was not set.

Modules/_testcapimodule.c

+36-1
Original file line numberDiff line numberDiff line change
@@ -2307,6 +2307,40 @@ unicode_asutf8andsize(PyObject *self, PyObject *args)
23072307
return Py_BuildValue("(Nn)", result, utf8_len);
23082308
}
23092309

2310+
/* Test PyUnicode_DecodeUTF8() */
2311+
static PyObject *
2312+
unicode_decodeutf8(PyObject *self, PyObject *args)
2313+
{
2314+
const char *data;
2315+
Py_ssize_t size;
2316+
const char *errors = NULL;
2317+
2318+
if (!PyArg_ParseTuple(args, "y#|z", &data, &size, &errors))
2319+
return NULL;
2320+
2321+
return PyUnicode_DecodeUTF8(data, size, errors);
2322+
}
2323+
2324+
/* Test PyUnicode_DecodeUTF8Stateful() */
2325+
static PyObject *
2326+
unicode_decodeutf8stateful(PyObject *self, PyObject *args)
2327+
{
2328+
const char *data;
2329+
Py_ssize_t size;
2330+
const char *errors = NULL;
2331+
Py_ssize_t consumed = 123456789;
2332+
PyObject *result;
2333+
2334+
if (!PyArg_ParseTuple(args, "y#|z", &data, &size, &errors))
2335+
return NULL;
2336+
2337+
result = PyUnicode_DecodeUTF8Stateful(data, size, errors, &consumed);
2338+
if (!result) {
2339+
return NULL;
2340+
}
2341+
return Py_BuildValue("(Nn)", result, consumed);
2342+
}
2343+
23102344
static PyObject *
23112345
unicode_findchar(PyObject *self, PyObject *args)
23122346
{
@@ -6562,7 +6596,8 @@ static PyMethodDef TestMethods[] = {
65626596
{"unicode_asucs4", unicode_asucs4, METH_VARARGS},
65636597
{"unicode_asutf8", unicode_asutf8, METH_VARARGS},
65646598
{"unicode_asutf8andsize", unicode_asutf8andsize, METH_VARARGS},
6565-
{"unicode_findchar", unicode_findchar, METH_VARARGS},
6599+
{"unicode_decodeutf8", unicode_decodeutf8, METH_VARARGS},
6600+
{"unicode_decodeutf8stateful",unicode_decodeutf8stateful, METH_VARARGS}, {"unicode_findchar", unicode_findchar, METH_VARARGS},
65666601
{"unicode_copycharacters", unicode_copycharacters, METH_VARARGS},
65676602
#if USE_UNICODE_WCHAR_CACHE
65686603
{"unicode_legacy_string", unicode_legacy_string, METH_VARARGS},

Objects/unicodeobject.c

+3
Original file line numberDiff line numberDiff line change
@@ -5120,6 +5120,9 @@ unicode_decode_utf8(const char *s, Py_ssize_t size,
51205120
}
51215121
s += ascii_decode(s, end, PyUnicode_1BYTE_DATA(u));
51225122
if (s == end) {
5123+
if (consumed) {
5124+
*consumed = size;
5125+
}
51235126
return u;
51245127
}
51255128

0 commit comments

Comments
 (0)