Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion Lib/test/test_ctypes/test_callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
c_short, c_ushort, c_int, c_uint,
c_long, c_longlong, c_ulonglong, c_ulong,
c_float, c_double, c_longdouble, py_object)
from ctypes.util import find_library
from ctypes.util import find_library, wrap_dll_function
from test import support
from test.support import import_helper
_ctypes_test = import_helper.import_module("_ctypes_test")
Expand Down Expand Up @@ -328,6 +328,20 @@ def func():
f"of ctypes callback function {func!r}")
self.assertIsNone(cm.unraisable.object)

def test_narrow_int_return_widened(self):
# gh-156933: Narrow integers were not widened on s390x
CALLBACK = CFUNCTYPE(c_int)

@wrap_dll_function(CDLL(_ctypes_test.__file__))
def _testfunc_callback_int_to_longlong(func: CALLBACK) -> c_longlong:
pass

@CALLBACK
def cb():
return -1

self.assertEqual(_testfunc_callback_int_to_longlong(cb), -1)


if __name__ == '__main__':
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix incorrect integer return values from :mod:`ctypes` callbacks on some
platforms, such as s390x.
5 changes: 5 additions & 0 deletions Modules/_ctypes/_ctypes_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,11 @@ EXPORT(long long) _testfunc_callback_q_qf(long long value,
return sum;
}

EXPORT(long long) _testfunc_callback_int_to_longlong(int (*func)(void))
{
return func();
}

typedef struct {
char *name;
char *value;
Expand Down
67 changes: 59 additions & 8 deletions Modules/_ctypes/callbacks.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,22 @@ TryAddRef(PyObject *cnv, CDataObject *obj)
}
#endif

static int
is_narrow_int_ffi_type(int type)
{
switch (type) {
case FFI_TYPE_SINT8:
case FFI_TYPE_UINT8:
case FFI_TYPE_SINT16:
case FFI_TYPE_UINT16:
case FFI_TYPE_SINT32:
case FFI_TYPE_UINT32:
return 1;
default:
return 0;
}
}

/******************************************************************************
*
* Call the python object with all arguments
Expand Down Expand Up @@ -222,13 +238,21 @@ static void _CallPythonObject(ctypes_state *st,
if (restype != &ffi_type_void && result) {
assert(setfunc);

#ifdef WORDS_BIGENDIAN
/* See the corresponding code in _ctypes_callproc():
in callproc.c, around line 1219. */
if (restype->type != FFI_TYPE_FLOAT && restype->size < sizeof(ffi_arg)) {
mem = (char *)mem + sizeof(ffi_arg) - restype->size;
}
#endif
/* libffi's closure contract requires integral results narrower
than ffi_arg to fill a whole register, sign-extended if signed;
setfunc() only writes restype->size bytes. */
union {
ffi_arg arg;
int8_t s8;
uint8_t u8;
int16_t s16;
uint16_t u16;
int32_t s32;
uint32_t u32;
} narrow_res = {0};
int narrow = restype->size < sizeof(ffi_arg) &&
is_narrow_int_ffi_type(restype->type);
void *resmem = narrow ? (void *)&narrow_res : mem;

/* keep is an object we have to keep alive so that the result
stays valid. If there is no such object, the setfunc will
Expand All @@ -239,7 +263,34 @@ static void _CallPythonObject(ctypes_state *st,
be the result. EXCEPT when restype is py_object - Python
itself knows how to manage the refcount of these objects.
*/
PyObject *keep = setfunc(mem, result, restype->size);
PyObject *keep = setfunc(resmem, result, restype->size);

if (narrow && keep != NULL) {
ffi_arg widened;
switch (restype->type) {
case FFI_TYPE_SINT8:
widened = (ffi_arg)(ffi_sarg)narrow_res.s8;
break;
case FFI_TYPE_SINT16:
widened = (ffi_arg)(ffi_sarg)narrow_res.s16;
break;
case FFI_TYPE_SINT32:
widened = (ffi_arg)(ffi_sarg)narrow_res.s32;
break;
case FFI_TYPE_UINT8:
widened = narrow_res.u8;
break;
case FFI_TYPE_UINT16:
widened = narrow_res.u16;
break;
case FFI_TYPE_UINT32:
widened = narrow_res.u32;
break;
default:
Py_UNREACHABLE();
}
memcpy(mem, &widened, sizeof(ffi_arg));
}

if (keep == NULL) {
/* Could not convert callback result. */
Expand Down
Loading