Skip to content

Commit ca0cdf4

Browse files
[3.13] gh-156933: Widen narrow integer results in ctypes callbacks (GH-157045) (GH-158297)
_CallPythonObject() only wrote restype->size bytes into the closure's result buffer, leaving the unused high-order bits of the ffi_arg-sized register untouched. libffi's ffi_prep_closure_loc() documents that integral types narrower than a machine register must be widened to fill it, sign-extending signed types. On architectures that always read the full register for narrow return values (s390x), this leaves garbage in the high bits, which broke libclang callbacks used by cindex.py. (cherry picked from commit b6f9a50) Co-authored-by: Lazizbek Ergashev <lazerg2@gmail.com>
1 parent c23ef5c commit ca0cdf4

4 files changed

Lines changed: 81 additions & 8 deletions

File tree

‎Lib/test/test_ctypes/test_callbacks.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,21 @@ def func():
328328
f"of ctypes callback function {func!r}")
329329
self.assertIsNone(cm.unraisable.object)
330330

331+
def test_narrow_int_return_widened(self):
332+
# gh-156933: Narrow integers were not widened on s390x
333+
CALLBACK = CFUNCTYPE(c_int)
334+
335+
dll = CDLL(_ctypes_test.__file__)
336+
_testfunc_callback_int_to_longlong = dll._testfunc_callback_int_to_longlong
337+
_testfunc_callback_int_to_longlong.argtypes = [CALLBACK]
338+
_testfunc_callback_int_to_longlong.restype = c_longlong
339+
340+
@CALLBACK
341+
def cb():
342+
return -1
343+
344+
self.assertEqual(_testfunc_callback_int_to_longlong(cb), -1)
345+
331346

332347
if __name__ == '__main__':
333348
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix incorrect integer return values from :mod:`ctypes` callbacks on some
2+
platforms, such as s390x.

‎Modules/_ctypes/_ctypes_test.c‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,11 @@ EXPORT(long long) _testfunc_callback_q_qf(long long value,
580580
return sum;
581581
}
582582

583+
EXPORT(long long) _testfunc_callback_int_to_longlong(int (*func)(void))
584+
{
585+
return func();
586+
}
587+
583588
typedef struct {
584589
char *name;
585590
char *value;

‎Modules/_ctypes/callbacks.c‎

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,22 @@ TryAddRef(PyObject *cnv, CDataObject *obj)
112112
}
113113
#endif
114114

115+
static int
116+
is_narrow_int_ffi_type(int type)
117+
{
118+
switch (type) {
119+
case FFI_TYPE_SINT8:
120+
case FFI_TYPE_UINT8:
121+
case FFI_TYPE_SINT16:
122+
case FFI_TYPE_UINT16:
123+
case FFI_TYPE_SINT32:
124+
case FFI_TYPE_UINT32:
125+
return 1;
126+
default:
127+
return 0;
128+
}
129+
}
130+
115131
/******************************************************************************
116132
*
117133
* Call the python object with all arguments
@@ -233,13 +249,21 @@ static void _CallPythonObject(ctypes_state *st,
233249
if (restype != &ffi_type_void && result) {
234250
assert(setfunc);
235251

236-
#ifdef WORDS_BIGENDIAN
237-
/* See the corresponding code in _ctypes_callproc():
238-
in callproc.c, around line 1219. */
239-
if (restype->type != FFI_TYPE_FLOAT && restype->size < sizeof(ffi_arg)) {
240-
mem = (char *)mem + sizeof(ffi_arg) - restype->size;
241-
}
242-
#endif
252+
/* libffi's closure contract requires integral results narrower
253+
than ffi_arg to fill a whole register, sign-extended if signed;
254+
setfunc() only writes restype->size bytes. */
255+
union {
256+
ffi_arg arg;
257+
int8_t s8;
258+
uint8_t u8;
259+
int16_t s16;
260+
uint16_t u16;
261+
int32_t s32;
262+
uint32_t u32;
263+
} narrow_res = {0};
264+
int narrow = restype->size < sizeof(ffi_arg) &&
265+
is_narrow_int_ffi_type(restype->type);
266+
void *resmem = narrow ? (void *)&narrow_res : mem;
243267

244268
/* keep is an object we have to keep alive so that the result
245269
stays valid. If there is no such object, the setfunc will
@@ -250,7 +274,34 @@ static void _CallPythonObject(ctypes_state *st,
250274
be the result. EXCEPT when restype is py_object - Python
251275
itself knows how to manage the refcount of these objects.
252276
*/
253-
PyObject *keep = setfunc(mem, result, 0);
277+
PyObject *keep = setfunc(resmem, result, 0);
278+
279+
if (narrow && keep != NULL) {
280+
ffi_arg widened;
281+
switch (restype->type) {
282+
case FFI_TYPE_SINT8:
283+
widened = (ffi_arg)(ffi_sarg)narrow_res.s8;
284+
break;
285+
case FFI_TYPE_SINT16:
286+
widened = (ffi_arg)(ffi_sarg)narrow_res.s16;
287+
break;
288+
case FFI_TYPE_SINT32:
289+
widened = (ffi_arg)(ffi_sarg)narrow_res.s32;
290+
break;
291+
case FFI_TYPE_UINT8:
292+
widened = narrow_res.u8;
293+
break;
294+
case FFI_TYPE_UINT16:
295+
widened = narrow_res.u16;
296+
break;
297+
case FFI_TYPE_UINT32:
298+
widened = narrow_res.u32;
299+
break;
300+
default:
301+
Py_UNREACHABLE();
302+
}
303+
memcpy(mem, &widened, sizeof(ffi_arg));
304+
}
254305

255306
if (keep == NULL) {
256307
/* Could not convert callback result. */

0 commit comments

Comments
 (0)