Skip to content

Commit 5b13e86

Browse files
serhiy-storchakamiss-islington
authored andcommitted
[3.11] pythongh-99612: Fix PyUnicode_DecodeUTF8Stateful() for ASCII-only data (pythonGH-99613) (pythonGH-107224)
(cherry picked from commit b8b3e6a) Co-authored-by: Serhiy Storchaka <[email protected]> Previously *consumed was not set in this case. (cherry picked from commit f08e52c)
1 parent a9e5e59 commit 5b13e86

File tree

4 files changed

+95
-1
lines changed

4 files changed

+95
-1
lines changed

Diff for: 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.

Diff for: Modules/_testcapimodule.c

+36-1
Original file line numberDiff line numberDiff line change
@@ -2112,6 +2112,40 @@ unicode_asutf8andsize(PyObject *self, PyObject *args)
21122112
return Py_BuildValue("(Nn)", result, utf8_len);
21132113
}
21142114

2115+
/* Test PyUnicode_DecodeUTF8() */
2116+
static PyObject *
2117+
unicode_decodeutf8(PyObject *self, PyObject *args)
2118+
{
2119+
const char *data;
2120+
Py_ssize_t size;
2121+
const char *errors = NULL;
2122+
2123+
if (!PyArg_ParseTuple(args, "y#|z", &data, &size, &errors))
2124+
return NULL;
2125+
2126+
return PyUnicode_DecodeUTF8(data, size, errors);
2127+
}
2128+
2129+
/* Test PyUnicode_DecodeUTF8Stateful() */
2130+
static PyObject *
2131+
unicode_decodeutf8stateful(PyObject *self, PyObject *args)
2132+
{
2133+
const char *data;
2134+
Py_ssize_t size;
2135+
const char *errors = NULL;
2136+
Py_ssize_t consumed = 123456789;
2137+
PyObject *result;
2138+
2139+
if (!PyArg_ParseTuple(args, "y#|z", &data, &size, &errors))
2140+
return NULL;
2141+
2142+
result = PyUnicode_DecodeUTF8Stateful(data, size, errors, &consumed);
2143+
if (!result) {
2144+
return NULL;
2145+
}
2146+
return Py_BuildValue("(Nn)", result, consumed);
2147+
}
2148+
21152149
static PyObject *
21162150
unicode_findchar(PyObject *self, PyObject *args)
21172151
{
@@ -5846,7 +5880,8 @@ static PyMethodDef TestMethods[] = {
58465880
{"unicode_asucs4", unicode_asucs4, METH_VARARGS},
58475881
{"unicode_asutf8", unicode_asutf8, METH_VARARGS},
58485882
{"unicode_asutf8andsize", unicode_asutf8andsize, METH_VARARGS},
5849-
{"unicode_findchar", unicode_findchar, METH_VARARGS},
5883+
{"unicode_decodeutf8", unicode_decodeutf8, METH_VARARGS},
5884+
{"unicode_decodeutf8stateful",unicode_decodeutf8stateful, METH_VARARGS}, {"unicode_findchar", unicode_findchar, METH_VARARGS},
58505885
{"unicode_copycharacters", unicode_copycharacters, METH_VARARGS},
58515886
#if USE_UNICODE_WCHAR_CACHE
58525887
{"unicode_encodedecimal", unicode_encodedecimal, METH_VARARGS},

Diff for: Objects/unicodeobject.c

+3
Original file line numberDiff line numberDiff line change
@@ -5206,6 +5206,9 @@ unicode_decode_utf8(const char *s, Py_ssize_t size,
52065206
}
52075207
s += ascii_decode(s, end, PyUnicode_1BYTE_DATA(u));
52085208
if (s == end) {
5209+
if (consumed) {
5210+
*consumed = size;
5211+
}
52095212
return u;
52105213
}
52115214

0 commit comments

Comments
 (0)