https://github.com/python/cpython/commit/cc3ab211a8159e7e73a08ec7b0c93417af8e9b91
commit: cc3ab211a8159e7e73a08ec7b0c93417af8e9b91
branch: 3.15
author: Miss Islington (bot) <[email protected]>
committer: hugovk <[email protected]>
date: 2026-09-28T09:03:12+03:00
summary:

[3.15] gh-156933: Widen narrow integer results in `ctypes` callbacks 
(GH-157045) (#158295)

Co-authored-by: Lazizbek Ergashev <[email protected]>
Co-authored-by: Peter Bierma <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-09-07-00-21-31.gh-issue-156933.OalCjC.rst
M Lib/test/test_ctypes/test_callbacks.py
M Modules/_ctypes/_ctypes_test.c
M Modules/_ctypes/callbacks.c

diff --git a/Lib/test/test_ctypes/test_callbacks.py 
b/Lib/test/test_ctypes/test_callbacks.py
index 6c7c2e5270736e4..d1ac62816eb8222 100644
--- a/Lib/test/test_ctypes/test_callbacks.py
+++ b/Lib/test/test_ctypes/test_callbacks.py
@@ -328,6 +328,21 @@ 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)
+
+        dll = CDLL(_ctypes_test.__file__)
+        _testfunc_callback_int_to_longlong = 
dll._testfunc_callback_int_to_longlong
+        _testfunc_callback_int_to_longlong.argtypes = [CALLBACK]
+        _testfunc_callback_int_to_longlong.restype = c_longlong
+
+        @CALLBACK
+        def cb():
+            return -1
+
+        self.assertEqual(_testfunc_callback_int_to_longlong(cb), -1)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git 
a/Misc/NEWS.d/next/Library/2026-09-07-00-21-31.gh-issue-156933.OalCjC.rst 
b/Misc/NEWS.d/next/Library/2026-09-07-00-21-31.gh-issue-156933.OalCjC.rst
new file mode 100644
index 000000000000000..afe1dffa96b403d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-09-07-00-21-31.gh-issue-156933.OalCjC.rst
@@ -0,0 +1,2 @@
+Fix incorrect integer return values from :mod:`ctypes` callbacks on some
+platforms, such as s390x.
diff --git a/Modules/_ctypes/_ctypes_test.c b/Modules/_ctypes/_ctypes_test.c
index 991ff0d675c2f1c..6a5b20143f55272 100644
--- a/Modules/_ctypes/_ctypes_test.c
+++ b/Modules/_ctypes/_ctypes_test.c
@@ -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;
diff --git a/Modules/_ctypes/callbacks.c b/Modules/_ctypes/callbacks.c
index fd508ae61f2e040..e67817e8f6f4d96 100644
--- a/Modules/_ctypes/callbacks.c
+++ b/Modules/_ctypes/callbacks.c
@@ -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
@@ -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
@@ -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. */

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to