Skip to content

Commit cc3ab21

Browse files
miss-islingtonlazergZeroIntensity
authored
[3.15] gh-156933: Widen narrow integer results in ctypes callbacks (GH-157045) (#158295)
Co-authored-by: Lazizbek Ergashev <lazerg2@gmail.com> Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
1 parent 5985fcb commit cc3ab21

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
@@ -638,6 +638,11 @@ EXPORT(long long) _testfunc_callback_q_qf(long long value,
638638
return sum;
639639
}
640640

641+
EXPORT(long long) _testfunc_callback_int_to_longlong(int (*func)(void))
642+
{
643+
return func();
644+
}
645+
641646
typedef struct {
642647
char *name;
643648
char *value;

‎Modules/_ctypes/callbacks.c‎

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,22 @@ TryAddRef(PyObject *cnv, CDataObject *obj)
101101
}
102102
#endif
103103

104+
static int
105+
is_narrow_int_ffi_type(int type)
106+
{
107+
switch (type) {
108+
case FFI_TYPE_SINT8:
109+
case FFI_TYPE_UINT8:
110+
case FFI_TYPE_SINT16:
111+
case FFI_TYPE_UINT16:
112+
case FFI_TYPE_SINT32:
113+
case FFI_TYPE_UINT32:
114+
return 1;
115+
default:
116+
return 0;
117+
}
118+
}
119+
104120
/******************************************************************************
105121
*
106122
* Call the python object with all arguments
@@ -222,13 +238,21 @@ static void _CallPythonObject(ctypes_state *st,
222238
if (restype != &ffi_type_void && result) {
223239
assert(setfunc);
224240

225-
#ifdef WORDS_BIGENDIAN
226-
/* See the corresponding code in _ctypes_callproc():
227-
in callproc.c, around line 1219. */
228-
if (restype->type != FFI_TYPE_FLOAT && restype->size < sizeof(ffi_arg)) {
229-
mem = (char *)mem + sizeof(ffi_arg) - restype->size;
230-
}
231-
#endif
241+
/* libffi's closure contract requires integral results narrower
242+
than ffi_arg to fill a whole register, sign-extended if signed;
243+
setfunc() only writes restype->size bytes. */
244+
union {
245+
ffi_arg arg;
246+
int8_t s8;
247+
uint8_t u8;
248+
int16_t s16;
249+
uint16_t u16;
250+
int32_t s32;
251+
uint32_t u32;
252+
} narrow_res = {0};
253+
int narrow = restype->size < sizeof(ffi_arg) &&
254+
is_narrow_int_ffi_type(restype->type);
255+
void *resmem = narrow ? (void *)&narrow_res : mem;
232256

233257
/* keep is an object we have to keep alive so that the result
234258
stays valid. If there is no such object, the setfunc will
@@ -239,7 +263,34 @@ static void _CallPythonObject(ctypes_state *st,
239263
be the result. EXCEPT when restype is py_object - Python
240264
itself knows how to manage the refcount of these objects.
241265
*/
242-
PyObject *keep = setfunc(mem, result, restype->size);
266+
PyObject *keep = setfunc(resmem, result, restype->size);
267+
268+
if (narrow && keep != NULL) {
269+
ffi_arg widened;
270+
switch (restype->type) {
271+
case FFI_TYPE_SINT8:
272+
widened = (ffi_arg)(ffi_sarg)narrow_res.s8;
273+
break;
274+
case FFI_TYPE_SINT16:
275+
widened = (ffi_arg)(ffi_sarg)narrow_res.s16;
276+
break;
277+
case FFI_TYPE_SINT32:
278+
widened = (ffi_arg)(ffi_sarg)narrow_res.s32;
279+
break;
280+
case FFI_TYPE_UINT8:
281+
widened = narrow_res.u8;
282+
break;
283+
case FFI_TYPE_UINT16:
284+
widened = narrow_res.u16;
285+
break;
286+
case FFI_TYPE_UINT32:
287+
widened = narrow_res.u32;
288+
break;
289+
default:
290+
Py_UNREACHABLE();
291+
}
292+
memcpy(mem, &widened, sizeof(ffi_arg));
293+
}
243294

244295
if (keep == NULL) {
245296
/* Could not convert callback result. */

0 commit comments

Comments
 (0)