diff --git a/Lib/test/test_ctypes/test_incomplete.py b/Lib/test/test_ctypes/test_incomplete.py index 3189fcd1bd1330..8f6316d6fba61a 100644 --- a/Lib/test/test_ctypes/test_incomplete.py +++ b/Lib/test/test_ctypes/test_incomplete.py @@ -1,6 +1,6 @@ import ctypes import unittest -from ctypes import Structure, POINTER, pointer, c_char_p +from ctypes import Structure, POINTER, pointer, c_char_p, c_int # String-based "incomplete pointers" were implemented in ctypes 0.6.3 (2003, when # ctypes was an external project). They made obsolete by the current @@ -50,6 +50,29 @@ class cell(Structure): lpcell.set_type(cell) self.assertIs(POINTER(cell), lpcell) + def test_set_type_updates_format(self): + # gh-142966: set_type should update StgInfo.format + # to match the element type's format + with self.assertWarns(DeprecationWarning): + lp = POINTER("node") + + class node(Structure): + _fields_ = [("value", c_int)] + + # Get the expected format before set_type + node_format = memoryview(node()).format + expected_format = "&" + node_format + + lp.set_type(node) + + # Create instance to check format via memoryview + n = node(42) + p = lp(n) + actual_format = memoryview(p).format + + # After set_type, the pointer's format should be "&" + self.assertEqual(actual_format, expected_format) + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2025-12-19-11-30-31.gh-issue-142966.PzGiv2.rst b/Misc/NEWS.d/next/Library/2025-12-19-11-30-31.gh-issue-142966.PzGiv2.rst new file mode 100644 index 00000000000000..92ea407c6b456e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-19-11-30-31.gh-issue-142966.PzGiv2.rst @@ -0,0 +1 @@ +Fix :func:`!ctypes.POINTER.set_type` not updating the format string to match the type. diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 774ac71ce9ec56..54c91cd04aa7dc 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -1266,6 +1266,32 @@ PyCPointerType_SetProto(ctypes_state *st, PyObject *self, StgInfo *stginfo, PyOb return 0; } +// Set the format string for a pointer type based on its element type. +static int +PyCPointerType_SetFormat(ctypes_state *st, StgInfo *stginfo, PyObject *proto) +{ + StgInfo *iteminfo; + if (PyStgInfo_FromType(st, proto, &iteminfo) < 0) { + return -1; + } + assert(iteminfo); // PyCPointerType_SetProto already verified this + + const char *current_format = iteminfo->format ? iteminfo->format : "B"; + char *new_format; + if (iteminfo->shape != NULL) { + new_format = _ctypes_alloc_format_string_with_shape( + iteminfo->ndim, iteminfo->shape, "&", current_format); + } else { + new_format = _ctypes_alloc_format_string("&", current_format); + } + if (new_format == NULL) { + return -1; + } + PyMem_Free(stginfo->format); + stginfo->format = new_format; + return 0; +} + static PyCArgObject * PyCPointerType_paramfunc(ctypes_state *st, CDataObject *self) { @@ -1314,35 +1340,15 @@ PyCPointerType_init(PyObject *self, PyObject *args, PyObject *kwds) return -1; } if (proto) { - const char *current_format; if (PyCPointerType_SetProto(st, self, stginfo, proto) < 0) { Py_DECREF(proto); return -1; } - StgInfo *iteminfo; - if (PyStgInfo_FromType(st, proto, &iteminfo) < 0) { + if (PyCPointerType_SetFormat(st, stginfo, proto) < 0) { Py_DECREF(proto); return -1; } - /* PyCPointerType_SetProto has verified proto has a stginfo. */ - assert(iteminfo); - /* If iteminfo->format is NULL, then this is a pointer to an - incomplete type. We create a generic format string - 'pointer to bytes' in this case. XXX Better would be to - fix the format string later... - */ - current_format = iteminfo->format ? iteminfo->format : "B"; - if (iteminfo->shape != NULL) { - /* pointer to an array: the shape needs to be prefixed */ - stginfo->format = _ctypes_alloc_format_string_with_shape( - iteminfo->ndim, iteminfo->shape, "&", current_format); - } else { - stginfo->format = _ctypes_alloc_format_string("&", current_format); - } Py_DECREF(proto); - if (stginfo->format == NULL) { - return -1; - } } return 0; @@ -1376,6 +1382,9 @@ PyCPointerType_set_type_impl(PyTypeObject *self, PyTypeObject *cls, if (PyCPointerType_SetProto(st, (PyObject *)self, info, type) < 0) { return NULL; } + if (PyCPointerType_SetFormat(st, info, type) < 0) { + return NULL; + } if (PyObject_SetAttr((PyObject *)self, &_Py_ID(_type_), type) < 0) { return NULL; }