gh-99266: ctypes: Preserve more detailed exception in `ArgumentError`

Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
This commit is contained in:
Kamil Turek 2023-01-21 14:44:43 +01:00 committed by GitHub
parent 13566a37c2
commit b4e11a7985
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 83 additions and 7 deletions

View File

@ -466,6 +466,14 @@ integer, string, bytes, a :mod:`ctypes` instance, or an object with an
Return types
^^^^^^^^^^^^
.. testsetup::
from ctypes import CDLL, c_char, c_char_p
from ctypes.util import find_library
libc = CDLL(find_library('c'))
strchr = libc.strchr
By default functions are assumed to return the C :c:expr:`int` type. Other
return types can be specified by setting the :attr:`restype` attribute of the
function object.
@ -502,18 +510,19 @@ If you want to avoid the ``ord("x")`` calls above, you can set the
:attr:`argtypes` attribute, and the second argument will be converted from a
single character Python bytes object into a C char::
.. doctest::
>>> strchr.restype = c_char_p
>>> strchr.argtypes = [c_char_p, c_char]
>>> strchr(b"abcdef", b"d")
'def'
b'def'
>>> strchr(b"abcdef", b"def")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ArgumentError: argument 2: TypeError: one character string expected
ctypes.ArgumentError: argument 2: TypeError: one character bytes, bytearray or integer expected
>>> print(strchr(b"abcdef", b"x"))
None
>>> strchr(b"abcdef", b"d")
'def'
b'def'
>>>
You can also use a callable Python object (a function or a class for example) as

View File

@ -54,6 +54,23 @@ class FunctionTestCase(unittest.TestCase):
class X(object, Structure):
_fields_ = []
def test_c_char_parm(self):
proto = CFUNCTYPE(c_int, c_char)
def callback(*args):
return 0
callback = proto(callback)
self.assertEqual(callback(b"a"), 0)
with self.assertRaises(ArgumentError) as cm:
callback(b"abc")
self.assertEqual(str(cm.exception),
"argument 1: TypeError: one character bytes, "
"bytearray or integer expected")
@need_symbol('c_wchar')
def test_wchar_parm(self):
f = dll._testfunc_i_bhilfd
@ -62,6 +79,18 @@ class FunctionTestCase(unittest.TestCase):
self.assertEqual(result, 139)
self.assertEqual(type(result), int)
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")
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")
@need_symbol('c_wchar')
def test_wchar_result(self):
f = dll._testfunc_i_bhilfd

View File

@ -78,6 +78,29 @@ class SimpleTypesTestCase(unittest.TestCase):
pa = c_wchar_p.from_param(c_wchar_p("123"))
self.assertEqual(type(pa), c_wchar_p)
def test_c_char(self):
from ctypes import c_char
with self.assertRaises(TypeError) as cm:
c_char.from_param(b"abc")
self.assertEqual(str(cm.exception),
"one character bytes, bytearray or integer expected")
@need_symbol('c_wchar')
def test_c_wchar(self):
from ctypes import c_wchar
with self.assertRaises(TypeError) as cm:
c_wchar.from_param("abc")
self.assertEqual(str(cm.exception),
"one character unicode string expected")
with self.assertRaises(TypeError) as cm:
c_wchar.from_param(123)
self.assertEqual(str(cm.exception),
"unicode string expected instead of int instance")
def test_int_pointers(self):
from ctypes import c_short, c_uint, c_int, c_long, POINTER, pointer
LPINT = POINTER(c_int)

View File

@ -0,0 +1 @@
Preserve more detailed error messages in :mod:`ctypes`.

View File

@ -2197,6 +2197,7 @@ PyCSimpleType_from_param(PyObject *type, PyObject *value)
struct fielddesc *fd;
PyObject *as_parameter;
int res;
PyObject *exc, *val, *tb;
/* If the value is already an instance of the requested type,
we can use it as is */
@ -2230,24 +2231,37 @@ PyCSimpleType_from_param(PyObject *type, PyObject *value)
parg->obj = fd->setfunc(&parg->value, value, 0);
if (parg->obj)
return (PyObject *)parg;
PyErr_Clear();
PyErr_Fetch(&exc, &val, &tb);
Py_DECREF(parg);
if (_PyObject_LookupAttr(value, &_Py_ID(_as_parameter_), &as_parameter) < 0) {
Py_XDECREF(exc);
Py_XDECREF(val);
Py_XDECREF(tb);
return NULL;
}
if (as_parameter) {
if (_Py_EnterRecursiveCall("while processing _as_parameter_")) {
Py_DECREF(as_parameter);
Py_XDECREF(exc);
Py_XDECREF(val);
Py_XDECREF(tb);
return NULL;
}
value = PyCSimpleType_from_param(type, as_parameter);
_Py_LeaveRecursiveCall();
Py_DECREF(as_parameter);
Py_XDECREF(exc);
Py_XDECREF(val);
Py_XDECREF(tb);
return value;
}
PyErr_SetString(PyExc_TypeError,
"wrong type");
if (exc) {
PyErr_Restore(exc, val, tb);
}
else {
PyErr_SetString(PyExc_TypeError, "wrong type");
}
return NULL;
}