-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Expand file tree
/
Copy path_support.py
More file actions
151 lines (122 loc) · 5.76 KB
/
_support.py
File metadata and controls
151 lines (122 loc) · 5.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# Some classes and types are not export to _ctypes module directly.
import ctypes
from _ctypes import Structure, Union, _Pointer, Array, _SimpleCData, CFuncPtr
import sys
from test import support
_CData = Structure.__base__
assert _CData.__name__ == "_CData"
# metaclasses
PyCStructType = type(Structure)
UnionType = type(Union)
PyCPointerType = type(_Pointer)
PyCArrayType = type(Array)
PyCSimpleType = type(_SimpleCData)
PyCFuncPtrType = type(CFuncPtr)
# type flags
Py_TPFLAGS_DISALLOW_INSTANTIATION = 1 << 7
Py_TPFLAGS_IMMUTABLETYPE = 1 << 8
def is_underaligned(ctype):
"""Return true when type's alignment is less than its size.
A famous example is 64-bit int on 32-bit x86.
"""
return ctypes.alignment(ctype) < ctypes.sizeof(ctype)
class StructCheckMixin:
def check_struct(self, structure):
"""Assert that a structure is well-formed"""
self._check_struct_or_union(structure, is_struct=True)
def check_union(self, union):
"""Assert that a union is well-formed"""
self._check_struct_or_union(union, is_struct=False)
def check_struct_or_union(self, cls):
if issubclass(cls, Structure):
self._check_struct_or_union(cls, is_struct=True)
elif issubclass(cls, Union):
self._check_struct_or_union(cls, is_struct=False)
else:
raise TypeError(cls)
def _check_struct_or_union(self, cls, is_struct):
# Check that fields are not overlapping (for structs),
# and that their metadata is consistent.
used_bits = 0
is_little_endian = (
hasattr(cls, '_swappedbytes_') ^ (sys.byteorder == 'little'))
anon_names = getattr(cls, '_anonymous_', ())
cls_size = ctypes.sizeof(cls)
for name, requested_type, *rest_of_tuple in cls._fields_:
field = getattr(cls, name)
with self.subTest(name=name, field=field):
is_bitfield = len(rest_of_tuple) > 0
# name
self.assertEqual(field.name, name)
# type
self.assertEqual(field.type, requested_type)
# offset === byte_offset
self.assertEqual(field.byte_offset, field.offset)
if not is_struct:
self.assertEqual(field.byte_offset, 0)
# byte_size
self.assertEqual(field.byte_size, ctypes.sizeof(field.type))
self.assertGreaterEqual(field.byte_size, 0)
# Check that the field is inside the struct.
# See gh-130410 for why this is skipped for bitfields of
# underaligned types. Later in this function (see `bit_end`)
# we assert that the value *bits* are inside the struct.
if not (field.is_bitfield and is_underaligned(field.type)):
self.assertLessEqual(field.byte_offset + field.byte_size,
cls_size)
# size
self.assertGreaterEqual(field.size, 0)
if is_bitfield:
# size has backwards-compatible bit-packed info
expected_size = (field.bit_size << 16) + field.bit_offset
self.assertEqual(field.size, expected_size)
else:
# size == byte_size
self.assertEqual(field.size, field.byte_size)
# is_bitfield (bool)
self.assertIs(field.is_bitfield, is_bitfield)
# bit_offset
if is_bitfield:
self.assertGreaterEqual(field.bit_offset, 0)
self.assertLessEqual(field.bit_offset + field.bit_size,
field.byte_size * 8)
else:
self.assertEqual(field.bit_offset, 0)
if not is_struct:
if is_little_endian:
self.assertEqual(field.bit_offset, 0)
else:
self.assertEqual(field.bit_offset,
field.byte_size * 8 - field.bit_size)
# bit_size
if is_bitfield:
self.assertGreaterEqual(field.bit_size, 0)
self.assertLessEqual(field.bit_size, field.byte_size * 8)
[requested_bit_size] = rest_of_tuple
self.assertEqual(field.bit_size, requested_bit_size)
else:
self.assertEqual(field.bit_size, field.byte_size * 8)
# is_anonymous (bool)
self.assertIs(field.is_anonymous, name in anon_names)
# In a struct, field should not overlap.
# (Test skipped if the structs is enormous.)
if is_struct and cls_size < 10_000:
# Get a mask indicating where the field is within the struct
if is_little_endian:
tp_shift = field.byte_offset * 8
else:
tp_shift = (cls_size
- field.byte_offset
- field.byte_size) * 8
mask = (1 << field.bit_size) - 1
mask <<= (tp_shift + field.bit_offset)
assert mask.bit_count() == field.bit_size
# Check that these bits aren't shared with previous fields
self.assertEqual(used_bits & mask, 0)
# Mark the bits for future checks
used_bits |= mask
# field is inside cls
bit_end = (field.byte_offset * 8
+ field.bit_offset
+ field.bit_size)
self.assertLessEqual(bit_end, cls_size * 8)