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.23k stars 679 forks source link

Failed to generate `Default` impl for arrays larger than 32 entries #2803

Open wmmc88 opened 3 months ago

wmmc88 commented 3 months ago

Input C/C++ Header

#define CCHDEVICENAME 32
typedef long LONG;
typedef char CHAR;
typedef unsigned long DWORD;

typedef struct tagRECT
{
    LONG    left;
    LONG    top;
    LONG    right;
    LONG    bottom;
} RECT;
typedef struct tagMONITORINFO
{
    DWORD   cbSize;
    RECT    rcMonitor;
    RECT    rcWork;
    DWORD   dwFlags;
} MONITORINFO, *LPMONITORINFO;
typedef struct tagMONITORINFOEXA
{
    MONITORINFO;
    CHAR        szDevice[CCHDEVICENAME];
} MONITORINFOEXA, *LPMONITORINFOEXA;

Bindgen Invocation

use std::env;
use std::path::PathBuf;

fn main() {

    let bindings = bindgen::Builder::default()
        .header_contents(
            "input.h",
            r#"
#define CCHDEVICENAME 32
typedef long LONG;
typedef char CHAR;
typedef unsigned long DWORD;

typedef struct tagRECT
{
    LONG    left;
    LONG    top;
    LONG    right;
    LONG    bottom;
} RECT;
typedef struct tagMONITORINFO
{
    DWORD   cbSize;
    RECT    rcMonitor;
    RECT    rcWork;
    DWORD   dwFlags;
} MONITORINFO, *LPMONITORINFO;
typedef struct tagMONITORINFOEXA
{
    MONITORINFO;
    CHAR        szDevice[CCHDEVICENAME];
} MONITORINFOEXA, *LPMONITORINFOEXA;"#,
        )
        .derive_default(true)
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
        .generate()
        .expect("Unable to generate bindings");

    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");
}

Actual Results

error[E0277]: the trait bound `[u8; 40]: Default` is not satisfied
   --> D:\git-repos\bindgen-test\target\debug\build\bindgen-test-39d5729a71a2ce1c\out/bindings.rs:140:5
    |
138 | #[derive(Debug, Default, Copy, Clone)]
    |                 ------- in this derive macro expansion
139 | pub struct tagMONITORINFOEXA {
140 |     pub __bindgen_padding_0: [u8; 40usize],
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Default` is not implemented for `[u8; 40]`
    |
    = help: the following other types implement trait `Default`:
              [T; 0]
              [T; 1]
              [T; 2]
              [T; 3]
              [T; 4]
              [T; 5]
              [T; 6]
              [T; 7]
            and 27 others
    = note: this error originates in the derive macro `Default` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0277`.
error: could not compile `bindgen-test` (lib) due to 1 previous error

Caused by:
  process didn't exit successfully: `sccache rustc --crate-name bindgen_test --edition=2021 src\lib.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts,future-incompat --diagnostic-width=191 --crate-type lib --emit=dep-info,metadata,link -C embed-bitcode=no -C debuginfo=2 -C metadata=dfb5b3ab9206c6f2 -C extra-filename=-dfb5b3ab9206c6f2 --out-dir D:\git-repos\bindgen-test\target\debug\deps -C incremental=D:\git-repos\bindgen-test\target\debug\incremental -L dependency=D:\git-repos\bindgen-test\target\debug\deps` (exit code: 1)

and/or

/* automatically generated by rust-bindgen 0.69.4 */

pub const CCHDEVICENAME: u32 = 32;
pub type LONG = ::std::os::raw::c_long;
pub type CHAR = ::std::os::raw::c_char;
pub type DWORD = ::std::os::raw::c_ulong;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct tagRECT {
    pub left: LONG,
    pub top: LONG,
    pub right: LONG,
    pub bottom: LONG,
}
#[test]
fn bindgen_test_layout_tagRECT() {
    const UNINIT: ::std::mem::MaybeUninit<tagRECT> = ::std::mem::MaybeUninit::uninit();
    let ptr = UNINIT.as_ptr();
    assert_eq!(
        ::std::mem::size_of::<tagRECT>(),
        16usize,
        concat!("Size of: ", stringify!(tagRECT))
    );
    assert_eq!(
        ::std::mem::align_of::<tagRECT>(),
        4usize,
        concat!("Alignment of ", stringify!(tagRECT))
    );
    assert_eq!(
        unsafe { ::std::ptr::addr_of!((*ptr).left) as usize - ptr as usize },
        0usize,
        concat!(
            "Offset of field: ",
            stringify!(tagRECT),
            "::",
            stringify!(left)
        )
    );
    assert_eq!(
        unsafe { ::std::ptr::addr_of!((*ptr).top) as usize - ptr as usize },
        4usize,
        concat!(
            "Offset of field: ",
            stringify!(tagRECT),
            "::",
            stringify!(top)
        )
    );
    assert_eq!(
        unsafe { ::std::ptr::addr_of!((*ptr).right) as usize - ptr as usize },
        8usize,
        concat!(
            "Offset of field: ",
            stringify!(tagRECT),
            "::",
            stringify!(right)
        )
    );
    assert_eq!(
        unsafe { ::std::ptr::addr_of!((*ptr).bottom) as usize - ptr as usize },
        12usize,
        concat!(
            "Offset of field: ",
            stringify!(tagRECT),
            "::",
            stringify!(bottom)
        )
    );
}
pub type RECT = tagRECT;
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct tagMONITORINFO {
    pub cbSize: DWORD,
    pub rcMonitor: RECT,
    pub rcWork: RECT,
    pub dwFlags: DWORD,
}
#[test]
fn bindgen_test_layout_tagMONITORINFO() {
    const UNINIT: ::std::mem::MaybeUninit<tagMONITORINFO> = ::std::mem::MaybeUninit::uninit();
    let ptr = UNINIT.as_ptr();
    assert_eq!(
        ::std::mem::size_of::<tagMONITORINFO>(),
        40usize,
        concat!("Size of: ", stringify!(tagMONITORINFO))
    );
    assert_eq!(
        ::std::mem::align_of::<tagMONITORINFO>(),
        4usize,
        concat!("Alignment of ", stringify!(tagMONITORINFO))
    );
    assert_eq!(
        unsafe { ::std::ptr::addr_of!((*ptr).cbSize) as usize - ptr as usize },
        0usize,
        concat!(
            "Offset of field: ",
            stringify!(tagMONITORINFO),
            "::",
            stringify!(cbSize)
        )
    );
    assert_eq!(
        unsafe { ::std::ptr::addr_of!((*ptr).rcMonitor) as usize - ptr as usize },
        4usize,
        concat!(
            "Offset of field: ",
            stringify!(tagMONITORINFO),
            "::",
            stringify!(rcMonitor)
        )
    );
    assert_eq!(
        unsafe { ::std::ptr::addr_of!((*ptr).rcWork) as usize - ptr as usize },
        20usize,
        concat!(
            "Offset of field: ",
            stringify!(tagMONITORINFO),
            "::",
            stringify!(rcWork)
        )
    );
    assert_eq!(
        unsafe { ::std::ptr::addr_of!((*ptr).dwFlags) as usize - ptr as usize },
        36usize,
        concat!(
            "Offset of field: ",
            stringify!(tagMONITORINFO),
            "::",
            stringify!(dwFlags)
        )
    );
}
pub type MONITORINFO = tagMONITORINFO;
pub type LPMONITORINFO = *mut tagMONITORINFO;
#[repr(C)]
#[repr(align(4))]
#[derive(Debug, Default, Copy, Clone)]
pub struct tagMONITORINFOEXA {
    pub __bindgen_padding_0: [u8; 40usize],
    pub szDevice: [CHAR; 32usize],
}
#[test]
fn bindgen_test_layout_tagMONITORINFOEXA() {
    const UNINIT: ::std::mem::MaybeUninit<tagMONITORINFOEXA> = ::std::mem::MaybeUninit::uninit();
    let ptr = UNINIT.as_ptr();
    assert_eq!(
        ::std::mem::size_of::<tagMONITORINFOEXA>(),
        72usize,
        concat!("Size of: ", stringify!(tagMONITORINFOEXA))
    );
    assert_eq!(
        ::std::mem::align_of::<tagMONITORINFOEXA>(),
        4usize,
        concat!("Alignment of ", stringify!(tagMONITORINFOEXA))
    );
    assert_eq!(
        unsafe { ::std::ptr::addr_of!((*ptr).szDevice) as usize - ptr as usize },
        40usize,
        concat!(
            "Offset of field: ",
            stringify!(tagMONITORINFOEXA),
            "::",
            stringify!(szDevice)
        )
    );
}
pub type MONITORINFOEXA = tagMONITORINFOEXA;
pub type LPMONITORINFOEXA = *mut tagMONITORINFOEXA;

Expected Results

I expected it to generate a default implementation successfully. This seems to be another regression of #1718/#1719 and #2082, where it fails to generate a valid Default when the array has more than the 32 entries (where rust itself generates a Default for <=32 entries).

emilio commented 2 months ago

Which rust version are you using? We have code to deal with this, see https://github.com/rust-lang/rust-bindgen/commit/a380678490b821201f10b861144b15bbe08694a2

emilio commented 2 months ago

Ah, so I couldn't repro this, but that's because for some reason --target=x86_64-pc-win32 is needed to repro:

./target/debug/bindgen --with-derive-default t.h -- --target=x86_64-pc-win32

Is the command I used to reproduce. It seems the MONITORINFO; line is kind of like a member, but it's unnamed and clang doesn't expose it as such... o.O

emilio commented 2 months ago

And somehow that only happens when targeting windows

emilio commented 2 months ago

I think that limitation in rustc is mostly historical? https://github.com/rust-lang/rust/pull/124163 removes it.

wmmc88 commented 2 months ago

@emilio

Which rust version are you using? We have code to deal with this, see https://github.com/rust-lang/rust-bindgen/commit/a380678490b821201f10b861144b15bbe08694a2

I'm able to repro on latest stable(stable-x86_64-pc-windows-msvc unchanged - rustc 1.77.2 (25ef9e3d8 2024-04-09)), beta(beta-x86_64-pc-windows-msvc updated - rustc 1.78.0-beta.7 (6fd191292 2024-04-12) (from rustc 1.78.0-beta.5 (9eff51035 2024-04-06))), and nightly(nightly-x86_64-pc-windows-msvc updated - rustc 1.79.0-nightly (e3181b091 2024-04-18) (from rustc 1.79.0-nightly (8b2459c1f 2024-04-09))). I'm assuming this is some sort of regression.

Ah, so I couldn't repro this, but that's because for some reason --target=x86_64-pc-win32 is needed to repro:

I am using x64 windows w/ msvc toolchain, so my native target is x86_64-pc-windows-msvc

I've also tried enabling clang's msvc compat, and it doesn't alter behavior here:

.clang_args([
            "-fms-compatibility",
            "-fms-extensions",
            "-fdelayed-template-parsing",
        ])
wmmc88 commented 2 months ago

@emilio looks like your proposed patch to rust was rejected. Can this continue to be handled by bindgen?

pvdrz commented 2 months ago

I think this issue is a bit deeper than just a missing Default implementation. If we had something like this:

// hello.c
typedef struct bar {
  int *a[17];
} bar;

typedef struct foo {
  bar;
  int b;
} foo;

and ran bindgen hello.c --with-derive-default --no-layout-tests -- --target=x86_64-pc-windows-mvsc -fms-extensions you'd get:

/* automatically generated by rust-bindgen 0.69.4 */

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct bar {
    pub a: [*mut ::std::os::raw::c_int; 17usize],
}
impl Default for bar {
    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()
        }
    }
}
#[repr(C)]
#[repr(align(8))]
#[derive(Debug, Default, Copy, Clone)]
pub struct foo {
    pub __bindgen_padding_0: [u32; 34usize],
    pub b: ::std::os::raw::c_int,
}

implementing Default for foo is most likely wrong as you'd end up with an array of null pointers. I guess this is just a symptom of clang exposing the anonymous struct inside foo as an opaque type instead of giving the proper information.

suprohub commented 1 month ago

bruh