Skip to content

Commit

Permalink
pythongh-117557: Improve error messages when a string, bytes or bytea…
Browse files Browse the repository at this point in the history
…rray of length 1 are expected (pythonGH-117631)
  • Loading branch information
serhiy-storchaka authored May 28, 2024
1 parent bf08f0a commit b313cc6
Show file tree
Hide file tree
Showing 18 changed files with 811 additions and 161 deletions.
261 changes: 230 additions & 31 deletions Lib/test/clinic.test.c

Large diffs are not rendered by default.

26 changes: 20 additions & 6 deletions Lib/test/test_ctypes/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import sys
import unittest
from ctypes import (CDLL, Structure, Array, CFUNCTYPE,
byref, POINTER, pointer, ArgumentError,
byref, POINTER, pointer, ArgumentError, sizeof,
c_char, c_wchar, c_byte, c_char_p, c_wchar_p,
c_short, c_int, c_long, c_longlong, c_void_p,
c_float, c_double, c_longdouble)
Expand Down Expand Up @@ -72,7 +72,8 @@ def callback(*args):

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

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

with self.assertRaises(ArgumentError) as cm:
f(1, "abc", 3, 4, 5.0, 6.0)
self.assertEqual(str(cm.exception),
"argument 2: TypeError: one character unicode string "
"expected")
"argument 2: TypeError: a unicode character expected, "
"not a string of length 3")

with self.assertRaises(ArgumentError) as cm:
f(1, "", 3, 4, 5.0, 6.0)
self.assertEqual(str(cm.exception),
"argument 2: TypeError: a unicode character expected, "
"not a string of length 0")

if sizeof(c_wchar) < 4:
with self.assertRaises(ArgumentError) as cm:
f(1, "\U0001f40d", 3, 4, 5.0, 6.0)
self.assertEqual(str(cm.exception),
"argument 2: TypeError: the string '\\U0001f40d' "
"cannot be converted to a single wchar_t character")

def test_c_char_p_parm(self):
"""Test the error message when converting an incompatible type to c_char_p."""
Expand Down
22 changes: 18 additions & 4 deletions Lib/test/test_ctypes/test_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from ctypes import (CDLL, PyDLL, ArgumentError,
Structure, Array, Union,
_Pointer, _SimpleCData, _CFuncPtr,
POINTER, pointer, byref,
POINTER, pointer, byref, sizeof,
c_void_p, c_char_p, c_wchar_p, py_object,
c_bool,
c_char, c_wchar,
Expand Down Expand Up @@ -87,19 +87,33 @@ def test_c_char(self):
with self.assertRaises(TypeError) as cm:
c_char.from_param(b"abc")
self.assertEqual(str(cm.exception),
"one character bytes, bytearray or integer expected")
"one character bytes, bytearray, or an integer "
"in range(256) expected, not bytes of length 3")

def test_c_wchar(self):
with self.assertRaises(TypeError) as cm:
c_wchar.from_param("abc")
self.assertEqual(str(cm.exception),
"one character unicode string expected")
"a unicode character expected, not a string of length 3")

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

with self.assertRaises(TypeError) as cm:
c_wchar.from_param(123)
self.assertEqual(str(cm.exception),
"unicode string expected instead of int instance")
"a unicode character expected, not instance of int")

if sizeof(c_wchar) < 4:
with self.assertRaises(TypeError) as cm:
c_wchar.from_param('\U0001f40d')
self.assertEqual(str(cm.exception),
"the string '\\U0001f40d' cannot be converted to "
"a single wchar_t character")



def test_int_pointers(self):
LPINT = POINTER(c_int)
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_str.py
Original file line number Diff line number Diff line change
Expand Up @@ -1578,7 +1578,7 @@ def __int__(self):
self.assertRaisesRegex(TypeError, '%u format: a real number is required, not complex', operator.mod, '%u', 3j)
self.assertRaisesRegex(TypeError, '%i format: a real number is required, not complex', operator.mod, '%i', 2j)
self.assertRaisesRegex(TypeError, '%d format: a real number is required, not complex', operator.mod, '%d', 1j)
self.assertRaisesRegex(TypeError, '%c requires int or char', operator.mod, '%c', pi)
self.assertRaisesRegex(TypeError, r'%c requires an int or a unicode character, not .*\.PseudoFloat', operator.mod, '%c', pi)

class RaisingNumber:
def __int__(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Improve error messages when a string, bytes or bytearray object of length 1
is expected.
59 changes: 42 additions & 17 deletions Modules/_ctypes/cfield.c
Original file line number Diff line number Diff line change
Expand Up @@ -1100,25 +1100,45 @@ O_set(void *ptr, PyObject *value, Py_ssize_t size)
static PyObject *
c_set(void *ptr, PyObject *value, Py_ssize_t size)
{
if (PyBytes_Check(value) && PyBytes_GET_SIZE(value) == 1) {
if (PyBytes_Check(value)) {
if (PyBytes_GET_SIZE(value) != 1) {
PyErr_Format(PyExc_TypeError,
"one character bytes, bytearray, or an integer "
"in range(256) expected, not bytes of length %zd",
PyBytes_GET_SIZE(value));
return NULL;
}
*(char *)ptr = PyBytes_AS_STRING(value)[0];
_RET(value);
}
if (PyByteArray_Check(value) && PyByteArray_GET_SIZE(value) == 1) {
if (PyByteArray_Check(value)) {
if (PyByteArray_GET_SIZE(value) != 1) {
PyErr_Format(PyExc_TypeError,
"one character bytes, bytearray, or an integer "
"in range(256) expected, not bytearray of length %zd",
PyByteArray_GET_SIZE(value));
return NULL;
}
*(char *)ptr = PyByteArray_AS_STRING(value)[0];
_RET(value);
}
if (PyLong_Check(value))
{
long longval = PyLong_AsLong(value);
if (longval < 0 || longval >= 256)
goto error;
if (PyLong_Check(value)) {
int overflow;
long longval = PyLong_AsLongAndOverflow(value, &overflow);
if (longval == -1 && PyErr_Occurred()) {
return NULL;
}
if (overflow || longval < 0 || longval >= 256) {
PyErr_SetString(PyExc_TypeError, "integer not in range(256)");
return NULL;
}
*(char *)ptr = (char)longval;
_RET(value);
}
error:
PyErr_Format(PyExc_TypeError,
"one character bytes, bytearray or integer expected");
"one character bytes, bytearray, or an integer "
"in range(256) expected, not %T",
value);
return NULL;
}

Expand All @@ -1137,22 +1157,27 @@ u_set(void *ptr, PyObject *value, Py_ssize_t size)
wchar_t chars[2];
if (!PyUnicode_Check(value)) {
PyErr_Format(PyExc_TypeError,
"unicode string expected instead of %s instance",
Py_TYPE(value)->tp_name);
"a unicode character expected, not instance of %T",
value);
return NULL;
} else
Py_INCREF(value);
}

len = PyUnicode_AsWideChar(value, chars, 2);
if (len != 1) {
Py_DECREF(value);
PyErr_SetString(PyExc_TypeError,
"one character unicode string expected");
if (PyUnicode_GET_LENGTH(value) != 1) {
PyErr_Format(PyExc_TypeError,
"a unicode character expected, not a string of length %zd",
PyUnicode_GET_LENGTH(value));
}
else {
PyErr_Format(PyExc_TypeError,
"the string %A cannot be converted to a single wchar_t character",
value);
}
return NULL;
}

*(wchar_t *)ptr = chars[0];
Py_DECREF(value);

_RET(value);
}
Expand Down
32 changes: 23 additions & 9 deletions Modules/_cursesmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -233,13 +233,20 @@ static int
PyCurses_ConvertToChtype(PyCursesWindowObject *win, PyObject *obj, chtype *ch)
{
long value;
if(PyBytes_Check(obj) && PyBytes_Size(obj) == 1) {
if (PyBytes_Check(obj)) {
if (PyBytes_GET_SIZE(obj) != 1) {
PyErr_Format(PyExc_TypeError,
"expect int or bytes or str of length 1, "
"got a bytes of length %zd",
PyBytes_GET_SIZE(obj));
return 0;
}
value = (unsigned char)PyBytes_AsString(obj)[0];
}
else if (PyUnicode_Check(obj)) {
if (PyUnicode_GetLength(obj) != 1) {
if (PyUnicode_GET_LENGTH(obj) != 1) {
PyErr_Format(PyExc_TypeError,
"expect bytes or str of length 1, or int, "
"expect int or bytes or str of length 1, "
"got a str of length %zi",
PyUnicode_GET_LENGTH(obj));
return 0;
Expand Down Expand Up @@ -272,7 +279,7 @@ PyCurses_ConvertToChtype(PyCursesWindowObject *win, PyObject *obj, chtype *ch)
}
else {
PyErr_Format(PyExc_TypeError,
"expect bytes or str of length 1, or int, got %s",
"expect int or bytes or str of length 1, got %s",
Py_TYPE(obj)->tp_name);
return 0;
}
Expand Down Expand Up @@ -315,7 +322,7 @@ PyCurses_ConvertToCchar_t(PyCursesWindowObject *win, PyObject *obj,
#ifdef HAVE_NCURSESW
if (PyUnicode_AsWideChar(obj, buffer, 2) != 1) {
PyErr_Format(PyExc_TypeError,
"expect bytes or str of length 1, or int, "
"expect int or bytes or str of length 1, "
"got a str of length %zi",
PyUnicode_GET_LENGTH(obj));
return 0;
Expand All @@ -326,7 +333,14 @@ PyCurses_ConvertToCchar_t(PyCursesWindowObject *win, PyObject *obj,
return PyCurses_ConvertToChtype(win, obj, ch);
#endif
}
else if(PyBytes_Check(obj) && PyBytes_Size(obj) == 1) {
else if (PyBytes_Check(obj)) {
if (PyBytes_GET_SIZE(obj) != 1) {
PyErr_Format(PyExc_TypeError,
"expect int or bytes or str of length 1, "
"got a bytes of length %zd",
PyBytes_GET_SIZE(obj));
return 0;
}
value = (unsigned char)PyBytes_AsString(obj)[0];
}
else if (PyLong_CheckExact(obj)) {
Expand All @@ -340,7 +354,7 @@ PyCurses_ConvertToCchar_t(PyCursesWindowObject *win, PyObject *obj,
}
else {
PyErr_Format(PyExc_TypeError,
"expect bytes or str of length 1, or int, got %s",
"expect int or bytes or str of length 1, got %s",
Py_TYPE(obj)->tp_name);
return 0;
}
Expand Down Expand Up @@ -4443,7 +4457,7 @@ PyCurses_ConvertToWchar_t(PyObject *obj,
wchar_t buffer[2];
if (PyUnicode_AsWideChar(obj, buffer, 2) != 1) {
PyErr_Format(PyExc_TypeError,
"expect str of length 1 or int, "
"expect int or str of length 1, "
"got a str of length %zi",
PyUnicode_GET_LENGTH(obj));
return 0;
Expand All @@ -4470,7 +4484,7 @@ PyCurses_ConvertToWchar_t(PyObject *obj,
}
else {
PyErr_Format(PyExc_TypeError,
"expect str of length 1 or int, got %s",
"expect int or str of length 1, got %s",
Py_TYPE(obj)->tp_name);
return 0;
}
Expand Down
40 changes: 28 additions & 12 deletions Modules/arraymodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -260,20 +260,32 @@ u_getitem(arrayobject *ap, Py_ssize_t i)
static int
u_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
{
PyObject *u;
if (!PyArg_Parse(v, "U;array item must be unicode character", &u)) {
if (!PyUnicode_Check(v)) {
PyErr_Format(PyExc_TypeError,
"array item must be a unicode character, not %T",
v);
return -1;
}

Py_ssize_t len = PyUnicode_AsWideChar(u, NULL, 0);
Py_ssize_t len = PyUnicode_AsWideChar(v, NULL, 0);
if (len != 2) {
PyErr_SetString(PyExc_TypeError,
"array item must be unicode character");
if (PyUnicode_GET_LENGTH(v) != 1) {
PyErr_Format(PyExc_TypeError,
"array item must be a unicode character, "
"not a string of length %zd",
PyUnicode_GET_LENGTH(v));
}
else {
PyErr_Format(PyExc_TypeError,
"string %A cannot be converted to "
"a single wchar_t character",
v);
}
return -1;
}

wchar_t w;
len = PyUnicode_AsWideChar(u, &w, 1);
len = PyUnicode_AsWideChar(v, &w, 1);
assert(len == 1);

if (i >= 0) {
Expand All @@ -291,19 +303,23 @@ w_getitem(arrayobject *ap, Py_ssize_t i)
static int
w_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
{
PyObject *u;
if (!PyArg_Parse(v, "U;array item must be unicode character", &u)) {
if (!PyUnicode_Check(v)) {
PyErr_Format(PyExc_TypeError,
"array item must be a unicode character, not %T",
v);
return -1;
}

if (PyUnicode_GetLength(u) != 1) {
PyErr_SetString(PyExc_TypeError,
"array item must be unicode character");
if (PyUnicode_GET_LENGTH(v) != 1) {
PyErr_Format(PyExc_TypeError,
"array item must be a unicode character, "
"not a string of length %zd",
PyUnicode_GET_LENGTH(v));
return -1;
}

if (i >= 0) {
((Py_UCS4 *)ap->ob_item)[i] = PyUnicode_READ_CHAR(u, 0);
((Py_UCS4 *)ap->ob_item)[i] = PyUnicode_READ_CHAR(v, 0);
}
return 0;
}
Expand Down
Loading

0 comments on commit b313cc6

Please sign in to comment.