gh-142966: Make ctypes.POINTER.set_type also reset format (GH-142967)

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>
This commit is contained in:
Jeong, YunWon 2026-01-27 01:40:56 +09:00 committed by GitHub
parent 933540e332
commit 9181d776da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 44 additions and 25 deletions

View 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 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 "&<element_format>"
self.assertEqual(actual_format, expected_format)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1 @@
Fix :func:`!ctypes.POINTER.set_type` not updating the format string to match the type.

View 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;