Skip to content

[libclang/python] Fix visitor callback return data type - #221024

Merged
AaronBallman merged 3 commits into
llvm:mainfrom
miladfarca:s390-libclang-fix
Sep 24, 2026
Merged

AaronBallman merged 3 commits into
llvm:mainfrom
miladfarca:s390-libclang-fix

Conversation

@miladfarca

Copy link
Copy Markdown
Contributor

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.

@llvmorg-github-actions llvmorg-github-actions Bot added clang Clang issues not falling into any other category clang:as-a-library libclang and C++ API labels Sep 3, 2026
@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-clang

Author: Milad Fa (miladfarca)

Changes

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.


Full diff: https://github.com/llvm/llvm-project/pull/221024.diff

1 Files Affected:

  • (modified) clang/bindings/python/clang/cindex.py (+3-2)
diff --git a/clang/bindings/python/clang/cindex.py b/clang/bindings/python/clang/cindex.py
index bc00dd770ce3b..4423096085535 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,
@@ -4117,8 +4118,8 @@ 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)
+cursor_visit_callback = CFUNCTYPE(c_long, Cursor, Cursor, py_object)
+fields_visit_callback = CFUNCTYPE(c_long, Cursor, py_object)
 
 # Functions strictly alphabetical order.
 FUNCTION_LIST: list[LibFunc] = [

@DeinAlptraum

Copy link
Copy Markdown
Contributor

Perhaps I'm misunderstanding something, but the visitor should return instances of CXChildVisitResult, which is a bare enum with no explicit type and only 3 variants, so a regular int should be enough. Why would this need a long?

https://timsong-cpp.github.io/cppwp/n4659/dcl.enum#7

For an enumeration whose underlying type is not fixed, the underlying type is an integral type that can represent all the enumerator values defined in the enumeration. If no integral type can represent all the enumerator values, the enumeration is ill-formed.
It is implementation-defined which integral type is used as the underlying type except that the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int.
If the [enumerator-list](https://timsong-cpp.github.io/cppwp/n4659/dcl.enum#nt:enumerator-list) is empty, the underlying type is as if the enumeration had a single enumerator with value 0.

Also adding @Endilll because C++ isn't my strong suite

@DeinAlptraum
DeinAlptraum requested a review from Endilll September 4, 2026 00:17

@Endilll Endilll left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but I want @AaronBallman to also take a look before this lands

@Endilll
Endilll requested a review from AaronBallman September 4, 2026 06:37
@Endilll

Endilll commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Perhaps I'm misunderstanding something, but the visitor should return instances of CXChildVisitResult, which is a bare enum with no explicit type and only 3 variants, so a regular int should be enough. Why would this need a long?

The issue is that when this enum is put into a register that serves as the return slot, c_int preserves the upper four bytes of that register, which causes them to be garbage alongside the actual value in the lower 4 bytes. So the fix definitely makes sense on some level, but I wonder why ctypes does not sign-extend c_int on s390x to satisfy their psABI.

@miladfarca can you raise or find an existing issue about this on ctypes side, and put it as a comment in the code? My hope is that this would be a somewhat temporary fix, until Python releases with this bug reach EOL. Also, can we limit this workaround to s390x? I guess ctypes has less bugs on mainstream platforms, because I haven't seen other platform complaining about this.

For the reference, I dug up the following in s390x psABI version 1.7 (1.2.5 "Return Values"):

Any integer type with 64 or fewer bits, including _BitInt(𝑁) with 𝑁 ≤ 64, _Bool,
as well as any enum type, is returned in r2. The return value is zero- or sign-extended
to 64 bits, as appropriate.

@miladfarca

Copy link
Copy Markdown
Contributor Author

@DeinAlptraum It's correct that the type is an enum however similar to @Endilll's explanation, ctypes has no concept of an enum. They get passed a data size (8/4/2 etc.) and in this case all they see is a 4 byte data type which per s390x ABI (correctly pasted above) and libffi contract, needs to be sign or zero extended to a full register size.

I have also created an issue on cpython upstream and have made this patch s390x specific:
python/cpython#156933

@Endilll Endilll left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@AaronBallman AaronBallman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally LGTM! But this should come with a release note so users know about the fix. It should also come with a test which demonstrates the fix, if possible.

@miladfarca

Copy link
Copy Markdown
Contributor Author

I have added a release note as well as a test, the crash itself came from V8's metagen on s390x.

@DeinAlptraum

DeinAlptraum commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

LGMT, but could you confirm again this is what you imaged @AaronBallman ?

@AaronBallman AaronBallman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Can you update your email address to be public (https://llvm.org/docs/DeveloperPolicy.html#email-addresses), please? It comes up as this for me:

[email protected]

Then we can land the changes on your behalf.

@miladfarca

Copy link
Copy Markdown
Contributor Author

@AaronBallman not a problem, I've made it public.

@AaronBallman
AaronBallman merged commit f05ae44 into llvm:main Sep 24, 2026
13 checks passed
@ZeroIntensity

Copy link
Copy Markdown

I've merged a fix for this upstream, which will be available in Python 3.13.16 and 3.14.8 sometime in the next week or so (see PEPs 719 and 745 for the release schedule).

laoshd pushed a commit to laoshd/llvm-project that referenced this pull request Sep 30, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:as-a-library libclang and C++ API clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants