]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-142966: Make ctypes.POINTER.set_type also reset format (GH-142967)
authorJeong, YunWon <69878+youknowone@users.noreply.github.com>
Mon, 26 Jan 2026 16:40:56 +0000 (01:40 +0900)
committerGitHub <noreply@github.com>
Mon, 26 Jan 2026 16:40:56 +0000 (17:40 +0100)
Make the deprecated set_type method resets the format, using the
same code as in type initialization.

Implementation note: this was done in PyCPointerType_init
after calling PyCPointerType_SetProto, but was forgotten
after in PyCPointerType_set_type_impl's call to
PyCPointerType_SetProto.
With this change, setting the format is conceptually part of
setting proto (i.e. the pointed-to type).

Co-authored-by: AN Long <aisk@users.noreply.github.com>
Lib/test/test_ctypes/test_incomplete.py
Misc/NEWS.d/next/Library/2025-12-19-11-30-31.gh-issue-142966.PzGiv2.rst [new file with mode: 0644]
Modules/_ctypes/_ctypes.c

index 3189fcd1bd1330cc1dd43237dddc9aaed8cfa29e..8f6316d6fba61a26aca85389898d26f6deb59ad1 100644 (file)
@@ -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 TestSetPointerType(unittest.TestCase):
         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 "&<element_format>"
+        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 (file)
index 0000000..92ea407
--- /dev/null
@@ -0,0 +1 @@
+Fix :func:`!ctypes.POINTER.set_type` not updating the format string to match the type.
index 6d9193af657e2a1d45c036198deaed3541a04746..2c691c3766fc4dd14ae4730f430a3bc68e2ca7cb 100644 (file)
@@ -1258,11 +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));
     }
+    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(
+            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;
+    }
     return 0;
 }
 
@@ -1314,35 +1333,11 @@ 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) {
-            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;