[clang] f05ae44 - [libclang/python] Fix visitor callback return data type (#221024)

via cfe-commits cfe-commits at lists.llvm.org
Thu Sep 24 07:01:20 PDT 2026


Author: Milad Fa
Date: 2026-09-24T10:01:11-04:00
New Revision: f05ae447f56404dce7b43145676242d63e0734f4

URL: https://github.com/llvm/llvm-project/commit/f05ae447f56404dce7b43145676242d63e0734f4
DIFF: https://github.com/llvm/llvm-project/commit/f05ae447f56404dce7b43145676242d63e0734f4.diff

LOG: [libclang/python] Fix visitor callback return data type (#221024)

Declare the visitor callbacks return as c_long, i.e. a full register
word rather than c_int.

This is currently causing an issue on s390x, a 64-bit big-endian target.
The V8 JavaScript engine has recently started using libclang as a
dependency
to parse header files. It is currently crashing on s390x with a SIGFPE
due to this issue.
Both the s390x ELF ABI and libffi require the value to be sign/zero
extended to register
size, and leaving it as `c_int` leaves garbage on the high side.

cindex.py calls into libclang through ctypes, which builds the callback
closures using
libffi. With a `c_int` return, ctypes writes only the low 32 bits and
skips the extension
that both the ABI and libffi's closure contract require, so libffi hands
libclang a register
whose high bits are garbage.

Using c_long makes the value fill the register at its native width on
all platforms.

Added: 
    

Modified: 
    clang/bindings/python/clang/cindex.py
    clang/bindings/python/tests/cindex/test_cursor.py
    clang/docs/ReleaseNotes.md

Removed: 
    


################################################################################
diff  --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index 7414691f3ae2a2..d425d3c069ae1b 100644
--- a/clang/bindings/python/clang/cindex.py
+++ b/clang/bindings/python/clang/cindex.py
@@ -71,6 +71,7 @@
     byref,
     c_char_p,
     c_int,
+    c_long,
     c_longlong,
     c_uint,
     c_ulong,
@@ -82,6 +83,7 @@
 )
 
 import os
+import platform
 import sys
 from enum import Enum
 import warnings
@@ -4121,8 +4123,16 @@ def set_property(self, property, value):
 translation_unit_includes_callback = CFUNCTYPE(
     None, c_object_p, POINTER(SourceLocation), c_uint, py_object
 )
-cursor_visit_callback = CFUNCTYPE(c_int, Cursor, Cursor, py_object)
-fields_visit_callback = CFUNCTYPE(c_int, Cursor, py_object)
+# On s390x the visitor callbacks must return a full register word (c_long)
+# rather than c_int. ctypes does not sign/zero-extend a narrow closure return
+# to the full 64-bit return register the s390x ELF ABI requires, leaving
+# garbage in the high bytes. libclang reads the full register and faults with
+# a SIGFPE.
+# TODO: Remove once the ctypes fix (https://github.com/python/cpython/issues/156933)
+# has propagated.
+_visitor_result = c_long if platform.machine() == "s390x" else c_int
+cursor_visit_callback = CFUNCTYPE(_visitor_result, Cursor, Cursor, py_object)
+fields_visit_callback = CFUNCTYPE(_visitor_result, Cursor, py_object)
 
 # Functions strictly alphabetical order.
 FUNCTION_LIST: list[LibFunc] = [

diff  --git a/clang/bindings/python/tests/cindex/test_cursor.py b/clang/bindings/python/tests/cindex/test_cursor.py
index c8ffa15c2e84b9..ada62fcdb1b51c 100644
--- a/clang/bindings/python/tests/cindex/test_cursor.py
+++ b/clang/bindings/python/tests/cindex/test_cursor.py
@@ -10,11 +10,15 @@
     TranslationUnit,
     TypeKind,
     conf,
+    cursor_visit_callback,
+    fields_visit_callback,
 )
 
 
 import gc
+import platform
 import unittest
+from ctypes import c_int, c_long
 
 from .util import get_cursor, get_cursors, get_tu
 
@@ -114,6 +118,15 @@ class C {
 
 
 class TestCursor(unittest.TestCase):
+    def test_visitor_callback_return_type(self):
+        # On s390x the visitor callbacks must return a full register word so
+        # ctypes writes a fully extended return register; a narrow c_int leaves
+        # the high bytes uninitialized and libclang faults with SIGFPE.
+        # Works around https://github.com/python/cpython/issues/156933.
+        expected = c_long if platform.machine() == "s390x" else c_int
+        self.assertEqual(cursor_visit_callback._restype_, expected)
+        self.assertEqual(fields_visit_callback._restype_, expected)
+
     def test_get_children(self):
         tu = get_tu(CHILDREN_TEST)
 

diff  --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 9e01980263f9f1..4349f342b3b89f 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -972,6 +972,12 @@ The `alpha.cplusplus.UseAfterLifetimeEnd` checker was renamed to `alpha.core.Use
 
 ### Python Binding Changes
 
+- Fixed a crash (`SIGFPE`) when traversing an AST via the visitor callbacks
+  (e.g. `Cursor.get_children`) on s390x. The callbacks now return a full
+  register word so the return value is correctly extended, working around a
+  `ctypes` bug (https://github.com/python/cpython/issues/156933) that left the
+  high bytes of the return register uninitialized.
+
 ### OpenMP Support
 
 - Added the OpenMP 6.1 `#pragma omp flatten` loop transformation and the


        


More information about the cfe-commits mailing list