Skip to content

Commit 2a2cf53

Browse files
committed
pythongh-46376: add (failing) tests for ctypes/pointer/cast/set_contents
Previous attempt to fix pythongh-46376 was incomplete and overall it didn't succeed, and was reverted. However, we have discovered some dangerous issues with ctypes, that aren't fixed or documented anywhere. This commit adds tests (@expectedfailure) so at least developers are aware of situations where memory might be corrupted, leaked or when changing a pointer value might have no effect. Hopefully, we should be able to remove @expectedfailure in the future, with new shiny ctypes implementation or at least a bugfix.
1 parent e407cea commit 2a2cf53

File tree

6 files changed

+333
-46
lines changed

6 files changed

+333
-46
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ Tools/unicode/data/
124124
/config.status.lineno
125125
# hendrikmuhs/ccache-action@v1
126126
/.ccache
127+
# clangd and it's debugger
128+
/.cache
127129
/platform
128130
/profile-clean-stamp
129131
/profile-run-stamp

Lib/test/test_ctypes/test_arrays.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import sys
33
import unittest
44
import warnings
5-
from ctypes import (Structure, Array, sizeof, addressof,
5+
from ctypes import (Structure, Array, sizeof, addressof, POINTER, pointer,
66
create_string_buffer, create_unicode_buffer,
77
c_char, c_wchar, c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint,
88
c_long, c_ulonglong, c_float, c_double, c_longdouble)
@@ -249,6 +249,24 @@ def test_deprecation(self):
249249
with self.assertWarns(DeprecationWarning):
250250
CharArray = ctypes.ARRAY(c_char, 3)
251251

252+
@unittest.expectedFailure # gh-46376, gh-107131, gh-107940, gh-108222
253+
def test_ptr_reuse(self):
254+
arr = 3 * POINTER(c_short)
255+
256+
vals = arr(pointer(c_short(10)), pointer(c_short(11)), None)
257+
258+
self.assertEqual(vals[0].contents.value, 10)
259+
self.assertEqual(vals[1].contents.value, 11)
260+
261+
vals[2] = vals[0]
262+
263+
self.assertEqual(vals[2].contents.value, 10)
264+
265+
vals[2].contents = c_short(12)
266+
267+
self.assertEqual(vals[2].contents.value, 12)
268+
self.assertEqual(vals[0].contents.value, 12)
269+
252270

253271
if __name__ == '__main__':
254272
unittest.main()

Lib/test/test_ctypes/test_cast.py

Lines changed: 297 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import gc
12
import sys
23
import unittest
3-
from ctypes import (Structure, Union, POINTER, cast, sizeof, addressof,
4-
c_void_p, c_char_p, c_wchar_p,
5-
c_byte, c_short, c_int)
4+
from ctypes import (Structure, Union, pointer, POINTER, sizeof, addressof,
5+
c_void_p, c_char_p, c_wchar_p, cast,
6+
c_byte, c_short, c_int, c_int16,
7+
_pointer_type_cache)
8+
import weakref
69

710

811
class Test(unittest.TestCase):
@@ -95,6 +98,297 @@ class MyUnion(Union):
9598
_fields_ = [("a", c_int)]
9699
self.assertRaises(TypeError, cast, array, MyUnion)
97100

101+
@unittest.expectedFailure # gh-46376, gh-107131, gh-107940, gh-108222
102+
def test_cast_detached_structure(self):
103+
class Struct(Structure):
104+
_fields_ = [("a", POINTER(c_int))]
105+
106+
x = Struct(a=pointer(c_int(23)))
107+
self.assertEqual(x.a.contents.value, 23)
108+
109+
p = cast(x.a, POINTER(c_int)) # intentinally, cast to same
110+
p.contents = c_int(32)
111+
112+
self.assertEqual(x.a.contents.value, 32)
113+
114+
@unittest.expectedFailure # gh-46376, gh-107131, gh-107940, gh-108222
115+
def test_casted_structure(self):
116+
class Struct(Structure):
117+
_fields_ = [("a", POINTER(c_int))]
118+
119+
x = Struct(a=pointer(c_int(23)))
120+
self.assertEqual(x.a.contents.value, 23)
121+
122+
p = cast(pointer(x), POINTER(POINTER(c_int))) # intentinally, cast to same
123+
p.contents = pointer(c_int(32))
124+
125+
self.assertEqual(x.a.contents.value, 32)
126+
127+
# to avoid leaking when tests are run several times
128+
# clean up the types left in the cache.
129+
del _pointer_type_cache[Struct]
130+
131+
@unittest.expectedFailure # gh-46376, gh-107131, gh-107940, gh-108222
132+
def test_pointer_array(self):
133+
w = weakref.WeakValueDictionary()
134+
arr = 3 * POINTER(c_short)
135+
136+
vals = arr(
137+
w.setdefault("10", pointer(c_short(10))),
138+
w.setdefault("11", pointer(c_short(11))),
139+
None,
140+
)
141+
142+
self.assertEqual(vals[0].contents.value, 10)
143+
self.assertEqual(vals[1].contents.value, 11)
144+
145+
class Struct(Structure):
146+
_fields_ = [('a', c_int16)]
147+
148+
v = cast(pointer(vals), POINTER(Struct * 3)).contents
149+
v[2] = v[0]
150+
151+
self.assertTrue(vals[2])
152+
self.assertEqual(vals[2].contents.value, 10)
153+
154+
vals[2].contents = w.setdefault("12", pointer(c_short(12)))
155+
156+
self.assertEqual(vals[2].contents.value, 12)
157+
self.assertEqual(vals[0].contents.value, 12)
158+
159+
gc.collect()
160+
self.assertNotIn("10", w)
161+
162+
def test_pointer_identity1(self):
163+
class Struct(Structure):
164+
_fields_ = [('a', c_int16)]
165+
166+
Struct3 = 3 * Struct
167+
c_array = (2 * Struct3)(
168+
Struct3(Struct(a=1), Struct(a=2), Struct(a=3)),
169+
Struct3(Struct(a=4), Struct(a=5), Struct(a=6))
170+
)
171+
self.assertEqual(c_array[0][0].a, 1)
172+
self.assertEqual(c_array[0][1].a, 2)
173+
self.assertEqual(c_array[0][2].a, 3)
174+
self.assertEqual(c_array[1][0].a, 4)
175+
self.assertEqual(c_array[1][1].a, 5)
176+
self.assertEqual(c_array[1][2].a, 6)
177+
178+
p_obj = cast(pointer(c_array), POINTER(pointer(c_array)._type_))
179+
obj = p_obj.contents
180+
self.assertEqual(obj[0][0].a, 1)
181+
self.assertEqual(obj[0][1].a, 2)
182+
self.assertEqual(obj[0][2].a, 3)
183+
self.assertEqual(obj[1][0].a, 4)
184+
self.assertEqual(obj[1][1].a, 5)
185+
self.assertEqual(obj[1][2].a, 6)
186+
187+
p_obj = cast(pointer(c_array[0]), POINTER(pointer(c_array)._type_))
188+
obj = p_obj.contents
189+
190+
self.assertEqual(obj[0][0].a, 1)
191+
self.assertEqual(obj[0][1].a, 2)
192+
self.assertEqual(obj[0][2].a, 3)
193+
self.assertEqual(obj[1][0].a, 4)
194+
self.assertEqual(obj[1][1].a, 5)
195+
self.assertEqual(obj[1][2].a, 6)
196+
197+
@unittest.expectedFailure # gh-46376, gh-107131, gh-107940, gh-108222
198+
def test_cast_structure_array(self):
199+
class Struct(Structure):
200+
_fields_ = [('a', c_int16)]
201+
202+
Struct3 = 3 * Struct
203+
c_array = (2 * Struct3)(
204+
Struct3(Struct(a=1), Struct(a=2), Struct(a=3)),
205+
Struct3(Struct(a=4), Struct(a=5), Struct(a=6))
206+
)
207+
obj = cast(pointer(c_array), POINTER(pointer(c_array)._type_)).contents
208+
209+
c_array[0][0] = Struct(a=100)
210+
self.assertEqual(c_array[0][0].a, 100)
211+
self.assertEqual(obj[0][0].a, 100)
212+
213+
cast(pointer(c_array), POINTER(c_short)).contents = c_short(200)
214+
self.assertEqual(c_array[0][0].a, 200)
215+
self.assertEqual(obj[0][0].a, 200)
216+
217+
def test_pointer_identity2(self):
218+
class Struct(Structure):
219+
_fields_ = [('a', c_int16)]
220+
221+
StructPointer = POINTER(Struct)
222+
s1 = Struct(a=10)
223+
s2 = Struct(a=20)
224+
s3 = Struct(a=30)
225+
pointer_array = (3 * StructPointer)(pointer(s1), pointer(s2), pointer(s3))
226+
self.assertEqual(pointer_array[0][0].a, 10)
227+
self.assertEqual(pointer_array[1][0].a, 20)
228+
self.assertEqual(pointer_array[2][0].a, 30)
229+
self.assertEqual(pointer_array[0].contents.a, 10)
230+
self.assertEqual(pointer_array[1].contents.a, 20)
231+
self.assertEqual(pointer_array[2].contents.a, 30)
232+
233+
p_obj = cast(pointer(pointer_array[0]), POINTER(pointer(pointer_array)._type_))
234+
obj = p_obj.contents
235+
self.assertEqual(obj[0][0].a, 10)
236+
self.assertEqual(obj[1][0].a, 20)
237+
self.assertEqual(obj[2][0].a, 30)
238+
self.assertEqual(obj[0].contents.a, 10)
239+
self.assertEqual(obj[1].contents.a, 20)
240+
self.assertEqual(obj[2].contents.a, 30)
241+
242+
@unittest.expectedFailure # gh-46376, gh-107131, gh-107940, gh-108222
243+
def test_cast_structure_pointer_array(self):
244+
class Struct(Structure):
245+
_fields_ = [('a', c_int16)]
246+
247+
StructPointer = POINTER(Struct)
248+
s1 = Struct(a=10)
249+
s2 = Struct(a=20)
250+
s3 = Struct(a=30)
251+
pointer_array = (3 * StructPointer)(pointer(s1), pointer(s2), pointer(s3))
252+
obj = cast(pointer(pointer_array[0]),
253+
POINTER(pointer(pointer_array)._type_)).contents
254+
255+
pointer_array[0][0].a = 50
256+
self.assertEqual(obj[0][0].a, 50)
257+
258+
c = cast(pointer(pointer_array[0]), POINTER(POINTER(c_short)))
259+
c.contents = pointer(c_short(100))
260+
self.assertEqual(pointer_array[0].contents.a, 100)
261+
self.assertEqual(obj[0][0].a, 100)
262+
263+
@unittest.expectedFailure # gh-46376, gh-107131, gh-107940, gh-108222
264+
def test_pointer_identity3(self):
265+
class Struct(Structure):
266+
_fields_ = [('a', c_int16)]
267+
268+
class StructWithPointers(Structure):
269+
_fields_ = [("s1", POINTER(Struct)), ("s2", POINTER(Struct))]
270+
271+
s1 = Struct(a=10)
272+
s2 = Struct(a=20)
273+
s3 = Struct(a=30)
274+
275+
StructPointer = POINTER(Struct)
276+
277+
pointer_array = (3 * StructPointer)(pointer(s1), pointer(s2), pointer(s3))
278+
279+
struct = StructWithPointers(s1=pointer(s1), s2=pointer(s2))
280+
p_obj = pointer(struct)
281+
obj = p_obj.contents
282+
self.assertEqual(addressof(obj), addressof(struct)) # assertIs?
283+
284+
self.assertEqual(obj.s1[0].a, 10)
285+
self.assertEqual(obj.s2[0].a, 20)
286+
self.assertEqual(obj.s1.contents.a, 10)
287+
self.assertEqual(obj.s2.contents.a, 20)
288+
289+
p_obj = cast(pointer(struct), POINTER(pointer(pointer_array)._type_))
290+
obj = p_obj.contents
291+
self.assertEqual(addressof(obj), addressof(struct))
292+
self.assertEqual(addressof(obj[0]), addressof(struct))
293+
self.assertEqual(addressof(obj[0]), addressof(struct.s1))
294+
self.assertEqual(addressof(obj[1]), addressof(struct.s2))
295+
self.assertEqual(addressof(obj[0].contents), addressof(struct.s1.contents))
296+
self.assertEqual(addressof(obj[0][0]), addressof(pointer_array[0].contents))
297+
self.assertEqual(addressof(obj[1][0]), addressof(pointer_array[1].contents))
298+
299+
self.assertEqual(obj[0][0].a, 10)
300+
self.assertEqual(obj[1][0].a, 20)
301+
self.assertEqual(obj[0].contents.a, 10)
302+
self.assertEqual(obj[1].contents.a, 20)
303+
304+
obj[0][0].a = 23
305+
self.assertEqual(pointer_array[0].contents.a, 23)
306+
307+
obj[0].contents.a = 32
308+
self.assertEqual(pointer_array[0].contents.a, 32)
309+
310+
obj[1].contents = Struct(a=42)
311+
self.assertEqual(pointer_array[1].contents.a, 42)
312+
313+
def test_pointer_set_contents(self):
314+
class Struct(Structure):
315+
_fields_ = [('a', c_int16)]
316+
317+
w = weakref.WeakValueDictionary()
318+
p = pointer(w.setdefault(23, Struct(a=23)))
319+
self.assertIs(p._type_, Struct)
320+
self.assertEqual(cast(p, POINTER(c_int16)).contents._type_, 'h')
321+
322+
pp = pointer(p)
323+
self.assertIs(pp._type_, POINTER(Struct))
324+
325+
cast(pp, POINTER(POINTER(c_int16))).contents.contents = w.setdefault(
326+
32,
327+
c_int16(32)
328+
)
329+
330+
self.assertEqual(addressof(p.contents), addressof(pp.contents.contents))
331+
self.assertEqual(pp[0].contents.a, 32)
332+
self.assertEqual(pp.contents[0].a, 32)
333+
self.assertEqual(pp.contents.contents.a, 32)
334+
del pp
335+
gc.collect()
336+
337+
self.assertEqual(p[0].a, 32)
338+
self.assertEqual(p.contents.a, 32)
339+
self.assertEqual(cast(p, POINTER(c_int16)).contents.value, 32)
340+
341+
@unittest.expectedFailure # gh-46376, gh-107131, gh-107940, gh-108222
342+
def test_p2p_set_contents(self):
343+
class StructA(Structure):
344+
_fields_ = [('a', POINTER(c_int))]
345+
class StructB(Structure):
346+
_fields_ = [('b', POINTER(c_int))]
347+
348+
p = pointer(StructA(a=pointer(c_int(23))))
349+
self.assertEqual(p.contents.a.contents.value, 23)
350+
351+
p.contents.a.contents.value = 32
352+
self.assertEqual(p.contents.a.contents.value, 32)
353+
354+
w = weakref.WeakValueDictionary()
355+
p[0].a = pointer(w.setdefault("1", c_int(100)))
356+
357+
self.assertEqual(p.contents.a.contents.value, 100)
358+
self.assertEqual(p[0].a.contents.value, 100)
359+
360+
pp = cast(p, POINTER(POINTER(c_int)))
361+
pp.contents.contents = w.setdefault("2", c_int(200))
362+
gc.collect()
363+
364+
self.assertEqual(p.contents.a.contents.value, 200)
365+
self.assertEqual(cast(pp, POINTER(StructA)).contents.a.contents.value, 200)
366+
self.assertEqual(cast(pp, POINTER(StructA))[0].a.contents.value, 200)
367+
368+
del pp
369+
gc.collect()
370+
371+
self.assertEqual(p.contents.a.contents.value, 200)
372+
self.assertEqual(p[0].a.contents.value, 200)
373+
374+
x = StructA(a=pointer(c_int(23)))
375+
r = cast(pointer(x), POINTER(POINTER(c_int)))
376+
r.contents.contents = w.setdefault("3", c_int(300))
377+
del r
378+
gc.collect()
379+
self.assertEqual(x.a[0], 300)
380+
381+
cast(pointer(x), POINTER(StructB)).contents.b = pointer(
382+
w.setdefault("4", c_int(400))
383+
)
384+
gc.collect()
385+
self.assertEqual(x.a[0], 400)
386+
387+
# to avoid leaking when tests are run several times
388+
# clean up the types left in the cache.
389+
del _pointer_type_cache[StructA]
390+
del _pointer_type_cache[StructB]
391+
98392

99393
if __name__ == "__main__":
100394
unittest.main()

0 commit comments

Comments
 (0)