Skip to content

Commit a8c7223

Browse files
committed
[2.7] bpo-10746: Fix ctypes PEP 3118 type codes for c_long, c_bool, c_int (pythonGH-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.. (cherry picked from commit 07f1658)
1 parent 20958e6 commit a8c7223

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

@@ -117,60 +145,59 @@ class Complete(Structure):
117145
(c_char, "<c", None, c_char),
118146
(c_byte, "<b", None, c_byte),
119147
(c_ubyte, "<B", None, c_ubyte),
120-
(c_short, "<h", None, c_short),
121-
(c_ushort, "<H", None, c_ushort),
148+
(c_short, "<" + s_short, None, c_short),
149+
(c_ushort, "<" + s_ushort, None, c_ushort),
122150

123-
# c_int and c_uint may be aliases to c_long
124-
#(c_int, "<i", None, c_int),
125-
#(c_uint, "<I", None, c_uint),
151+
(c_int, "<" + s_int, None, c_int),
152+
(c_uint, "<" + s_uint, None, c_uint),
126153

127-
(c_long, "<l", None, c_long),
128-
(c_ulong, "<L", None, c_ulong),
154+
(c_long, "<" + s_long, None, c_long),
155+
(c_ulong, "<" + s_ulong, None, c_ulong),
129156

130-
# c_longlong and c_ulonglong are aliases on 64-bit platforms
131-
#(c_longlong, "<q", None, c_longlong),
132-
#(c_ulonglong, "<Q", None, c_ulonglong),
157+
(c_longlong, "<" + s_longlong, None, c_longlong),
158+
(c_ulonglong, "<" + s_ulonglong, None, c_ulonglong),
133159

134160
(c_float, "<f", None, c_float),
135161
(c_double, "<d", None, c_double),
136-
# c_longdouble may be an alias to c_double
137162

138-
(c_bool, "<?", None, c_bool),
163+
(c_longdouble, "<" + s_longdouble, None, c_longdouble),
164+
165+
(c_bool, "<" + s_bool, None, c_bool),
139166
(py_object, "<O", None, py_object),
140167

141168
## pointers
142169

143170
(POINTER(c_byte), "&<b", None, POINTER(c_byte)),
144-
(POINTER(POINTER(c_long)), "&&<l", None, POINTER(POINTER(c_long))),
171+
(POINTER(POINTER(c_long)), "&&<" + s_long, None, POINTER(POINTER(c_long))),
145172

146173
## arrays and pointers
147174

148175
(c_double * 4, "<d", (4,), c_double),
149176
(c_float * 4 * 3 * 2, "<f", (2,3,4), c_float),
150-
(POINTER(c_short) * 2, "&<h", (2,), POINTER(c_short)),
151-
(POINTER(c_short) * 2 * 3, "&<h", (3,2,), POINTER(c_short)),
152-
(POINTER(c_short * 2), "&(2)<h", None, POINTER(c_short)),
177+
(POINTER(c_short) * 2, "&<" + s_short, (2,), POINTER(c_short)),
178+
(POINTER(c_short) * 2 * 3, "&<" + s_short, (3,2,), POINTER(c_short)),
179+
(POINTER(c_short * 2), "&(2)<" + s_short, None, POINTER(c_short)),
153180

154181
## structures and unions
155182

156-
(Point, "T{<l:x:<l:y:}", None, Point),
183+
(Point, "T{<l:x:<l:y:}".replace('l', s_long), None, Point),
157184
# packed structures do not implement the pep
158-
(PackedPoint, "B", None, PackedPoint),
159-
(Point2, "T{<l:x:<l:y:}", None, Point2),
160-
(EmptyStruct, "T{}", None, EmptyStruct),
185+
(PackedPoint, "B", None, PackedPoint),
186+
(Point2, "T{<l:x:<l:y:}".replace('l', s_long), None, Point2),
187+
(EmptyStruct, "T{}", None, EmptyStruct),
161188
# the pep does't support unions
162-
(aUnion, "B", None, aUnion),
189+
(aUnion, "B", None, aUnion),
163190
# structure with sub-arrays
164-
(StructWithArrays, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}", None, StructWithArrays),
165-
(StructWithArrays * 3, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}", (3,), StructWithArrays),
191+
(StructWithArrays, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}".replace('l', s_long), None, StructWithArrays),
192+
(StructWithArrays * 3, "T{(2,3)<l:x:(4)T{<l:x:<l:y:}:y:}".replace('l', s_long), (3,), StructWithArrays),
166193

167194
## pointer to incomplete structure
168195
(Incomplete, "B", None, Incomplete),
169196
(POINTER(Incomplete), "&B", None, POINTER(Incomplete)),
170197

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

@@ -193,10 +220,10 @@ class LEPoint(LittleEndianStructure):
193220
# and little endian machines.
194221
#
195222
endian_types = [
196-
(BEPoint, "T{>l:x:>l:y:}", None, BEPoint),
197-
(LEPoint, "T{<l:x:<l:y:}", None, LEPoint),
198-
(POINTER(BEPoint), "&T{>l:x:>l:y:}", None, POINTER(BEPoint)),
199-
(POINTER(LEPoint), "&T{<l:x:<l:y:}", None, POINTER(LEPoint)),
223+
(BEPoint, "T{>l:x:>l:y:}".replace('l', s_long), None, BEPoint),
224+
(LEPoint, "T{<l:x:<l:y:}".replace('l', s_long), None, LEPoint),
225+
(POINTER(BEPoint), "&T{>l:x:>l:y:}".replace('l', s_long), None, POINTER(BEPoint)),
226+
(POINTER(LEPoint), "&T{<l:x:<l:y:}".replace('l', s_long), None, POINTER(LEPoint)),
200227
]
201228

202229
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
@@ -292,6 +292,71 @@ PyDict_GetItemProxy(PyObject *dict, PyObject *key)
292292
}
293293

294294
/******************************************************************/
295+
296+
/*
297+
Allocate a memory block for a pep3118 format string, filled with
298+
a suitable PEP 3118 type code corresponding to the given ctypes
299+
type. Returns NULL on failure, with the error indicator set.
300+
301+
This produces type codes in the standard size mode (cf. struct module),
302+
since the endianness may need to be swapped to a non-native one
303+
later on.
304+
*/
305+
static char *
306+
_ctypes_alloc_format_string_for_type(char code, int big_endian)
307+
{
308+
char *result;
309+
char pep_code = '\0';
310+
311+
switch (code) {
312+
#if SIZEOF_INT == 2
313+
case 'i': pep_code = 'h'; break;
314+
case 'I': pep_code = 'H'; break;
315+
#elif SIZEOF_INT == 4
316+
case 'i': pep_code = 'i'; break;
317+
case 'I': pep_code = 'I'; break;
318+
#elif SIZEOF_INT == 8
319+
case 'i': pep_code = 'q'; break;
320+
case 'I': pep_code = 'Q'; break;
321+
#else
322+
# error SIZEOF_INT has an unexpected value
323+
#endif /* SIZEOF_INT */
324+
#if SIZEOF_LONG == 4
325+
case 'l': pep_code = 'l'; break;
326+
case 'L': pep_code = 'L'; break;
327+
#elif SIZEOF_LONG == 8
328+
case 'l': pep_code = 'q'; break;
329+
case 'L': pep_code = 'Q'; break;
330+
#else
331+
# error SIZEOF_LONG has an unexpected value
332+
#endif /* SIZEOF_LONG */
333+
#if SIZEOF__BOOL == 1
334+
case '?': pep_code = '?'; break;
335+
#elif SIZEOF__BOOL == 2
336+
case '?': pep_code = 'H'; break;
337+
#elif SIZEOF__BOOL == 4
338+
case '?': pep_code = 'L'; break;
339+
#elif SIZEOF__BOOL == 8
340+
case '?': pep_code = 'Q'; break;
341+
#else
342+
# error SIZEOF__BOOL has an unexpected value
343+
#endif /* SIZEOF__BOOL */
344+
default:
345+
/* The standard-size code is the same as the ctypes one */
346+
pep_code = code;
347+
break;
348+
}
349+
350+
result = PyMem_Malloc(3);
351+
if (result == NULL)
352+
return NULL;
353+
354+
result[0] = big_endian ? '>' : '<';
355+
result[1] = pep_code;
356+
result[2] = '\0';
357+
return result;
358+
}
359+
295360
/*
296361
Allocate a memory block for a pep3118 format string, copy prefix (if
297362
non-null) and suffix into it. Returns NULL on failure, with the error
@@ -1999,9 +2064,9 @@ PyCSimpleType_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
19992064
stgdict->setfunc = fmt->setfunc;
20002065
stgdict->getfunc = fmt->getfunc;
20012066
#ifdef WORDS_BIGENDIAN
2002-
stgdict->format = _ctypes_alloc_format_string(">", proto_str);
2067+
stgdict->format = _ctypes_alloc_format_string_for_type(proto_str[0], 1);
20032068
#else
2004-
stgdict->format = _ctypes_alloc_format_string("<", proto_str);
2069+
stgdict->format = _ctypes_alloc_format_string_for_type(proto_str[0], 0);
20052070
#endif
20062071
if (stgdict->format == NULL) {
20072072
Py_DECREF(result);

0 commit comments

Comments
 (0)