https://github.com/python/cpython/commit/ca0cdf42cf2181c345739bc0e24c8ff771bd8c38
commit: ca0cdf42cf2181c345739bc0e24c8ff771bd8c38
branch: 3.13
author: Peter Bierma <[email protected]>
committer: ZeroIntensity <[email protected]>
date: 2026-09-27T19:04:34Z
summary:

[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 b6f9a50e654dd76394bece71e6a8240ef28107ab)

Co-authored-by: Lazizbek Ergashev <[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 8f483dfe1db8019..56342860c2ca3ae 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 d802f9cb4f7436c..39dc579f45bfb4e 100644
--- a/Modules/_ctypes/_ctypes_test.c
+++ b/Modules/_ctypes/_ctypes_test.c
@@ -580,6 +580,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 675a82577fa9b8c..b9610e1c893700a 100644
--- a/Modules/_ctypes/callbacks.c
+++ b/Modules/_ctypes/callbacks.c
@@ -112,6 +112,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
@@ -233,13 +249,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
@@ -250,7 +274,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, 0);
+        PyObject *keep = setfunc(resmem, result, 0);
+
+        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