From 428fdda4b9b440fa4b6da4528c75ae455367a6d0 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 19 Dec 2025 11:14:16 +0900 Subject: [PATCH 1/5] 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 ff04b9e16a2b94667e7260bff6c3160223d0bd19 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 19 Dec 2025 11:23:09 +0900 Subject: [PATCH 2/5] 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 563e95a762599b..0569e07414a405 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 299cb4b5c7221be74b2db5b7d35846acdc08fa6f Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 19 Dec 2025 11:30:35 +0900 Subject: [PATCH 3/5] 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 ad01d7d4735655b06b09566baf0ac63a33da6d58 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/5] 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. From d87be53222596412220623bac88c2fb38990e85f Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" Date: Mon, 19 Jan 2026 00:46:38 +0900 Subject: [PATCH 5/5] address review --- Modules/_ctypes/_ctypes.c | 40 +++++++++++++-------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 0569e07414a405..faa3121770112d 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -1258,37 +1258,30 @@ PyCPointerType_SetProto(ctypes_state *st, PyObject *self, StgInfo *stginfo, PyOb return -1; } Py_XSETREF(stginfo->proto, Py_NewRef(proto)); + + // Set the format string for the pointer type based on element type. + // If info->format is NULL, this is a pointer to an incomplete type. + // We create a generic format string 'pointer to bytes' in this case. + char *new_format = NULL; STGINFO_LOCK(info); if (info->pointer_type == NULL) { Py_XSETREF(info->pointer_type, Py_NewRef(self)); } - STGINFO_UNLOCK(); - 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) { + const char *current_format = info->format ? info->format : "B"; + if (info->shape != NULL) { + // pointer to an array: the shape needs to be prefixed new_format = _ctypes_alloc_format_string_with_shape( - iteminfo->ndim, iteminfo->shape, "&", current_format); + info->ndim, info->shape, "&", current_format); } else { new_format = _ctypes_alloc_format_string("&", current_format); } + PyMem_Free(stginfo->format); + stginfo->format = new_format; + STGINFO_UNLOCK(); + if (new_format == NULL) { return -1; } - PyMem_Free(stginfo->format); - stginfo->format = new_format; return 0; } @@ -1344,10 +1337,6 @@ PyCPointerType_init(PyObject *self, PyObject *args, PyObject *kwds) Py_DECREF(proto); return -1; } - if (PyCPointerType_SetFormat(st, stginfo, proto) < 0) { - Py_DECREF(proto); - return -1; - } Py_DECREF(proto); } @@ -1382,9 +1371,6 @@ 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; }