From 2ddb9aae3928786057452fe8d6e57ac890f57c65 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Thu, 24 Nov 2022 21:17:32 +0100 Subject: [PATCH 01/15] Preserve the original exception --- Modules/_ctypes/_ctypes.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 4ce6433a2e45b3f..b10479ca890b665 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,6 +2231,8 @@ PyCSimpleType_from_param(PyObject *type, PyObject *value) parg->obj = fd->setfunc(&parg->value, value, 0); if (parg->obj) return (PyObject *)parg; + + PyErr_Fetch(&exc, &val, &tb); PyErr_Clear(); Py_DECREF(parg); @@ -2244,10 +2247,12 @@ PyCSimpleType_from_param(PyObject *type, PyObject *value) value = PyCSimpleType_from_param(type, as_parameter); _Py_LeaveRecursiveCall(); Py_DECREF(as_parameter); + Py_DECREF(exc); + Py_DECREF(val); + Py_DECREF(tb); return value; } - PyErr_SetString(PyExc_TypeError, - "wrong type"); + PyErr_Restore(exc, val, tb); return NULL; } From af44b0e4ec8b3908f70626ab856f597a7edaca17 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Thu, 24 Nov 2022 21:43:40 +0100 Subject: [PATCH 02/15] Convert the example to a doctest --- Doc/library/ctypes.rst | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index 50ab29375623552..4de5c820f2c6ac8 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 "", line 1, in - 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 From 8c8a002e62bef52bdeb62580d331fc3d966da344 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Thu, 24 Nov 2022 21:52:45 +0100 Subject: [PATCH 03/15] Add news entry --- .../next/Library/2022-11-24-21-52-31.gh-issue-99266.88GcV9.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2022-11-24-21-52-31.gh-issue-99266.88GcV9.rst 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 000000000000000..b7508d04019cc27 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-11-24-21-52-31.gh-issue-99266.88GcV9.rst @@ -0,0 +1 @@ +Preserve a more detailed exception in ``_ctypes._SimpleCData.from_param``. From 2e29f8a4a7789ff30ac44a95abb7f2600acc72aa Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Thu, 24 Nov 2022 22:30:44 +0100 Subject: [PATCH 04/15] Restore the exception only if present --- Modules/_ctypes/_ctypes.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index b10479ca890b665..b18a24c57f49028 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -2247,12 +2247,16 @@ PyCSimpleType_from_param(PyObject *type, PyObject *value) value = PyCSimpleType_from_param(type, as_parameter); _Py_LeaveRecursiveCall(); Py_DECREF(as_parameter); - Py_DECREF(exc); - Py_DECREF(val); - Py_DECREF(tb); + Py_XDECREF(exc); + Py_XDECREF(val); + Py_XDECREF(tb); return value; } - PyErr_Restore(exc, val, tb); + if (exc) + PyErr_Restore(exc, val, tb); + else + PyErr_SetString(PyExc_TypeError, + "wrong type"); return NULL; } From 7cc2c34e91085d8988714cd2c5bb1897c1618a38 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Thu, 24 Nov 2022 22:45:27 +0100 Subject: [PATCH 05/15] Remove redundant PyErr_Clear --- Modules/_ctypes/_ctypes.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index b18a24c57f49028..5fc6725c48c193f 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -2231,9 +2231,7 @@ PyCSimpleType_from_param(PyObject *type, PyObject *value) parg->obj = fd->setfunc(&parg->value, value, 0); if (parg->obj) return (PyObject *)parg; - PyErr_Fetch(&exc, &val, &tb); - PyErr_Clear(); Py_DECREF(parg); if (_PyObject_LookupAttr(value, &_Py_ID(_as_parameter_), &as_parameter) < 0) { From 091eed5c9d0be8ad9f7870b89e380fafa3f20949 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Fri, 20 Jan 2023 23:16:24 +0100 Subject: [PATCH 06/15] Add unit tests --- Lib/test/test_ctypes/test_functions.py | 17 +++++++++++++++++ Lib/test/test_ctypes/test_parameters.py | 10 ++++++++++ 2 files changed, 27 insertions(+) diff --git a/Lib/test/test_ctypes/test_functions.py b/Lib/test/test_ctypes/test_functions.py index 95633dfa8b38fde..ce4a4391a3110c0 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 diff --git a/Lib/test/test_ctypes/test_parameters.py b/Lib/test/test_ctypes/test_parameters.py index 84839d9c6a96d9b..c9f940b23f7e0b8 100644 --- a/Lib/test/test_ctypes/test_parameters.py +++ b/Lib/test/test_ctypes/test_parameters.py @@ -78,6 +78,16 @@ 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") + + def test_int_pointers(self): from ctypes import c_short, c_uint, c_int, c_long, POINTER, pointer LPINT = POINTER(c_int) From 556b86f8666d8d877374088068c93d78d705f289 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Fri, 20 Jan 2023 23:18:50 +0100 Subject: [PATCH 07/15] Improve the news message --- .../next/Library/2022-11-24-21-52-31.gh-issue-99266.88GcV9.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index b7508d04019cc27..1477fe7ed2fea6b 100644 --- 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 @@ -1 +1 @@ -Preserve a more detailed exception in ``_ctypes._SimpleCData.from_param``. +Make ctypes fundamental data types preserve a more detail exception in `from_param`. From ae11d8da971b379fbc887a7115b8135249693958 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Fri, 20 Jan 2023 23:21:51 +0100 Subject: [PATCH 08/15] Fix the news entry --- .../next/Library/2022-11-24-21-52-31.gh-issue-99266.88GcV9.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 1477fe7ed2fea6b..8d56f40d5f31d1c 100644 --- 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 @@ -1 +1 @@ -Make ctypes fundamental data types preserve a more detail exception in `from_param`. +Make ctypes fundamental data types preserve a more detail exception in ```from_param``. From 2096a88a18b7d4754e6fa4916b6877db849b0298 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Fri, 20 Jan 2023 23:39:39 +0100 Subject: [PATCH 09/15] Add more tests --- Lib/test/test_ctypes/test_functions.py | 12 ++++++++++++ Lib/test/test_ctypes/test_parameters.py | 16 +++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_ctypes/test_functions.py b/Lib/test/test_ctypes/test_functions.py index ce4a4391a3110c0..33ed648f01fb810 100644 --- a/Lib/test/test_ctypes/test_functions.py +++ b/Lib/test/test_ctypes/test_functions.py @@ -79,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 c9f940b23f7e0b8..58bfcca830353a4 100644 --- a/Lib/test/test_ctypes/test_parameters.py +++ b/Lib/test/test_ctypes/test_parameters.py @@ -83,10 +83,24 @@ 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") + @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 From 85929f943cfa2470c62e4ab746c48d416f782e44 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sat, 21 Jan 2023 00:02:56 +0100 Subject: [PATCH 10/15] Remove whitespaces --- Lib/test/test_ctypes/test_functions.py | 2 +- Lib/test/test_ctypes/test_parameters.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/test/test_ctypes/test_functions.py b/Lib/test/test_ctypes/test_functions.py index 33ed648f01fb810..703bd2c601ccf66 100644 --- a/Lib/test/test_ctypes/test_functions.py +++ b/Lib/test/test_ctypes/test_functions.py @@ -65,7 +65,7 @@ def callback(*args): with self.assertRaises(ArgumentError) as cm: callback(b"abc") - + self.assertEqual(str(cm.exception), "argument 1: TypeError: one character bytes, " "bytearray or integer expected") diff --git a/Lib/test/test_ctypes/test_parameters.py b/Lib/test/test_ctypes/test_parameters.py index 58bfcca830353a4..22d290db1bcb7d5 100644 --- a/Lib/test/test_ctypes/test_parameters.py +++ b/Lib/test/test_ctypes/test_parameters.py @@ -100,7 +100,6 @@ def test_c_wchar(self): 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 From 102cc82d44c447a14975122c250d25af79edecb1 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sat, 21 Jan 2023 01:07:37 +0100 Subject: [PATCH 11/15] Remove redundant tick --- .../next/Library/2022-11-24-21-52-31.gh-issue-99266.88GcV9.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 8d56f40d5f31d1c..5dd66e2f388c0e8 100644 --- 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 @@ -1 +1 @@ -Make ctypes fundamental data types preserve a more detail exception in ```from_param``. +Make ctypes fundamental data types preserve a more detail exception in ``from_param``. From 149d66f5d16018260f9842f3e848dea186240854 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sat, 21 Jan 2023 10:01:07 +0100 Subject: [PATCH 12/15] Add missing reference decrementations --- Modules/_ctypes/_ctypes.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 5fc6725c48c193f..c1c1587d0c44f0d 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -2240,6 +2240,9 @@ PyCSimpleType_from_param(PyObject *type, PyObject *value) 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); From 924e7bf0b889b7fb990592fa0be1b980c105b0d8 Mon Sep 17 00:00:00 2001 From: Kamil Turek Date: Sat, 21 Jan 2023 10:48:29 +0100 Subject: [PATCH 13/15] Add another missing reference decrementation --- Modules/_ctypes/_ctypes.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index c1c1587d0c44f0d..b8d06d5e8137d22 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -2235,6 +2235,9 @@ PyCSimpleType_from_param(PyObject *type, PyObject *value) 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) { From 441e1cba56858830937ebe203c04ac93ad31b43d Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Sat, 21 Jan 2023 18:40:29 +0530 Subject: [PATCH 14/15] fix formatting --- Modules/_ctypes/_ctypes.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index b8d06d5e8137d22..272cafb5a9a3811 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -2256,11 +2256,12 @@ PyCSimpleType_from_param(PyObject *type, PyObject *value) Py_XDECREF(tb); return value; } - if (exc) + if (exc) { PyErr_Restore(exc, val, tb); - else - PyErr_SetString(PyExc_TypeError, - "wrong type"); + } + else { + PyErr_SetString(PyExc_TypeError, "wrong type"); + } return NULL; } From 9536eb44ab1456760f79728277483bef273c2ada Mon Sep 17 00:00:00 2001 From: Kumar Aditya <59607654+kumaraditya303@users.noreply.github.com> Date: Sat, 21 Jan 2023 18:44:25 +0530 Subject: [PATCH 15/15] Update Misc/NEWS.d/next/Library/2022-11-24-21-52-31.gh-issue-99266.88GcV9.rst --- .../next/Library/2022-11-24-21-52-31.gh-issue-99266.88GcV9.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 5dd66e2f388c0e8..97e9569e40a9bf7 100644 --- 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 @@ -1 +1 @@ -Make ctypes fundamental data types preserve a more detail exception in ``from_param``. +Preserve more detailed error messages in :mod:`ctypes`.