Skip to content

Commit a42ad21

Browse files
committed
gh-46376: minimal fix for setting pointers via pointers in ctypes
1 parent b9bcd02 commit a42ad21

File tree

2 files changed

+129
-4
lines changed

2 files changed

+129
-4
lines changed

Lib/test/test_ctypes/test_pointers.py

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import unittest
66
from ctypes import (CDLL, CFUNCTYPE, Structure,
77
POINTER, pointer, _Pointer,
8-
byref, sizeof,
8+
addressof, byref, sizeof,
99
c_void_p, c_char_p,
1010
c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint,
1111
c_long, c_ulong, c_longlong, c_ulonglong,
@@ -472,6 +472,105 @@ class C(Structure):
472472
ptr.set_type(c_int)
473473
self.assertIs(ptr._type_, c_int)
474474

475+
def test_pointer_lifecycle_basic(self):
476+
i = c_long(1010)
477+
p = pointer(i)
478+
self.assertEqual(p[0], 1010)
479+
self.assertIsNone(p._b_base_)
480+
self.assertEqual(addressof(i), addressof(p.contents))
481+
482+
def test_pointer_lifecycle_set_contents(self):
483+
i = c_long(2020)
484+
p = pointer(c_long(1010))
485+
p.contents = i
486+
self.assertEqual(p[0], 2020)
487+
self.assertIsNone(p._b_base_)
488+
self.assertEqual(addressof(i), addressof(p.contents))
489+
490+
def test_pointer_lifecycle_set_pointer_contents(self):
491+
i = c_long(3030)
492+
p = pointer(c_long(1010))
493+
pointer(p).contents.contents = i
494+
self.assertEqual(p.contents.value, 3030)
495+
self.assertEqual(addressof(i), addressof(p.contents))
496+
497+
def test_pointer_lifecycle_array_set_contents(self):
498+
arr_type = POINTER(c_long) * 3
499+
arr_obj = arr_type()
500+
i = c_long(300300)
501+
arr_obj[0] = pointer(c_long(100100))
502+
arr_obj[1] = pointer(c_long(200200))
503+
arr_obj[2] = pointer(i)
504+
self.assertEqual(arr_obj[0].contents.value, 100100)
505+
self.assertEqual(arr_obj[1].contents.value, 200200)
506+
self.assertEqual(arr_obj[2].contents.value, 300300)
507+
self.assertEqual(addressof(i), addressof(arr_obj[2].contents))
508+
509+
def test_pointer_lifecycle_array_set_pointer_contents(self):
510+
arr_type = POINTER(c_long) * 3
511+
arr_obj = arr_type()
512+
i = c_long(200003)
513+
arr_obj[0].contents = c_long(100001)
514+
arr_obj[1].contents = c_long(200002)
515+
arr_obj[2].contents = i
516+
self.assertEqual(arr_obj[0].contents.value, 100001)
517+
self.assertEqual(arr_obj[1].contents.value, 200002)
518+
self.assertEqual(arr_obj[2].contents.value, 200003)
519+
self.assertEqual(addressof(i), addressof(arr_obj[2].contents))
520+
521+
def test_pointer_lifecycle_array_set_pointer_contents_pointer(self):
522+
arr_type = POINTER(c_long) * 3
523+
arr_obj = arr_type()
524+
i = c_long(200003)
525+
pointer(arr_obj[0]).contents.contents = c_long(100001)
526+
pointer(arr_obj[1]).contents.contents = c_long(200002)
527+
pointer(arr_obj[2]).contents.contents = i
528+
self.assertEqual(arr_obj[0].contents.value, 100001)
529+
self.assertEqual(arr_obj[1].contents.value, 200002)
530+
self.assertEqual(arr_obj[2].contents.value, 200003)
531+
self.assertEqual(addressof(i), addressof(arr_obj[2].contents))
532+
533+
def test_pointer_lifecycle_struct_set_contents(self):
534+
class S(Structure):
535+
_fields_ = (("s", POINTER(c_long)),)
536+
s = S(s=pointer(c_long(1111111)))
537+
s.s.contents = c_long(2222222)
538+
self.assertEqual(s.s.contents.value, 2222222)
539+
540+
def test_pointer_lifecycle_struct_set_contents_pointer(self):
541+
class S(Structure):
542+
_fields_ = (("s", POINTER(c_long)),)
543+
s = S(s=pointer(c_long(1111111)))
544+
pointer(s.s).contents.contents = c_long(2222222)
545+
self.assertEqual(s.s.contents.value, 2222222)
546+
547+
def test_pointer_lifecycle_struct_set_pointer_contents(self):
548+
class S(Structure):
549+
_fields_ = (("s", POINTER(c_long)),)
550+
s = S(s=pointer(c_long(1111111)))
551+
s.s = pointer(c_long(3333333))
552+
self.assertEqual(s.s.contents.value, 3333333)
553+
554+
def test_pointer_lifecycle_struct_with_extra_field(self):
555+
class U(Structure):
556+
_fields_ = (
557+
("s", POINTER(c_long)),
558+
("u", c_long),
559+
)
560+
u = U(s=pointer(c_long(1010101)))
561+
u.s.contents = c_long(202020202)
562+
self.assertEqual(u.s.contents.value, 202020202)
563+
564+
def test_pointer_lifecycle_struct_with_extra_field_pointer(self):
565+
class U(Structure):
566+
_fields_ = (
567+
("s", POINTER(c_uint)),
568+
("u", c_uint),
569+
)
570+
u = U(s=pointer(c_uint(1010101)))
571+
pointer(u.s).contents.contents = c_uint(202020202)
572+
self.assertEqual(u.s.contents.value, 202020202)
573+
475574

476575
if __name__ == '__main__':
477576
unittest.main()

Modules/_ctypes/_ctypes.c

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2958,7 +2958,7 @@ KeepRef_lock_held(CDataObject *target, Py_ssize_t index, PyObject *keep)
29582958
CDataObject *ob;
29592959
PyObject *key;
29602960

2961-
/* Optimization: no need to store None */
2961+
/* Optimization: no need to store None */
29622962
if (keep == Py_None) {
29632963
Py_DECREF(Py_None);
29642964
return 0;
@@ -5724,16 +5724,42 @@ Pointer_set_contents_lock_held(PyObject *op, PyObject *value, void *closure)
57245724
pointer instance has b_length set to 2 instead of 1, and we set
57255725
'value' itself as the second item of the b_objects list, additionally.
57265726
*/
5727+
5728+
CDataObject * root = self->b_base;
5729+
/* perhaps, this is a bit excessive: if we have are in a chain of pointers
5730+
that starts with non-pointer (e.g. a union), can we consider the current
5731+
pointer to be "detached" from this chain? */
5732+
while (root != NULL && root->b_base != NULL) {
5733+
root = root->b_base;
5734+
}
5735+
5736+
/* If the b_base is NULL now or if we are a part of chain of pointers fully
5737+
modeled within ctypes, AND the value is a pointer, array, struct or union,
5738+
we just override the b_base. */
5739+
if ((root == NULL || PyType_IsSubtype(Py_TYPE(root), st->PyCPointer_Type)) &&
5740+
(PyType_IsSubtype(Py_TYPE(value), st->PyCPointer_Type) ||
5741+
PyType_IsSubtype(Py_TYPE(value), st->PyCArray_Type) ||
5742+
PyType_IsSubtype(Py_TYPE(value), st->Struct_Type) ||
5743+
PyType_IsSubtype(Py_TYPE(value), st->Union_Type))
5744+
) {
5745+
Py_XSETREF(self->b_base, (CDataObject *) Py_NewRef(value));
5746+
return 0; // no need to add `value` to `keep` objects - it's in b_base
5747+
}
5748+
5749+
/* If we are a part of chain of pointers that is not fully modeled within
5750+
ctypes, (or modeled in a complex way, e.g., with arrays and structures),
5751+
then everything should be covered by keepref logic bellow */
5752+
57275753
Py_INCREF(value);
5728-
if (-1 == KeepRef(self, 1, value))
5754+
if (-1 == KeepRef_lock_held(self, 1, value))
57295755
return -1;
57305756

57315757
keep = GetKeepedObjects(dst);
57325758
if (keep == NULL)
57335759
return -1;
57345760

57355761
Py_INCREF(keep);
5736-
return KeepRef(self, 0, keep);
5762+
return KeepRef_lock_held(self, 0, keep);
57375763
}
57385764

57395765
static int

0 commit comments

Comments
 (0)