Skip to content

Commit bce9df9

Browse files
miss-islingtonjunkmdaiskerlend-aasland
authored
[3.12] gh-125783: Add tests to prevent regressions with the combination of ctypes and metaclasses. (GH-125881) (GH-125988)
cherry picked from commit 1384409 by Jun Komoda Also: Add test_ctypes/_support.py from 3.13+ This partially backports be89ee5 (#113727) by AN Long Co-authored-by: Jun Komoda <[email protected]> Co-authored-by: AN Long <[email protected]> Co-authored-by: Erlend E. Aasland <[email protected]>
1 parent bc9ae4a commit bce9df9

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed

Lib/test/test_ctypes/_support.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Some classes and types are not export to _ctypes module directly.
2+
3+
import ctypes
4+
from _ctypes import Structure, Union, _Pointer, Array, _SimpleCData, CFuncPtr
5+
6+
7+
_CData = Structure.__base__
8+
assert _CData.__name__ == "_CData"
9+
10+
class _X(Structure):
11+
_fields_ = [("x", ctypes.c_int)]
12+
CField = type(_X.x)
13+
14+
# metaclasses
15+
PyCStructType = type(Structure)
16+
UnionType = type(Union)
17+
PyCPointerType = type(_Pointer)
18+
PyCArrayType = type(Array)
19+
PyCSimpleType = type(_SimpleCData)
20+
PyCFuncPtrType = type(CFuncPtr)
21+
22+
# type flags
23+
Py_TPFLAGS_DISALLOW_INSTANTIATION = 1 << 7
24+
Py_TPFLAGS_IMMUTABLETYPE = 1 << 8
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import unittest
2+
import ctypes
3+
from ctypes import POINTER, c_void_p
4+
5+
from ._support import PyCSimpleType
6+
7+
8+
class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
9+
def tearDown(self):
10+
# to not leak references, we must clean _pointer_type_cache
11+
ctypes._reset_cache()
12+
13+
def test_creating_pointer_in_dunder_new_1(self):
14+
# Test metaclass whose instances are C types; when the type is
15+
# created it automatically creates a pointer type for itself.
16+
# The pointer type is also an instance of the metaclass.
17+
# Such an implementation is used in `IUnknown` of the `comtypes`
18+
# project. See gh-124520.
19+
20+
class ct_meta(type):
21+
def __new__(cls, name, bases, namespace):
22+
self = super().__new__(cls, name, bases, namespace)
23+
24+
# Avoid recursion: don't set up a pointer to
25+
# a pointer (to a pointer...)
26+
if bases == (c_void_p,):
27+
# When creating PtrBase itself, the name
28+
# is not yet available
29+
return self
30+
if issubclass(self, PtrBase):
31+
return self
32+
33+
if bases == (object,):
34+
ptr_bases = (self, PtrBase)
35+
else:
36+
ptr_bases = (self, POINTER(bases[0]))
37+
p = p_meta(f"POINTER({self.__name__})", ptr_bases, {})
38+
ctypes._pointer_type_cache[self] = p
39+
return self
40+
41+
class p_meta(PyCSimpleType, ct_meta):
42+
pass
43+
44+
class PtrBase(c_void_p, metaclass=p_meta):
45+
pass
46+
47+
class CtBase(object, metaclass=ct_meta):
48+
pass
49+
50+
class Sub(CtBase):
51+
pass
52+
53+
class Sub2(Sub):
54+
pass
55+
56+
self.assertIsInstance(POINTER(Sub2), p_meta)
57+
self.assertTrue(issubclass(POINTER(Sub2), Sub2))
58+
self.assertTrue(issubclass(POINTER(Sub2), POINTER(Sub)))
59+
self.assertTrue(issubclass(POINTER(Sub), POINTER(CtBase)))
60+
61+
def test_creating_pointer_in_dunder_new_2(self):
62+
# A simpler variant of the above, used in `CoClass` of the `comtypes`
63+
# project.
64+
65+
class ct_meta(type):
66+
def __new__(cls, name, bases, namespace):
67+
self = super().__new__(cls, name, bases, namespace)
68+
if isinstance(self, p_meta):
69+
return self
70+
p = p_meta(f"POINTER({self.__name__})", (self, c_void_p), {})
71+
ctypes._pointer_type_cache[self] = p
72+
return self
73+
74+
class p_meta(PyCSimpleType, ct_meta):
75+
pass
76+
77+
class Core(object):
78+
pass
79+
80+
class CtBase(Core, metaclass=ct_meta):
81+
pass
82+
83+
class Sub(CtBase):
84+
pass
85+
86+
self.assertIsInstance(POINTER(Sub), p_meta)
87+
self.assertTrue(issubclass(POINTER(Sub), Sub))

0 commit comments

Comments
 (0)