Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion Lib/test/test_ctypes/test_incomplete.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix :func:`!ctypes.POINTER.set_type` not updating the format string to match the type.
51 changes: 30 additions & 21 deletions Modules/_ctypes/_ctypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
Loading