Skip to content

Generated bitfield accessors shift by 64 when a field spans a 65-bit window #3416

Description

@lwz23

Input C/C++ header

struct __attribute__((packed)) S {
    unsigned long long p : 2;
    unsigned long long x : 63;
};

Bindgen invocation

$ bindgen input.h \
    --rust-target 1.75 \
    --rust-edition 2021 \
    -- -std=gnu11 > bindings.rs

Actual output

The generated record layout is correct:

#[repr(C, packed)]
#[derive(Debug, Copy, Clone)]
pub struct S {
    pub _bitfield_1: __BindgenBitfieldUnit<[u8; 9usize]>,
}

impl S {
    #[inline]
    pub fn x(&self) -> ::std::os::raw::c_ulonglong {
        self._bitfield_1.get_const::<2usize, 63u8>() as u64 as _
    }
}

The generated helper calculates a nine-byte window and accumulates it in a
u64:

let bit_shift = BIT_OFFSET % 8;
let bytes_needed = (BIT_WIDTH as usize + bit_shift + 7) / 8;
let mut val = 0u64;
let mut i = 0;
while i < bytes_needed {
    val |= (self.storage[start_byte + i].reverse_bits() as u64) << (i * 8);
    i += 1;
}

I ran the unchanged binding against the all-ones representation. Clang reports
that x occupies bits 2 through 64 and that S is nine bytes:

0:0-1 | unsigned long long p
0:2-64 | unsigned long long x
       | [sizeof=9, align=1]

The C accessor returns:

x=0x7fffffffffffffff

With overflow checks enabled, the generated Rust getter panics:

attempt to shift left with overflow

With an optimized build and overflow checks disabled, it returns:

x=0x3fffffffffffffff

What is wrong

x is 63 bits wide but begins at intra-byte offset 2. Its extraction window is
therefore 65 bits and spans nine bytes. When the loop reaches the ninth byte,
it shifts a u64 by 64. The setter paths perform the corresponding right shift
by 64 while extracting the ninth value and mask bytes.

The storage type and record layout are correct. The error is in the generated
getter, setter, raw accessor, and constructor method bodies.

Expected output

The generated accessors should handle a field whose width plus intra-byte
offset exceeds 64 bits without shifting a u64 by 64. Both builds should
produce:

x=0x7fffffffffffffff

The setters, raw setters, and new_bitfield_1 should also write a value that
the C declaration reads back as 0x7fffffffffffffff.

Impact

With current main, the generated getters, setters, raw accessors, and
constructor panic when overflow checks are enabled. In an optimized build, the
getter loses the highest bit, and the write paths store
0x3fffffffffffffff where C expects 0x7fffffffffffffff.

Bindgen 0.72.1 does not reproduce this regression.

Environment

bindgen: 0.72.0, current main 25b23474496e78f6a1fbf1c02cb66f11e4d176d0
clang/libclang: 15.0.7
rustc: 1.75.0
target: x86_64-unknown-linux-gnu
OS: Ubuntu 22.04.5 LTS, x86_64

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions