Skip to content

Commit a84b664

Browse files
committed
gh-46376: add (failing) tests for ctypes/pointer/cast/set_contents
Previous attempt to fix gh-46376 was incomplete and overall it didn't succeed, and was reverted in 3.11. 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 a84b664

File tree

6 files changed

+339
-46
lines changed

6 files changed

+339
-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: 303 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,303 @@ 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+
@unittest.expectedFailure # gh-46376, gh-107131, gh-107940, gh-108222
314+
def test_pointer_set_contents(self):
315+
class Struct(Structure):
316+
_fields_ = [('a', c_int16)]
317+
318+
w = weakref.WeakValueDictionary()
319+
p = pointer(w.setdefault(23, Struct(a=23)))
320+
self.assertIs(p._type_, Struct)
321+
self.assertEqual(cast(p, POINTER(c_int16)).contents._type_, 'h')
322+
323+
pp = pointer(p)
324+
self.assertIs(pp._type_, POINTER(Struct))
325+
326+
cast(pp, POINTER(POINTER(c_int16))).contents.contents = w.setdefault(
327+
"k32",
328+
c_int16(32)
329+
)
330+
331+
self.assertEqual(addressof(p.contents), addressof(pp.contents.contents))
332+
self.assertEqual(pp[0].contents.a, 32)
333+
self.assertEqual(pp.contents[0].a, 32)
334+
self.assertEqual(pp.contents.contents.a, 32)
335+
del pp
336+
gc.collect()
337+
338+
# if c_int16(32) was destroyed both of these two asserts should fail
339+
self.assertIn("k32", w) # but fail here first, so we don't use-after-fee
340+
self.assertEqual(p[0].a, 32)
341+
342+
self.assertEqual(p.contents.a, 32)
343+
self.assertEqual(cast(p, POINTER(c_int16)).contents.value, 32)
344+
345+
@unittest.expectedFailure # gh-46376, gh-107131, gh-107940, gh-108222
346+
def test_p2p_set_contents(self):
347+
class StructA(Structure):
348+
_fields_ = [('a', POINTER(c_int))]
349+
class StructB(Structure):
350+
_fields_ = [('b', POINTER(c_int))]
351+
352+
p = pointer(StructA(a=pointer(c_int(23))))
353+
self.assertEqual(p.contents.a.contents.value, 23)
354+
355+
p.contents.a.contents.value = 32
356+
self.assertEqual(p.contents.a.contents.value, 32)
357+
358+
w = weakref.WeakValueDictionary()
359+
p[0].a = pointer(w.setdefault("1", c_int(100)))
360+
361+
self.assertEqual(p.contents.a.contents.value, 100)
362+
self.assertEqual(p[0].a.contents.value, 100)
363+
364+
pp = cast(p, POINTER(POINTER(c_int)))
365+
pp.contents.contents = w.setdefault("2", c_int(200))
366+
gc.collect()
367+
368+
self.assertEqual(p.contents.a.contents.value, 200)
369+
self.assertEqual(cast(pp, POINTER(StructA)).contents.a.contents.value, 200)
370+
self.assertEqual(cast(pp, POINTER(StructA))[0].a.contents.value, 200)
371+
372+
del pp
373+
gc.collect()
374+
375+
self.assertEqual(p.contents.a.contents.value, 200)
376+
self.assertEqual(p[0].a.contents.value, 200)
377+
378+
x = StructA(a=pointer(c_int(23)))
379+
r = cast(pointer(x), POINTER(POINTER(c_int)))
380+
r.contents.contents = w.setdefault("3", c_int(300))
381+
del r
382+
gc.collect()
383+
# if c_int(300) was destroyed both of these two asserts should fail
384+
self.assertIn("3", w) # but fail here first, so we don't use-after-fee
385+
self.assertEqual(x.a[0], 300)
386+
387+
cast(pointer(x), POINTER(StructB)).contents.b = pointer(
388+
w.setdefault("4", c_int(400))
389+
)
390+
gc.collect()
391+
self.assertEqual(x.a[0], 400)
392+
393+
# to avoid leaking when tests are run several times
394+
# clean up the types left in the cache.
395+
del _pointer_type_cache[StructA]
396+
del _pointer_type_cache[StructB]
397+
98398

99399
if __name__ == "__main__":
100400
unittest.main()

0 commit comments

Comments
 (0)