Skip to content

Commit 07f1658

Browse files
pvpitrou
authored andcommitted
bpo-10746: Fix ctypes PEP 3118 type codes for c_long, c_bool, c_int (#31)
Ctypes currently produces wrong pep3118 type codes for several types. E.g. memoryview(ctypes.c_long()).format gives "<l" on 64-bit platforms, but it should be "<q" instead for sizeof(c_long) == 8 The problem is that the '<>' endian specification in the struct syntax also turns on the "standard size" mode, which makes type characters have a platform-independent meaning, which does not match with the codes used internally in ctypes. The struct module format syntax also does not allow specifying native-size non-native-endian items. This commit adds a converter function that maps the internal ctypes codes to appropriate struct module standard-size codes in the pep3118 format strings. The tests are modified to check for this.
1 parent a30f6d4 commit 07f1658

File tree

3 files changed

+123
-30
lines changed

3 files changed

+123
-30
lines changed

Lib/ctypes/test/test_pep3118.py

+55-28
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,34 @@ class Complete(Structure):
112112
# This table contains format strings as they look on little endian
113113
# machines. The test replaces '<' with '>' on big endian machines.
114114
#
115+
116+
# Platform-specific type codes
117+
s_bool = {1: '?', 2: 'H', 4: 'L', 8: 'Q'}[sizeof(c_bool)]
118+
s_short = {2: 'h', 4: 'l', 8: 'q'}[sizeof(c_short)]
119+
s_ushort = {2: 'H', 4: 'L', 8: 'Q'}[sizeof(c_ushort)]
120+
s_int = {2: 'h', 4: 'i', 8: 'q'}[sizeof(c_int)]
121+
s_uint = {2: 'H', 4: 'I', 8: 'Q'}[sizeof(c_uint)]
122+
s_long = {4: 'l', 8: 'q'}[sizeof(c_long)]
123+
s_ulong = {4: 'L', 8: 'Q'}[sizeof(c_ulong)]
124+
s_longlong = "q"
125+
s_ulonglong = "Q"
126+
s_float = "f"
127+
s_double = "d"
128+
s_longdouble = "g"
129+
130+
# Alias definitions in ctypes/__init__.py
131+
if c_int is c_long:
132+
s_int = s_long
133+
if c_uint is c_ulong:
134+
s_uint = s_ulong
135+
if c_longlong is c_long:
136+
s_longlong = s_long
137+
if c_ulonglong is c_ulong:
138+
s_ulonglong = s_ulong
139+
if c_longdouble is c_double:
140+
s_longdouble = s_double
141+
142+
115143
native_types = [
116144
# type format shape calc itemsize
117145

@@ -120,60 +148,59 @@ class Complete(Structure):
120148
(c_char, "<c", (), c_char),
121149
(c_byte, "<b", (), c_byte),
122150
(c_ubyte, "<B", (), c_ubyte),
123-
(c_short, "<h", (), c_short),
124-
(c_ushort, "<H", (), c_ushort),
151+
(c_short, "<" + s_short, (), c_short),
152+
(c_ushort, "<" + s_ushort, (), c_ushort),
125153

126-
# c_int and c_uint may be aliases to c_long
127-
#(c_int, "<i", (), c_int),
128-
#(c_uint, "<I", (), c_uint),
154+
(c_int, "<" + s_int, (), c_int),
155+
(c_uint, "<" + s_uint, (), c_uint),
129156

130-
(c_long, "<l", (), c_long),
131-
(c_ulong, "<L", (), c_ulong),
157+
(c_long, "<" + s_long, (), c_long),
158+
(c_ulong, "<" + s_ulong, (), c_ulong),
132159

133-
# c_longlong and c_ulonglong are aliases on 64-bit platforms
134-
#(c_longlong, "<q", None, c_longlong),
135-
#(c_ulonglong, "<Q", None, c_ulonglong),
160+
(c_longlong, "<" + s_longlong, (), c_longlong),
161+
(c_ulonglong, "<" + s_ulonglong, (), c_ulonglong),
136162

137163
(c_float, "<f", (), c_float),
138164
(c_double, "<d", (), c_double),
139-
# c_longdouble may be an alias to c_double
140165

141-
(c_bool, "<?", (), c_bool),
166+
(c_longdouble, "<" + s_longdouble, (), c_longdouble),
167+
168+
(c_bool, "<" + s_bool, (), c_bool),
142169
(py_object, "<O", (), py_object),
143170

144171
## pointers
145172

146173
(POINTER(c_byte), "&<b", (), POINTER(c_byte)),
147-
(POINTER(POINTER(c_long)), "&&<l", (), POINTER(POINTER(c_long))),
174+
(POINTER(POINTER(c_long)), "&&<" + s_long, (), POINTER(POINTER(c_long))),
148175

149176
## arrays and pointers
150177

151178
(c_double * 4, "<d", (4,), c_double),
152179
(c_float * 4 * 3 * 2, "<f", (2,3,4), c_float),
153-
(POINTER(c_short) * 2, "&<h", (2,), POINTER(c_short)),
154-
(POINTER(c_short) * 2 * 3, "&<h", (3,2,), POINTER(c_short)),
155-
(POINTER(c_short * 2), "&(2)<h", (), POINTER(c_short)),
180+
(POINTER(c_short) * 2, "&<" + s_short, (2,), POINTER(c_short)),
181+
(POINTER(c_short) * 2 * 3, "&<" + s_short, (3,2,), POINTER(c_short)),
182+
(POINTER(c_short * 2), "&(2)<" + s_short, (), POINTER(c_short)),
156183

157184
## structures and unions
158185

159-
(Point, "T{<l:x:<l:y:}", (), Point),
186+
(Point, "T{<l:x:<l:y:}".replace('l', s_long), (), Point),
160187
# packed structures do not implement the pep
161-
(PackedPoint, "B", (), PackedPoint),
162-
(Point2, "T{<l:x:<l:y:}", (), Point2),
163-
(EmptyStruct, "T{}", (), EmptyStruct),
188+
(PackedPoint, "B", (), PackedPoint),
189+
(Point2, "T{<l:x:<l:y:}".replace('l', s_long), (), Point2),
190+
(EmptyStruct, "T{}", (), EmptyStruct),
164191
# the pep does't support unions
165-
(aUnion, "B", (), aUnion),
192+
(aUnion, "B", (), aUnion),
166193
# structure with sub-arrays
167-
(StructWithArrays, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}", (), StructWithArrays),
168-
(StructWithArrays * 3, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}", (3,), StructWithArrays),
194+
(StructWithArrays, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}".replace('l', s_long), (), StructWithArrays),
195+
(StructWithArrays * 3, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}".replace('l', s_long), (3,), StructWithArrays),
169196

170197
## pointer to incomplete structure
171198
(Incomplete, "B", (), Incomplete),
172199
(POINTER(Incomplete), "&B", (), POINTER(Incomplete)),
173200

174201
# 'Complete' is a structure that starts incomplete, but is completed after the
175202
# pointer type to it has been created.
176-
(Complete, "T{<l:a:}", (), Complete),
203+
(Complete, "T{<l:a:}".replace('l', s_long), (), Complete),
177204
# Unfortunately the pointer format string is not fixed...
178205
(POINTER(Complete), "&B", (), POINTER(Complete)),
179206

@@ -196,10 +223,10 @@ class LEPoint(LittleEndianStructure):
196223
# and little endian machines.
197224
#
198225
endian_types = [
199-
(BEPoint, "T{>l:x:>l:y:}", (), BEPoint),
200-
(LEPoint, "T{<l:x:<l:y:}", (), LEPoint),
201-
(POINTER(BEPoint), "&T{>l:x:>l:y:}", (), POINTER(BEPoint)),
202-
(POINTER(LEPoint), "&T{<l:x:<l:y:}", (), POINTER(LEPoint)),
226+
(BEPoint, "T{>l:x:>l:y:}".replace('l', s_long), (), BEPoint),
227+
(LEPoint, "T{<l:x:<l:y:}".replace('l', s_long), (), LEPoint),
228+
(POINTER(BEPoint), "&T{>l:x:>l:y:}".replace('l', s_long), (), POINTER(BEPoint)),
229+
(POINTER(LEPoint), "&T{<l:x:<l:y:}".replace('l', s_long), (), POINTER(LEPoint)),
203230
]
204231

205232
if __name__ == "__main__":
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix ctypes producing wrong PEP 3118 type codes for integer types.

Modules/_ctypes/_ctypes.c

+67-2
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,71 @@ PyDict_GetItemProxy(PyObject *dict, PyObject *key)
249249
}
250250

251251
/******************************************************************/
252+
253+
/*
254+
Allocate a memory block for a pep3118 format string, filled with
255+
a suitable PEP 3118 type code corresponding to the given ctypes
256+
type. Returns NULL on failure, with the error indicator set.
257+
258+
This produces type codes in the standard size mode (cf. struct module),
259+
since the endianness may need to be swapped to a non-native one
260+
later on.
261+
*/
262+
static char *
263+
_ctypes_alloc_format_string_for_type(char code, int big_endian)
264+
{
265+
char *result;
266+
char pep_code = '\0';
267+
268+
switch (code) {
269+
#if SIZEOF_INT == 2
270+
case 'i': pep_code = 'h'; break;
271+
case 'I': pep_code = 'H'; break;
272+
#elif SIZEOF_INT == 4
273+
case 'i': pep_code = 'i'; break;
274+
case 'I': pep_code = 'I'; break;
275+
#elif SIZEOF_INT == 8
276+
case 'i': pep_code = 'q'; break;
277+
case 'I': pep_code = 'Q'; break;
278+
#else
279+
# error SIZEOF_INT has an unexpected value
280+
#endif /* SIZEOF_INT */
281+
#if SIZEOF_LONG == 4
282+
case 'l': pep_code = 'l'; break;
283+
case 'L': pep_code = 'L'; break;
284+
#elif SIZEOF_LONG == 8
285+
case 'l': pep_code = 'q'; break;
286+
case 'L': pep_code = 'Q'; break;
287+
#else
288+
# error SIZEOF_LONG has an unexpected value
289+
#endif /* SIZEOF_LONG */
290+
#if SIZEOF__BOOL == 1
291+
case '?': pep_code = '?'; break;
292+
#elif SIZEOF__BOOL == 2
293+
case '?': pep_code = 'H'; break;
294+
#elif SIZEOF__BOOL == 4
295+
case '?': pep_code = 'L'; break;
296+
#elif SIZEOF__BOOL == 8
297+
case '?': pep_code = 'Q'; break;
298+
#else
299+
# error SIZEOF__BOOL has an unexpected value
300+
#endif /* SIZEOF__BOOL */
301+
default:
302+
/* The standard-size code is the same as the ctypes one */
303+
pep_code = code;
304+
break;
305+
}
306+
307+
result = PyMem_Malloc(3);
308+
if (result == NULL)
309+
return NULL;
310+
311+
result[0] = big_endian ? '>' : '<';
312+
result[1] = pep_code;
313+
result[2] = '\0';
314+
return result;
315+
}
316+
252317
/*
253318
Allocate a memory block for a pep3118 format string, copy prefix (if
254319
non-null) and suffix into it. Returns NULL on failure, with the error
@@ -1930,9 +1995,9 @@ PyCSimpleType_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
19301995
stgdict->setfunc = fmt->setfunc;
19311996
stgdict->getfunc = fmt->getfunc;
19321997
#ifdef WORDS_BIGENDIAN
1933-
stgdict->format = _ctypes_alloc_format_string(">", proto_str);
1998+
stgdict->format = _ctypes_alloc_format_string_for_type(proto_str[0], 1);
19341999
#else
1935-
stgdict->format = _ctypes_alloc_format_string("<", proto_str);
2000+
stgdict->format = _ctypes_alloc_format_string_for_type(proto_str[0], 0);
19362001
#endif
19372002
if (stgdict->format == NULL) {
19382003
Py_DECREF(result);

0 commit comments

Comments
 (0)