[Python-checkins] gh-99266: ctypes: Preserve more detailed exception in `ArgumentError`

kumaraditya303 webhook-mailer at python.org
Sat Jan 21 08:44:48 EST 2023


https://github.com/python/cpython/commit/b4e11a7985a3bc116596c63d1e5f8bbd653041b9
commit: b4e11a7985a3bc116596c63d1e5f8bbd653041b9
branch: main
author: Kamil Turek <kamil.turek at hotmail.com>
committer: kumaraditya303 <59607654+kumaraditya303 at users.noreply.github.com>
date: 2023-01-21T19:14:43+05:30
summary:

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

Co-authored-by: Kumar Aditya <59607654+kumaraditya303 at users.noreply.github.com>

files:
A Misc/NEWS.d/next/Library/2022-11-24-21-52-31.gh-issue-99266.88GcV9.rst
M Doc/library/ctypes.rst
M Lib/test/test_ctypes/test_functions.py
M Lib/test/test_ctypes/test_parameters.py
M Modules/_ctypes/_ctypes.c

diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst
index 50ab29375623..4de5c820f2c6 100644
--- a/Doc/library/ctypes.rst
+++ b/Doc/library/ctypes.rst
@@ -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
diff --git a/Lib/test/test_ctypes/test_functions.py b/Lib/test/test_ctypes/test_functions.py
index 95633dfa8b38..703bd2c601cc 100644
--- a/Lib/test/test_ctypes/test_functions.py
+++ b/Lib/test/test_ctypes/test_functions.py
@@ -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
@@ -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
diff --git a/Lib/test/test_ctypes/test_parameters.py b/Lib/test/test_ctypes/test_parameters.py
index 84839d9c6a96..22d290db1bcb 100644
--- a/Lib/test/test_ctypes/test_parameters.py
+++ b/Lib/test/test_ctypes/test_parameters.py
@@ -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)
diff --git a/Misc/NEWS.d/next/Library/2022-11-24-21-52-31.gh-issue-99266.88GcV9.rst b/Misc/NEWS.d/next/Library/2022-11-24-21-52-31.gh-issue-99266.88GcV9.rst
new file mode 100644
index 000000000000..97e9569e40a9
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-11-24-21-52-31.gh-issue-99266.88GcV9.rst
@@ -0,0 +1 @@
+Preserve more detailed error messages in :mod:`ctypes`.
diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index 4ce6433a2e45..272cafb5a9a3 100644
--- a/Modules/_ctypes/_ctypes.c
+++ b/Modules/_ctypes/_ctypes.c
@@ -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;
 }
 



More information about the Python-checkins mailing list