]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-99266: ctypes: Preserve more detailed exception in `ArgumentError`
authorKamil Turek <kamil.turek@hotmail.com>
Sat, 21 Jan 2023 13:44:43 +0000 (14:44 +0100)
committerGitHub <noreply@github.com>
Sat, 21 Jan 2023 13:44:43 +0000 (19:14 +0530)
Co-authored-by: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com>
Doc/library/ctypes.rst
Lib/test/test_ctypes/test_functions.py
Lib/test/test_ctypes/test_parameters.py
Misc/NEWS.d/next/Library/2022-11-24-21-52-31.gh-issue-99266.88GcV9.rst [new file with mode: 0644]
Modules/_ctypes/_ctypes.c

index 50ab2937562355224a9e4157b622645b19f15206..4de5c820f2c6ac89c0c7c61f8aba9c386ce41954 100644 (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
index 95633dfa8b38fdec61a7c2fe9cce48d896cdc19b..703bd2c601ccf66173dc7ac7da0cce0ad390abdf 100644 (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
index 84839d9c6a96d9ba1163a981414747d9f4b585e2..22d290db1bcb7d54a891202719668aa5e9a497e2 100644 (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)
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 (file)
index 0000000..97e9569
--- /dev/null
@@ -0,0 +1 @@
+Preserve more detailed error messages in :mod:`ctypes`.
index 4ce6433a2e45b3fe8456d0151bc3eb97f78134e0..272cafb5a9a3811ea33f890474457c559566c9ca 100644 (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;
 }