8000 gh-99266: ctypes: Preserve a more detailed exception in _SimpleCData.from_param by kamilturek · Pull Request #99760 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-99266: ctypes: Preserve a more detailed exception in _SimpleCData.from_param #99760

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jan 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions Doc/library/ctypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
29 changes: 29 additions & 0 deletions Lib/test/test_ctypes/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,23 @@ class X(object, _SimpleCData):
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
Expand All @@ -62,6 +79,18 @@ def test_wchar_parm(self):
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
Expand Down
23 changes: 23 additions & 0 deletions Lib/test/test_ctypes/test_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,29 @@ def test_cw_strings(self):
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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Preserve more detailed error messages in :mod:`ctypes`.
20 changes: 17 additions & 3 deletions Modules/_ctypes/_ctypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
}

Expand Down
0