diff --git a/bindgen-tests/tests/expectations/tests/issue-3295-aligned-struct-with-alignof.rs b/bindgen-tests/tests/expectations/tests/issue-3295-aligned-struct-with-alignof.rs new file mode 100644 index 0000000000..d1f204c330 --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/issue-3295-aligned-struct-with-alignof.rs @@ -0,0 +1,107 @@ +#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct base_t { + pub __bindgen_anon_1: base_t__bindgen_ty_1, +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct base_t__bindgen_ty_1 { + pub aaa: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct base_t__bindgen_ty_1__bindgen_ty_1 { + pub aaa: ::std::os::raw::c_int, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + [ + "Size of base_t__bindgen_ty_1__bindgen_ty_1", + ][::std::mem::size_of::() - 4usize]; + [ + "Alignment of base_t__bindgen_ty_1__bindgen_ty_1", + ][::std::mem::align_of::() - 4usize]; + [ + "Offset of field: base_t__bindgen_ty_1__bindgen_ty_1::aaa", + ][::std::mem::offset_of!(base_t__bindgen_ty_1__bindgen_ty_1, aaa) - 0usize]; +}; +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + [ + "Size of base_t__bindgen_ty_1", + ][::std::mem::size_of::() - 4usize]; + [ + "Alignment of base_t__bindgen_ty_1", + ][::std::mem::align_of::() - 4usize]; + [ + "Offset of field: base_t__bindgen_ty_1::aaa", + ][::std::mem::offset_of!(base_t__bindgen_ty_1, aaa) - 0usize]; +}; +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of base_t"][::std::mem::size_of::() - 4usize]; + ["Alignment of base_t"][::std::mem::align_of::() - 4usize]; +}; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct mixed_t { + pub __bindgen_anon_1: mixed_t__bindgen_ty_1, + pub __bindgen_anon_2: mixed_t__bindgen_ty_2, +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct mixed_t__bindgen_ty_1 { + pub real_field: ::std::os::raw::c_int, +} +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct mixed_t__bindgen_ty_1__bindgen_ty_1 { + pub phantom: ::std::os::raw::c_int, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + [ + "Size of mixed_t__bindgen_ty_1__bindgen_ty_1", + ][::std::mem::size_of::() - 4usize]; + [ + "Alignment of mixed_t__bindgen_ty_1__bindgen_ty_1", + ][::std::mem::align_of::() - 4usize]; + [ + "Offset of field: mixed_t__bindgen_ty_1__bindgen_ty_1::phantom", + ][::std::mem::offset_of!(mixed_t__bindgen_ty_1__bindgen_ty_1, phantom) - 0usize]; +}; +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + [ + "Size of mixed_t__bindgen_ty_1", + ][::std::mem::size_of::() - 4usize]; + [ + "Alignment of mixed_t__bindgen_ty_1", + ][::std::mem::align_of::() - 4usize]; + [ + "Offset of field: mixed_t__bindgen_ty_1::real_field", + ][::std::mem::offset_of!(mixed_t__bindgen_ty_1, real_field) - 0usize]; +}; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct mixed_t__bindgen_ty_2 { + pub another_real: ::std::os::raw::c_int, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + [ + "Size of mixed_t__bindgen_ty_2", + ][::std::mem::size_of::() - 4usize]; + [ + "Alignment of mixed_t__bindgen_ty_2", + ][::std::mem::align_of::() - 4usize]; + [ + "Offset of field: mixed_t__bindgen_ty_2::another_real", + ][::std::mem::offset_of!(mixed_t__bindgen_ty_2, another_real) - 0usize]; +}; +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of mixed_t"][::std::mem::size_of::() - 8usize]; + ["Alignment of mixed_t"][::std::mem::align_of::() - 4usize]; +}; diff --git a/bindgen-tests/tests/headers/issue-3295-aligned-struct-with-alignof.h b/bindgen-tests/tests/headers/issue-3295-aligned-struct-with-alignof.h new file mode 100644 index 0000000000..7b88920b77 --- /dev/null +++ b/bindgen-tests/tests/headers/issue-3295-aligned-struct-with-alignof.h @@ -0,0 +1,30 @@ +// bindgen-flags: --rust-target=1.77 + +// Regression test for #3295: clang exposes the anonymous struct declared +// inside `__alignof__(...)` of an aligned attribute as a child of the +// surrounding struct. Bindgen used to treat that child as an extra +// anonymous member, doubling the surrounding struct's layout and +// producing failing size assertions in the generated bindings. + +typedef struct base_t { + struct { + int aaa; + } __attribute__((aligned( + __alignof__( + struct {int aaa;} + ) + ))); +} base_t; + +typedef struct mixed_t { + struct { + int real_field; + } __attribute__((aligned( + __alignof__( + struct {int phantom;} + ) + ))); + struct { + int another_real; + }; +} mixed_t; diff --git a/bindgen/clang.rs b/bindgen/clang.rs index 9e614da9f8..aea669f295 100644 --- a/bindgen/clang.rs +++ b/bindgen/clang.rs @@ -389,6 +389,64 @@ impl Cursor { unsafe { clang_getCursorExtent(self.x) } } + /// Returns `true` if `other`'s source range lies fully within + /// `self`'s source range. + /// + /// This is useful for detecting child cursors that clang exposes + /// underneath a parent but whose source location is actually outside the + /// parent's body (e.g. anonymous types declared inside an attribute + /// expression on the parent). + pub(crate) fn extent_contains(&self, other: &Cursor) -> bool { + unsafe { + let parent_range = clang_getCursorExtent(self.x); + let parent_start = clang_getRangeStart(parent_range); + let parent_end = clang_getRangeEnd(parent_range); + let child_range = clang_getCursorExtent(other.x); + let child_start = clang_getRangeStart(child_range); + let child_end = clang_getRangeEnd(child_range); + + let mut parent_file = mem::zeroed::(); + let mut child_file = mem::zeroed::(); + let mut parent_start_off: c_uint = 0; + let mut parent_end_off: c_uint = 0; + let mut child_start_off: c_uint = 0; + let mut child_end_off: c_uint = 0; + + clang_getFileLocation( + parent_start, + &mut parent_file, + ptr::null_mut(), + ptr::null_mut(), + &mut parent_start_off, + ); + clang_getFileLocation( + parent_end, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + &mut parent_end_off, + ); + clang_getFileLocation( + child_start, + &mut child_file, + ptr::null_mut(), + ptr::null_mut(), + &mut child_start_off, + ); + clang_getFileLocation( + child_end, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + &mut child_end_off, + ); + + parent_file == child_file && + child_start_off >= parent_start_off && + child_end_off <= parent_end_off + } + } + /// Get the raw declaration comment for this referent, if one exists. pub(crate) fn raw_comment(&self) -> Option { let s = unsafe { diff --git a/bindgen/ir/comp.rs b/bindgen/ir/comp.rs index 92e69a2fe2..edbbabe86a 100644 --- a/bindgen/ir/comp.rs +++ b/bindgen/ir/comp.rs @@ -1477,7 +1477,20 @@ impl CompInfo { // A declaration of an union or a struct without name // could also be an unnamed field, unfortunately. - if cur.is_anonymous() && cur.kind() != CXCursor_EnumDecl + // However, anonymous types declared *outside* the + // parent struct's body — for example an anonymous + // struct that appears inside an `__alignof__` + // expression on the parent's + // `__attribute__((aligned(...)))` — are exposed by + // clang as child cursors of the parent struct, even + // though they are not real members. Skip those by + // checking that the child's source range is inside + // the parent's source range; otherwise we would + // append a phantom field and corrupt the parent's + // layout (#3295). + if cur.is_anonymous() && + cur.kind() != CXCursor_EnumDecl && + cursor.extent_contains(&cur) { let ty = cur.cur_type(); let public = cur.public_accessible();