Skip to content

Commit b313cc6

Browse files
gh-117557: Improve error messages when a string, bytes or bytearray of length 1 are expected (GH-117631)
1 parent bf08f0a commit b313cc6

File tree

18 files changed

+811
-161
lines changed

18 files changed

+811
-161
lines changed

Lib/test/clinic.test.c

Lines changed: 230 additions & 31 deletions
Large diffs are not rendered by default.

Lib/test/test_ctypes/test_functions.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import sys
33
import unittest
44
from ctypes import (CDLL, Structure, Array, CFUNCTYPE,
5-
byref, POINTER, pointer, ArgumentError,
5+
byref, POINTER, pointer, ArgumentError, sizeof,
66
c_char, c_wchar, c_byte, c_char_p, c_wchar_p,
77
c_short, c_int, c_long, c_longlong, c_void_p,
88
c_float, c_double, c_longdouble)
@@ -72,7 +72,8 @@ def callback(*args):
7272

7373
self.assertEqual(str(cm.exception),
7474
"argument 1: TypeError: one character bytes, "
75-
"bytearray or integer expected")
75+
"bytearray, or an integer in range(256) expected, "
76+
"not bytes of length 3")
7677

7778
def test_wchar_parm(self):
7879
f = dll._testfunc_i_bhilfd
@@ -84,14 +85,27 @@ def test_wchar_parm(self):
8485
with self.assertRaises(ArgumentError) as cm:
8586
f(1, 2, 3, 4, 5.0, 6.0)
8687
self.assertEqual(str(cm.exception),
87-
"argument 2: TypeError: unicode string expected "
88-
"instead of int instance")
88+
"argument 2: TypeError: a unicode character expected, "
89+
"not instance of int")
8990

9091
with self.assertRaises(ArgumentError) as cm:
9192
f(1, "abc", 3, 4, 5.0, 6.0)
9293
self.assertEqual(str(cm.exception),
93-
"argument 2: TypeError: one character unicode string "
94-
"expected")
94+
"argument 2: TypeError: a unicode character expected, "
95+
"not a string of length 3")
96+
97+
with self.assertRaises(ArgumentError) as cm:
98+
f(1, "", 3, 4, 5.0, 6.0)
99+
self.assertEqual(str(cm.exception),
100+
"argument 2: TypeError: a unicode character expected, "
101+
"not a string of length 0")
102+
103+
if sizeof(c_wchar) < 4:
104+
with self.assertRaises(ArgumentError) as cm:
105+
f(1, "\U0001f40d", 3, 4, 5.0, 6.0)
106+
self.assertEqual(str(cm.exception),
107+
"argument 2: TypeError: the string '\\U0001f40d' "
108+
"cannot be converted to a single wchar_t character")
95109

96110
def test_c_char_p_parm(self):
97111
"""Test the error message when converting an incompatible type to c_char_p."""

Lib/test/test_ctypes/test_parameters.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from ctypes import (CDLL, PyDLL, ArgumentError,
44
Structure, Array, Union,
55
_Pointer, _SimpleCData, _CFuncPtr,
6-
POINTER, pointer, byref,
6+
POINTER, pointer, byref, sizeof,
77
c_void_p, c_char_p, c_wchar_p, py_object,
88
c_bool,
99
c_char, c_wchar,
@@ -87,19 +87,33 @@ def test_c_char(self):
8787
with self.assertRaises(TypeError) as cm:
8888
c_char.from_param(b"abc")
8989
self.assertEqual(str(cm.exception),
90-
"one character bytes, bytearray or integer expected")
90+
"one character bytes, bytearray, or an integer "
91+
"in range(256) expected, not bytes of length 3")
9192

9293
def test_c_wchar(self):
9394
with self.assertRaises(TypeError) as cm:
9495
c_wchar.from_param("abc")
9596
self.assertEqual(str(cm.exception),
96-
"one character unicode string expected")
97+
"a unicode character expected, not a string of length 3")
9798

99+
with self.assertRaises(TypeError) as cm:
100+
c_wchar.from_param("")
101+
self.assertEqual(str(cm.exception),
102+
"a unicode character expected, not a string of length 0")
98103

99104
with self.assertRaises(TypeError) as cm:
100105
c_wchar.from_param(123)
101106
self.assertEqual(str(cm.exception),
102-
"unicode string expected instead of int instance")
107+
"a unicode character expected, not instance of int")
108+
109+
if sizeof(c_wchar) < 4:
110+
with self.assertRaises(TypeError) as cm:
111+
c_wchar.from_param('\U0001f40d')
112+
self.assertEqual(str(cm.exception),
113+
"the string '\\U0001f40d' cannot be converted to "
114+
"a single wchar_t character")
115+
116+
103117

104118
def test_int_pointers(self):
105119
LPINT = POINTER(c_int)

Lib/test/test_str.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1578,7 +1578,7 @@ def __int__(self):
15781578
self.assertRaisesRegex(TypeError, '%u format: a real number is required, not complex', operator.mod, '%u', 3j)
15791579
self.assertRaisesRegex(TypeError, '%i format: a real number is required, not complex', operator.mod, '%i', 2j)
15801580
self.assertRaisesRegex(TypeError, '%d format: a real number is required, not complex', operator.mod, '%d', 1j)
1581-
self.assertRaisesRegex(TypeError, '%c requires int or char', operator.mod, '%c', pi)
1581+
self.assertRaisesRegex(TypeError, r'%c requires an int or a unicode character, not .*\.PseudoFloat', operator.mod, '%c', pi)
15821582

15831583
class RaisingNumber:
15841584
def __int__(self):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improve error messages when a string, bytes or bytearray object of length 1
2+
is expected.

Modules/_ctypes/cfield.c

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,25 +1100,45 @@ O_set(void *ptr, PyObject *value, Py_ssize_t size)
11001100
static PyObject *
11011101
c_set(void *ptr, PyObject *value, Py_ssize_t size)
11021102
{
1103-
if (PyBytes_Check(value) && PyBytes_GET_SIZE(value) == 1) {
1103+
if (PyBytes_Check(value)) {
1104+
if (PyBytes_GET_SIZE(value) != 1) {
1105+
PyErr_Format(PyExc_TypeError,
1106+
"one character bytes, bytearray, or an integer "
1107+
"in range(256) expected, not bytes of length %zd",
1108+
PyBytes_GET_SIZE(value));
1109+
return NULL;
1110+
}
11041111
*(char *)ptr = PyBytes_AS_STRING(value)[0];
11051112
_RET(value);
11061113
}
1107-
if (PyByteArray_Check(value) && PyByteArray_GET_SIZE(value) == 1) {
1114+
if (PyByteArray_Check(value)) {
1115+
if (PyByteArray_GET_SIZE(value) != 1) {
1116+
PyErr_Format(PyExc_TypeError,
1117+
"one character bytes, bytearray, or an integer "
1118+
"in range(256) expected, not bytearray of length %zd",
1119+
PyByteArray_GET_SIZE(value));
1120+
return NULL;
1121+
}
11081122
*(char *)ptr = PyByteArray_AS_STRING(value)[0];
11091123
_RET(value);
11101124
}
1111-
if (PyLong_Check(value))
1112-
{
1113-
long longval = PyLong_AsLong(value);
1114-
if (longval < 0 || longval >= 256)
1115-
goto error;
1125+
if (PyLong_Check(value)) {
1126+
int overflow;
1127+
long longval = PyLong_AsLongAndOverflow(value, &overflow);
1128+
if (longval == -1 && PyErr_Occurred()) {
1129+
return NULL;
1130+
}
1131+
if (overflow || longval < 0 || longval >= 256) {
1132+
PyErr_SetString(PyExc_TypeError, "integer not in range(256)");
1133+
return NULL;
1134+
}
11161135
*(char *)ptr = (char)longval;
11171136
_RET(value);
11181137
}
1119-
error:
11201138
PyErr_Format(PyExc_TypeError,
1121-
"one character bytes, bytearray or integer expected");
1139+
"one character bytes, bytearray, or an integer "
1140+
"in range(256) expected, not %T",
1141+
value);
11221142
return NULL;
11231143
}
11241144

@@ -1137,22 +1157,27 @@ u_set(void *ptr, PyObject *value, Py_ssize_t size)
11371157
wchar_t chars[2];
11381158
if (!PyUnicode_Check(value)) {
11391159
PyErr_Format(PyExc_TypeError,
1140-
"unicode string expected instead of %s instance",
1141-
Py_TYPE(value)->tp_name);
1160+
"a unicode character expected, not instance of %T",
1161+
value);
11421162
return NULL;
1143-
} else
1144-
Py_INCREF(value);
1163+
}
11451164

11461165
len = PyUnicode_AsWideChar(value, chars, 2);
11471166
if (len != 1) {
1148-
Py_DECREF(value);
1149-
PyErr_SetString(PyExc_TypeError,
1150-
"one character unicode string expected");
1167+
if (PyUnicode_GET_LENGTH(value) != 1) {
1168+
PyErr_Format(PyExc_TypeError,
1169+
"a unicode character expected, not a string of length %zd",
1170+
PyUnicode_GET_LENGTH(value));
1171+
}
1172+
else {
1173+
PyErr_Format(PyExc_TypeError,
1174+
"the string %A cannot be converted to a single wchar_t character",
1175+
value);
1176+
}
11511177
return NULL;
11521178
}
11531179

11541180
*(wchar_t *)ptr = chars[0];
1155-
Py_DECREF(value);
11561181

11571182
_RET(value);
11581183
}

Modules/_cursesmodule.c

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -233,13 +233,20 @@ static int
233233
PyCurses_ConvertToChtype(PyCursesWindowObject *win, PyObject *obj, chtype *ch)
234234
{
235235
long value;
236-
if(PyBytes_Check(obj) && PyBytes_Size(obj) == 1) {
236+
if (PyBytes_Check(obj)) {
237+
if (PyBytes_GET_SIZE(obj) != 1) {
238+
PyErr_Format(PyExc_TypeError,
239+
"expect int or bytes or str of length 1, "
240+
"got a bytes of length %zd",
241+
PyBytes_GET_SIZE(obj));
242+
return 0;
243+
}
237244
value = (unsigned char)PyBytes_AsString(obj)[0];
238245
}
239246
else if (PyUnicode_Check(obj)) {
240-
if (PyUnicode_GetLength(obj) != 1) {
247+
if (PyUnicode_GET_LENGTH(obj) != 1) {
241248
PyErr_Format(PyExc_TypeError,
242-
"expect bytes or str of length 1, or int, "
249+
"expect int or bytes or str of length 1, "
243250
"got a str of length %zi",
244251
PyUnicode_GET_LENGTH(obj));
245252
return 0;
@@ -272,7 +279,7 @@ PyCurses_ConvertToChtype(PyCursesWindowObject *win, PyObject *obj, chtype *ch)
272279
}
273280
else {
274281
PyErr_Format(PyExc_TypeError,
275-
"expect bytes or str of length 1, or int, got %s",
282+
"expect int or bytes or str of length 1, got %s",
276283
Py_TYPE(obj)->tp_name);
277284
return 0;
278285
}
@@ -315,7 +322,7 @@ PyCurses_ConvertToCchar_t(PyCursesWindowObject *win, PyObject *obj,
315322
#ifdef HAVE_NCURSESW
316323
if (PyUnicode_AsWideChar(obj, buffer, 2) != 1) {
317324
PyErr_Format(PyExc_TypeError,
318-
"expect bytes or str of length 1, or int, "
325+
"expect int or bytes or str of length 1, "
319326
"got a str of length %zi",
320327
PyUnicode_GET_LENGTH(obj));
321328
return 0;
@@ -326,7 +333,14 @@ PyCurses_ConvertToCchar_t(PyCursesWindowObject *win, PyObject *obj,
326333
return PyCurses_ConvertToChtype(win, obj, ch);
327334
#endif
328335
}
329-
else if(PyBytes_Check(obj) && PyBytes_Size(obj) == 1) {
336+
else if (PyBytes_Check(obj)) {
337+
if (PyBytes_GET_SIZE(obj) != 1) {
338+
PyErr_Format(PyExc_TypeError,
339+
"expect int or bytes or str of length 1, "
340+
"got a bytes of length %zd",
341+
PyBytes_GET_SIZE(obj));
342+
return 0;
343+
}
330344
value = (unsigned char)PyBytes_AsString(obj)[0];
331345
}
332346
else if (PyLong_CheckExact(obj)) {
@@ -340,7 +354,7 @@ PyCurses_ConvertToCchar_t(PyCursesWindowObject *win, PyObject *obj,
340354
}
341355
else {
342356
PyErr_Format(PyExc_TypeError,
343-
"expect bytes or str of length 1, or int, got %s",
357+
"expect int or bytes or str of length 1, got %s",
344358
Py_TYPE(obj)->tp_name);
345359
return 0;
346360
}
@@ -4443,7 +4457,7 @@ PyCurses_ConvertToWchar_t(PyObject *obj,
44434457
wchar_t buffer[2];
44444458
if (PyUnicode_AsWideChar(obj, buffer, 2) != 1) {
44454459
PyErr_Format(PyExc_TypeError,
4446-
"expect str of length 1 or int, "
4460+
"expect int or str of length 1, "
44474461
"got a str of length %zi",
44484462
PyUnicode_GET_LENGTH(obj));
44494463
return 0;
@@ -4470,7 +4484,7 @@ PyCurses_ConvertToWchar_t(PyObject *obj,
44704484
}
44714485
else {
44724486
PyErr_Format(PyExc_TypeError,
4473-
"expect str of length 1 or int, got %s",
4487+
"expect int or str of length 1, got %s",
44744488
Py_TYPE(obj)->tp_name);
44754489
return 0;
44764490
}

Modules/arraymodule.c

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -260,20 +260,32 @@ u_getitem(arrayobject *ap, Py_ssize_t i)
260260
static int
261261
u_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
262262
{
263-
PyObject *u;
264-
if (!PyArg_Parse(v, "U;array item must be unicode character", &u)) {
263+
if (!PyUnicode_Check(v)) {
264+
PyErr_Format(PyExc_TypeError,
265+
"array item must be a unicode character, not %T",
266+
v);
265267
return -1;
266268
}
267269

268-
Py_ssize_t len = PyUnicode_AsWideChar(u, NULL, 0);
270+
Py_ssize_t len = PyUnicode_AsWideChar(v, NULL, 0);
269271
if (len != 2) {
270-
PyErr_SetString(PyExc_TypeError,
271-
"array item must be unicode character");
272+
if (PyUnicode_GET_LENGTH(v) != 1) {
273+
PyErr_Format(PyExc_TypeError,
274+
"array item must be a unicode character, "
275+
"not a string of length %zd",
276+
PyUnicode_GET_LENGTH(v));
277+
}
278+
else {
279+
PyErr_Format(PyExc_TypeError,
280+
"string %A cannot be converted to "
281+
"a single wchar_t character",
282+
v);
283+
}
272284
return -1;
273285
}
274286

275287
wchar_t w;
276-
len = PyUnicode_AsWideChar(u, &w, 1);
288+
len = PyUnicode_AsWideChar(v, &w, 1);
277289
assert(len == 1);
278290

279291
if (i >= 0) {
@@ -291,19 +303,23 @@ w_getitem(arrayobject *ap, Py_ssize_t i)
291303
static int
292304
w_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
293305
{
294-
PyObject *u;
295-
if (!PyArg_Parse(v, "U;array item must be unicode character", &u)) {
306+
if (!PyUnicode_Check(v)) {
307+
PyErr_Format(PyExc_TypeError,
308+
"array item must be a unicode character, not %T",
309+
v);
296310
return -1;
297311
}
298312

299-
if (PyUnicode_GetLength(u) != 1) {
300-
PyErr_SetString(PyExc_TypeError,
301-
"array item must be unicode character");
313+
if (PyUnicode_GET_LENGTH(v) != 1) {
314+
PyErr_Format(PyExc_TypeError,
315+
"array item must be a unicode character, "
316+
"not a string of length %zd",
317+
PyUnicode_GET_LENGTH(v));
302318
return -1;
303319
}
304320

305321
if (i >= 0) {
306-
((Py_UCS4 *)ap->ob_item)[i] = PyUnicode_READ_CHAR(u, 0);
322+
((Py_UCS4 *)ap->ob_item)[i] = PyUnicode_READ_CHAR(v, 0);
307323
}
308324
return 0;
309325
}

0 commit comments

Comments
 (0)