From 1e45180133385bf0d8130d003e5c418ec82cb410 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 13 Apr 2026 06:40:29 +0300 Subject: [PATCH 1/7] gh-148464: Add missing ``__ctype_le/be__`` attributes for complex types in the ctype module --- Lib/test/test_ctypes/test_byteswap.py | 43 +++++++++++++++ ...-04-13-06-22-27.gh-issue-148464.Bj_NZy.rst | 3 ++ Modules/_ctypes/_ctypes.c | 12 ++++- Modules/_ctypes/cfield.c | 54 +++++++++++++++++++ 4 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-04-13-06-22-27.gh-issue-148464.Bj_NZy.rst diff --git a/Lib/test/test_ctypes/test_byteswap.py b/Lib/test/test_ctypes/test_byteswap.py index f14e1aa32e17ab..6a1bae14773d27 100644 --- a/Lib/test/test_ctypes/test_byteswap.py +++ b/Lib/test/test_ctypes/test_byteswap.py @@ -1,4 +1,5 @@ import binascii +import ctypes import math import struct import sys @@ -165,6 +166,48 @@ def test_endian_double(self): self.assertEqual(s.value, math.pi) self.assertEqual(bin(struct.pack(">d", math.pi)), bin(s)) + @unittest.skipUnless(hasattr(ctypes, 'c_float_complex'), "No complex types") + def test_endian_float_complex(self): + c_float_complex = ctypes.c_float_complex + if sys.byteorder == "little": + self.assertIs(c_float_complex.__ctype_le__, c_float_complex) + self.assertIs(c_float_complex.__ctype_be__.__ctype_le__, + c_float_complex) + else: + self.assertIs(c_float_complex.__ctype_be__, c_float_complex) + self.assertIs(c_float_complex.__ctype_le__.__ctype_be__, + c_float_complex) + s = c_float_complex(math.pi+1j) + self.assertEqual(bin(struct.pack("F", math.pi+1j)), bin(s)) + self.assertAlmostEqual(s.value, math.pi+1j, places=6) + s = c_float_complex.__ctype_le__(math.pi+1j) + self.assertAlmostEqual(s.value, math.pi+1j, places=6) + self.assertEqual(bin(struct.pack("F", math.pi+1j)), bin(s)) + + @unittest.skipUnless(hasattr(ctypes, 'c_double_complex'), "No complex types") + def test_endian_double_complex(self): + c_double_complex = ctypes.c_double_complex + if sys.byteorder == "little": + self.assertIs(c_double_complex.__ctype_le__, c_double_complex) + self.assertIs(c_double_complex.__ctype_be__.__ctype_le__, + c_double_complex) + else: + self.assertIs(c_double_complex.__ctype_be__, c_double_complex) + self.assertIs(c_double_complex.__ctype_le__.__ctype_be__, + c_double_complex) + s = c_double_complex(math.pi+1j) + self.assertEqual(bin(struct.pack("D", math.pi+1j)), bin(s)) + self.assertAlmostEqual(s.value, math.pi+1j, places=6) + s = c_double_complex.__ctype_le__(math.pi+1j) + self.assertAlmostEqual(s.value, math.pi+1j, places=6) + self.assertEqual(bin(struct.pack("D", math.pi+1j)), bin(s)) + def test_endian_other(self): self.assertIs(c_byte.__ctype_le__, c_byte) self.assertIs(c_byte.__ctype_be__, c_byte) diff --git a/Misc/NEWS.d/next/Library/2026-04-13-06-22-27.gh-issue-148464.Bj_NZy.rst b/Misc/NEWS.d/next/Library/2026-04-13-06-22-27.gh-issue-148464.Bj_NZy.rst new file mode 100644 index 00000000000000..85b99531d033b1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-13-06-22-27.gh-issue-148464.Bj_NZy.rst @@ -0,0 +1,3 @@ +Add missing ``__ctype_le/be__`` attributes for +:class:`~ctypes.c_float_complex` and :class:`~ctypes.c_double_complex`. Patch +by Sergey B Kirpichev. diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 55eade1c8307ea..e330ffc9126686 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -2267,7 +2267,17 @@ static PyObject *CreateSwappedType(ctypes_state *st, PyTypeObject *type, return NULL; } - stginfo->ffi_type_pointer = *fmt->pffi_type; + if (!fmt->pffi_type->elements) { + stginfo->ffi_type_pointer = *fmt->pffi_type; + } + else { + stginfo->ffi_type_pointer.size = fmt->pffi_type->size; + stginfo->ffi_type_pointer.alignment = fmt->pffi_type->alignment; + stginfo->ffi_type_pointer.type = fmt->pffi_type->type; + stginfo->ffi_type_pointer.elements = PyMem_Malloc(2 * sizeof(ffi_type)); + memcpy(stginfo->ffi_type_pointer.elements, + fmt->pffi_type->elements, 2 * sizeof(ffi_type)); + } stginfo->align = fmt->pffi_type->alignment; stginfo->length = 0; stginfo->size = fmt->pffi_type->size; diff --git a/Modules/_ctypes/cfield.c b/Modules/_ctypes/cfield.c index 4ebca0e0b3db0a..eec8b60eab73f2 100644 --- a/Modules/_ctypes/cfield.c +++ b/Modules/_ctypes/cfield.c @@ -792,6 +792,32 @@ D_get(void *ptr, Py_ssize_t size) return PyComplex_FromDoubles(x[0], x[1]); } +static PyObject * +D_set_sw(void *ptr, PyObject *value, Py_ssize_t size) +{ + assert(NUM_BITS(size) || (size == 2*sizeof(double))); + Py_complex c = PyComplex_AsCComplex(value); + + if (c.real == -1 && PyErr_Occurred()) { + return NULL; + } + if (PyFloat_Pack8(c.real, ptr, PY_BIG_ENDIAN) + || PyFloat_Pack8(c.imag, ptr + sizeof(double), PY_BIG_ENDIAN)) + { + return NULL; + } + _RET(value); +} + +static PyObject * +D_get_sw(void *ptr, Py_ssize_t size) +{ + assert(NUM_BITS(size) || (size == 2*sizeof(double))); + return PyComplex_FromDoubles(PyFloat_Unpack8(ptr, PY_BIG_ENDIAN), + PyFloat_Unpack8(ptr + sizeof(double), + PY_BIG_ENDIAN)); +} + /* F: float complex */ static PyObject * F_set(void *ptr, PyObject *value, Py_ssize_t size) @@ -817,6 +843,32 @@ F_get(void *ptr, Py_ssize_t size) return PyComplex_FromDoubles(x[0], x[1]); } +static PyObject * +F_set_sw(void *ptr, PyObject *value, Py_ssize_t size) +{ + assert(NUM_BITS(size) || (size == 2*sizeof(float))); + Py_complex c = PyComplex_AsCComplex(value); + + if (c.real == -1 && PyErr_Occurred()) { + return NULL; + } + if (PyFloat_Pack4(c.real, ptr, PY_BIG_ENDIAN) + || PyFloat_Pack4(c.imag, ptr + sizeof(float), PY_BIG_ENDIAN)) + { + return NULL; + } + _RET(value); +} + +static PyObject * +F_get_sw(void *ptr, Py_ssize_t size) +{ + assert(NUM_BITS(size) || (size == 2*sizeof(float))); + return PyComplex_FromDoubles(PyFloat_Unpack4(ptr, PY_BIG_ENDIAN), + PyFloat_Unpack4(ptr + sizeof(float), + PY_BIG_ENDIAN)); +} + /* G: long double complex */ static PyObject * G_set(void *ptr, PyObject *value, Py_ssize_t size) @@ -1602,7 +1654,9 @@ for base_code, base_c_type in [ #if defined(_Py_FFI_SUPPORT_C_COMPLEX) if (Py_FFI_COMPLEX_AVAILABLE) { TABLE_ENTRY(D, &ffi_type_complex_double); + TABLE_ENTRY_SW(D, &ffi_type_complex_double); TABLE_ENTRY(F, &ffi_type_complex_float); + TABLE_ENTRY_SW(F, &ffi_type_complex_float); TABLE_ENTRY(G, &ffi_type_complex_longdouble); } #endif From 820c5d85ca3b9942952e719bbcb9c50b0a9ce9f7 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 14 Apr 2026 11:22:54 +0300 Subject: [PATCH 2/7] + handle memory errors --- Modules/_ctypes/_ctypes.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index e330ffc9126686..5ee38d825d18e2 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -2271,12 +2271,17 @@ static PyObject *CreateSwappedType(ctypes_state *st, PyTypeObject *type, stginfo->ffi_type_pointer = *fmt->pffi_type; } else { + const size_t els_size = sizeof(fmt->pffi_type->elements); stginfo->ffi_type_pointer.size = fmt->pffi_type->size; stginfo->ffi_type_pointer.alignment = fmt->pffi_type->alignment; stginfo->ffi_type_pointer.type = fmt->pffi_type->type; - stginfo->ffi_type_pointer.elements = PyMem_Malloc(2 * sizeof(ffi_type)); + stginfo->ffi_type_pointer.elements = PyMem_Malloc(els_size); + if (!stginfo->ffi_type_pointer.elements) { + Py_DECREF(result); + return PyErr_NoMemory(); + } memcpy(stginfo->ffi_type_pointer.elements, - fmt->pffi_type->elements, 2 * sizeof(ffi_type)); + fmt->pffi_type->elements, els_size); } stginfo->align = fmt->pffi_type->alignment; stginfo->length = 0; @@ -2382,6 +2387,10 @@ PyCSimpleType_init(PyObject *self, PyObject *args, PyObject *kwds) stginfo->ffi_type_pointer.alignment = fmt->pffi_type->alignment; stginfo->ffi_type_pointer.type = fmt->pffi_type->type; stginfo->ffi_type_pointer.elements = PyMem_Malloc(els_size); + if (!stginfo->ffi_type_pointer.elements) { + PyErr_NoMemory(); + goto error; + } memcpy(stginfo->ffi_type_pointer.elements, fmt->pffi_type->elements, els_size); } From 37305c73c8543e68442aa8403800729f74bc052f Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 15 Apr 2026 02:04:16 +0300 Subject: [PATCH 3/7] gh-148573: correct allocation of complex types in the ctypes Old code relying on implementation detail, that elements[1] for the FFI_TYPE_COMPLEX was never read. But this type actually shares same assumption as the FFI_TYPE_STRUCT: the elements field is a NULL-terminated array of pointers to ffi_type objects. So far for primitive types - only complex types have this struct field as non-NULL (two element array). --- Modules/_ctypes/_ctypes.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 5ee38d825d18e2..16e0c1ef992b7e 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -2382,7 +2382,8 @@ PyCSimpleType_init(PyObject *self, PyObject *args, PyObject *kwds) stginfo->ffi_type_pointer = *fmt->pffi_type; } else { - const size_t els_size = sizeof(fmt->pffi_type->elements); + assert(fmt->pffi_type->type == FFI_TYPE_COMPLEX); + const size_t els_size = sizeof(2 * sizeof(ffi_type *)); stginfo->ffi_type_pointer.size = fmt->pffi_type->size; stginfo->ffi_type_pointer.alignment = fmt->pffi_type->alignment; stginfo->ffi_type_pointer.type = fmt->pffi_type->type; From 5018200cdc8db9698d93d6d89c57557c75af0a81 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 15 Apr 2026 02:17:57 +0300 Subject: [PATCH 4/7] +1 --- Modules/_ctypes/_ctypes.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 16e0c1ef992b7e..21d801d91947e7 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -2271,7 +2271,8 @@ static PyObject *CreateSwappedType(ctypes_state *st, PyTypeObject *type, stginfo->ffi_type_pointer = *fmt->pffi_type; } else { - const size_t els_size = sizeof(fmt->pffi_type->elements); + assert(fmt->pffi_type->type == FFI_TYPE_COMPLEX); + const size_t els_size = sizeof(2 * sizeof(ffi_type *)); stginfo->ffi_type_pointer.size = fmt->pffi_type->size; stginfo->ffi_type_pointer.alignment = fmt->pffi_type->alignment; stginfo->ffi_type_pointer.type = fmt->pffi_type->type; From 5bc710126897ef733c0ad988198d48ae1300d82a Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 15 Apr 2026 02:25:18 +0300 Subject: [PATCH 5/7] factor out a helper --- Modules/_ctypes/_ctypes.c | 62 ++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 21d801d91947e7..9f36e985cf1a81 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -2222,6 +2222,28 @@ c_void_p_from_param_impl(PyObject *type, PyTypeObject *cls, PyObject *value) return NULL; } +static int +set_stginfo_ffi_type_pointer(StgInfo *stginfo, struct fielddesc *fmt) +{ + if (!fmt->pffi_type->elements) { + stginfo->ffi_type_pointer = *fmt->pffi_type; + } + else { + assert(fmt->pffi_type->type == FFI_TYPE_COMPLEX); + const size_t els_size = sizeof(2 * sizeof(ffi_type *)); + stginfo->ffi_type_pointer.size = fmt->pffi_type->size; + stginfo->ffi_type_pointer.alignment = fmt->pffi_type->alignment; + stginfo->ffi_type_pointer.type = fmt->pffi_type->type; + stginfo->ffi_type_pointer.elements = PyMem_Malloc(els_size); + if (!stginfo->ffi_type_pointer.elements) { + return -1; + } + memcpy(stginfo->ffi_type_pointer.elements, + fmt->pffi_type->elements, els_size); + } + return 0; +} + static PyMethodDef c_void_p_methods[] = {C_VOID_P_FROM_PARAM_METHODDEF {0}}; static PyMethodDef c_char_p_methods[] = {C_CHAR_P_FROM_PARAM_METHODDEF {0}}; static PyMethodDef c_wchar_p_methods[] = {C_WCHAR_P_FROM_PARAM_METHODDEF {0}}; @@ -2266,23 +2288,9 @@ static PyObject *CreateSwappedType(ctypes_state *st, PyTypeObject *type, Py_DECREF(result); return NULL; } - - if (!fmt->pffi_type->elements) { - stginfo->ffi_type_pointer = *fmt->pffi_type; - } - else { - assert(fmt->pffi_type->type == FFI_TYPE_COMPLEX); - const size_t els_size = sizeof(2 * sizeof(ffi_type *)); - stginfo->ffi_type_pointer.size = fmt->pffi_type->size; - stginfo->ffi_type_pointer.alignment = fmt->pffi_type->alignment; - stginfo->ffi_type_pointer.type = fmt->pffi_type->type; - stginfo->ffi_type_pointer.elements = PyMem_Malloc(els_size); - if (!stginfo->ffi_type_pointer.elements) { - Py_DECREF(result); - return PyErr_NoMemory(); - } - memcpy(stginfo->ffi_type_pointer.elements, - fmt->pffi_type->elements, els_size); + if (set_stginfo_ffi_type_pointer(stginfo, fmt)) { + Py_DECREF(result); + return PyErr_NoMemory(); } stginfo->align = fmt->pffi_type->alignment; stginfo->length = 0; @@ -2378,23 +2386,9 @@ PyCSimpleType_init(PyObject *self, PyObject *args, PyObject *kwds) if (!stginfo) { goto error; } - - if (!fmt->pffi_type->elements) { - stginfo->ffi_type_pointer = *fmt->pffi_type; - } - else { - assert(fmt->pffi_type->type == FFI_TYPE_COMPLEX); - const size_t els_size = sizeof(2 * sizeof(ffi_type *)); - stginfo->ffi_type_pointer.size = fmt->pffi_type->size; - stginfo->ffi_type_pointer.alignment = fmt->pffi_type->alignment; - stginfo->ffi_type_pointer.type = fmt->pffi_type->type; - stginfo->ffi_type_pointer.elements = PyMem_Malloc(els_size); - if (!stginfo->ffi_type_pointer.elements) { - PyErr_NoMemory(); - goto error; - } - memcpy(stginfo->ffi_type_pointer.elements, - fmt->pffi_type->elements, els_size); + if (set_stginfo_ffi_type_pointer(stginfo, fmt)) { + PyErr_NoMemory(); + goto error; } stginfo->align = fmt->pffi_type->alignment; stginfo->length = 0; From 46196ef18456d8fdc6f5cf6cd8864da26f6986ef Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 15 Apr 2026 09:48:54 +0300 Subject: [PATCH 6/7] Update Modules/_ctypes/_ctypes.c Co-authored-by: sunmy2019 <59365878+sunmy2019@users.noreply.github.com> --- Modules/_ctypes/_ctypes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 9f36e985cf1a81..36e66bf4761631 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -2230,7 +2230,7 @@ set_stginfo_ffi_type_pointer(StgInfo *stginfo, struct fielddesc *fmt) } else { assert(fmt->pffi_type->type == FFI_TYPE_COMPLEX); - const size_t els_size = sizeof(2 * sizeof(ffi_type *)); + const size_t els_size = 2 * sizeof(ffi_type *); stginfo->ffi_type_pointer.size = fmt->pffi_type->size; stginfo->ffi_type_pointer.alignment = fmt->pffi_type->alignment; stginfo->ffi_type_pointer.type = fmt->pffi_type->type; From 626f666b164b386653c038399ecc892d96b00ae2 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 15 Apr 2026 10:39:05 +0300 Subject: [PATCH 7/7] address review: use WORDS_BIGENDIAN macro --- Modules/_ctypes/cfield.c | 44 +++++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/Modules/_ctypes/cfield.c b/Modules/_ctypes/cfield.c index eec8b60eab73f2..b0dc11fdddcea1 100644 --- a/Modules/_ctypes/cfield.c +++ b/Modules/_ctypes/cfield.c @@ -801,11 +801,19 @@ D_set_sw(void *ptr, PyObject *value, Py_ssize_t size) if (c.real == -1 && PyErr_Occurred()) { return NULL; } - if (PyFloat_Pack8(c.real, ptr, PY_BIG_ENDIAN) - || PyFloat_Pack8(c.imag, ptr + sizeof(double), PY_BIG_ENDIAN)) +#ifdef WORDS_BIGENDIAN + if (PyFloat_Pack8(c.real, ptr, 1) + || PyFloat_Pack8(c.imag, ptr + sizeof(double), 1)) + { + return NULL; + } +#else + if (PyFloat_Pack8(c.real, ptr, 0) + || PyFloat_Pack8(c.imag, ptr + sizeof(double), 0)) { return NULL; } +#endif _RET(value); } @@ -813,9 +821,13 @@ static PyObject * D_get_sw(void *ptr, Py_ssize_t size) { assert(NUM_BITS(size) || (size == 2*sizeof(double))); - return PyComplex_FromDoubles(PyFloat_Unpack8(ptr, PY_BIG_ENDIAN), - PyFloat_Unpack8(ptr + sizeof(double), - PY_BIG_ENDIAN)); +#ifdef WORDS_BIGENDIAN + return PyComplex_FromDoubles(PyFloat_Unpack8(ptr, 1), + PyFloat_Unpack8(ptr + sizeof(double), 1)); +#else + return PyComplex_FromDoubles(PyFloat_Unpack8(ptr, 0), + PyFloat_Unpack8(ptr + sizeof(double), 0)); +#endif } /* F: float complex */ @@ -852,11 +864,19 @@ F_set_sw(void *ptr, PyObject *value, Py_ssize_t size) if (c.real == -1 && PyErr_Occurred()) { return NULL; } - if (PyFloat_Pack4(c.real, ptr, PY_BIG_ENDIAN) - || PyFloat_Pack4(c.imag, ptr + sizeof(float), PY_BIG_ENDIAN)) +#ifdef WORDS_BIGENDIAN + if (PyFloat_Pack4(c.real, ptr, 1) + || PyFloat_Pack4(c.imag, ptr + sizeof(float), 1)) + { + return NULL; + } +#else + if (PyFloat_Pack4(c.real, ptr, 0) + || PyFloat_Pack4(c.imag, ptr + sizeof(float), 0)) { return NULL; } +#endif _RET(value); } @@ -864,9 +884,13 @@ static PyObject * F_get_sw(void *ptr, Py_ssize_t size) { assert(NUM_BITS(size) || (size == 2*sizeof(float))); - return PyComplex_FromDoubles(PyFloat_Unpack4(ptr, PY_BIG_ENDIAN), - PyFloat_Unpack4(ptr + sizeof(float), - PY_BIG_ENDIAN)); +#ifdef WORDS_BIGENDIAN + return PyComplex_FromDoubles(PyFloat_Unpack4(ptr, 1), + PyFloat_Unpack4(ptr + sizeof(float), 1)); +#else + return PyComplex_FromDoubles(PyFloat_Unpack4(ptr, 0), + PyFloat_Unpack4(ptr + sizeof(float), 0)); +#endif } /* G: long double complex */