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