From 2f8705d047085394577f4c5732d99a655fa8fda1 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 19 Dec 2025 11:14:16 +0900 Subject: [PATCH 1/4] test_set_type_updates_format --- Lib/test/test_ctypes/test_incomplete.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) 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() From 3fcdc955b40dd92f88d9bc5ff8111c9d98ff9827 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 19 Dec 2025 11:23:09 +0900 Subject: [PATCH 2/4] ctypes: Fix set_type() to update format string for incomplete pointer types --- Modules/_ctypes/_ctypes.c | 51 +++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 21 deletions(-) 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; } From d34a253880066c02a45c63357c2d3837edd61ae1 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 19 Dec 2025 11:30:35 +0900 Subject: [PATCH 3/4] news --- .../next/Library/2025-12-19-11-30-31.gh-issue-142966.PzGiv2.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-12-19-11-30-31.gh-issue-142966.PzGiv2.rst 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..c7a50e7a4cc282 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-19-11-30-31.gh-issue-142966.PzGiv2.rst @@ -0,0 +1 @@ +ctypes.POINTER.set_type also resets format From 216331629ea4e53b8d3fd61afe5cd9efae8384cf Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 19 Dec 2025 13:01:51 +0900 Subject: [PATCH 4/4] Update Misc/NEWS.d/next/Library/2025-12-19-11-30-31.gh-issue-142966.PzGiv2.rst Co-authored-by: AN Long --- .../next/Library/2025-12-19-11-30-31.gh-issue-142966.PzGiv2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index c7a50e7a4cc282..92ea407c6b456e 100644 --- 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 @@ -1 +1 @@ -ctypes.POINTER.set_type also resets format +Fix :func:`!ctypes.POINTER.set_type` not updating the format string to match the type.