rust-lang / rust-bindgen

Automatically generates Rust FFI bindings to C (and some C++) libraries.
https://rust-lang.github.io/rust-bindgen/
BSD 3-Clause "New" or "Revised" License
4.5k stars 700 forks source link

Deriving default for a struct with a rustified enum member that isn't representable by 0 generates undefined behavior #2974

Open Supreeeme opened 2 weeks ago

Supreeeme commented 2 weeks ago
// header.h
enum E { A = 1, B, C };

struct S {
    enum E e;
};

bindgen invocation:

bindgen header.h --rustified-enum '.*' --with-derive-default 

bindgen output:

/* automatically generated by rust-bindgen 0.70.1 */

#[repr(u32)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub enum E {
    A = 1,
    B = 2,
    C = 3,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct S {
    pub e: E,
}
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
const _: () = {
    ["Size of S"][::std::mem::size_of::<S>() - 4usize];
    ["Alignment of S"][::std::mem::align_of::<S>() - 4usize];
    ["Offset of field: S::e"][::std::mem::offset_of!(S, e) - 0usize];
};
impl Default for S {
    fn default() -> Self {
        let mut s = ::std::mem::MaybeUninit::<Self>::uninit();
        unsafe {
            ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1);
            s.assume_init()
        }
    }
}

This implementation of Default is UB, since it generates an E with an invalid tag.

pvdrz commented 2 weeks ago

I'm slightly annoyed by the behaviour of rustified_enum in general. Given that there is no sane way to pick a default value for the enum in this case, I'm not sure how a proper fix for this looks like. Maybe we could just avoid deriving default for rustified enums unconditionally

Supreeeme commented 2 weeks ago

I think if a struct has a rustified enum member it'd be best to just avoid generating Default, yeah. Or even better, only avoid generating default if a rustified enum doesn't have a variant equal to 0, although I don't know if that special case would be worth the hassle.