Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Private padding and alignment fields + missing Defaut/Clone/Copy implementations #2782

Open
ClementNerma opened this issue Mar 18, 2024 · 3 comments

Comments

@ClementNerma
Copy link

ClementNerma commented Mar 18, 2024

Input C/C++ Header

// libfuse fetched using pkg_config
// Generating bindings for 'fuse.h'

Bindgen Invocation

bindgen::builder()
        // Add clang flags
        .clang_args(compile_flags)
        // Derive Debug, Copy and Default
        .derive_default(true)
        .derive_copy(true)
        .derive_debug(true)
        // Add CargoCallbacks so build.rs is rerun on header changes
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
        // Whitelist "fuse_*" symbols and blocklist everything else
        .allowlist_recursively(false)
        .allowlist_type("[fF][uU][sS][eE].*")
        .allowlist_function("[fF][uU][sS][eE].*")
        .allowlist_var("[fF][uU][sS][eE].*")
        .blocklist_type("fuse_log_func_t")
        .blocklist_function("fuse_set_log_func");
        // Let's generate!
        .header(header_path)
        .generate()
        .unwrap_or_else(|_| panic!("Failed to generate {} bindings", header))

Actual Results

// Extract of the generated bindings file

pub struct stat {
    pub st_dev: ::dev_t,
    pub st_ino: ::ino_t,
    pub st_nlink: ::nlink_t,
    pub st_mode: ::mode_t,
    pub st_uid: ::uid_t,
    pub st_gid: ::gid_t,
    __pad0: ::c_int,
    pub st_rdev: ::dev_t,
    pub st_size: ::off_t,
    pub st_blksize: ::blksize_t,
    pub st_blocks: ::blkcnt_t,
    pub st_atime: ::time_t,
    pub st_atime_nsec: i64,
    pub st_mtime: ::time_t,
    pub st_mtime_nsec: i64,
    pub st_ctime: ::time_t,
    pub st_ctime_nsec: i64,
    __unused: [i64; 3],
}

Expected Results

  • Both __pad0 and __unused should be marked as pub
  • Derives for Default, Clone and Copy are missing

The same problems occur on many other structs.

@emilio
Copy link
Contributor

emilio commented Apr 1, 2024

Could you create a reduced test-case? I suspect something weird is going on, because bindgen padding looks like pub __bindgen_padding_0: ....

@emilio
Copy link
Contributor

emilio commented Apr 1, 2024

Or like, something that I can run locally at least :)

@ClementNerma
Copy link
Author

Sure, here is an example:

# Cargo.toml
[package]
name = "buggy-crate"
version = "0.1.0"
edition = "2021"

[build-dependencies]
bindgen = "0.69.4"
pkg-config = "0.3.30"
// build.rs
use std::{env, iter, path::PathBuf};

pub static FUSE_VERSION: u32 = 316;
pub static FUSE_LIBNAME: &str = "fuse3";

fn fuse_binding_filter(builder: bindgen::Builder) -> bindgen::Builder {
    let mut builder = builder
        // Whitelist "fuse_*" symbols and blocklist everything else
        .allowlist_recursively(false)
        .allowlist_type("[fF][uU][sS][eE].*")
        .allowlist_function("[fF][uU][sS][eE].*")
        .allowlist_var("[fF][uU][sS][eE].*")
        .blocklist_type("fuse_log_func_t")
        .blocklist_function("fuse_set_log_func");

    // TODO: properly bind fuse_log_func_t and allowlist fuse_set_log_func again

    if cfg!(target_os = "macos") {
        // osxfuse needs this type
        builder = builder.allowlist_type("setattr_x");
    }

    builder
}

fn cuse_binding_filter(builder: bindgen::Builder) -> bindgen::Builder {
    builder
        // Whitelist "cuse_*" symbols and blocklist everything else
        .allowlist_recursively(false)
        .allowlist_type("[cC][uU][sS][eE].*")
        .allowlist_function("[cC][uU][sS][eE].*")
        .allowlist_var("[cC][uU][sS][eE].*")
}

fn generate_fuse_bindings(
    header: &str,
    fuse_lib: &pkg_config::Library,
    binding_filter: fn(bindgen::Builder) -> bindgen::Builder,
) {
    // Find header file
    let mut header_path: Option<PathBuf> = None;

    for include_path in fuse_lib.include_paths.iter() {
        let test_path = include_path.join(header);

        if test_path.exists() {
            header_path = Some(test_path);
            break;
        }
    }

    let header_path = header_path
        .unwrap_or_else(|| panic!("Cannot find {}", header))
        .to_str()
        .unwrap_or_else(|| panic!("Path to {} contains invalid unicode characters", header))
        .to_string();

    // Gather fuse defines
    let defines = fuse_lib.defines.iter().map(|(key, val)| match val {
        Some(val) => format!("-D{}={}", key, val),
        None => format!("-D{}", key),
    });

    // Gather include paths
    let includes = fuse_lib
        .include_paths
        .iter()
        .map(|dir| format!("-I{}", dir.display()));

    // API version definition
    let api_define = iter::once(format!("-DFUSE_USE_VERSION={FUSE_VERSION}"));

    // Chain compile flags
    let compile_flags = defines.chain(includes).chain(api_define);

    // Create bindgen builder
    let mut builder = bindgen::builder()
        // Add clang flags
        .clang_args(compile_flags)
        // Derive Debug, Copy and Default
        .derive_default(true)
        .derive_copy(true)
        .derive_debug(true)
        // Add CargoCallbacks so build.rs is rerun on header changes
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));

    builder = binding_filter(builder);

    // Generate bindings
    let bindings = builder
        .header(header_path)
        .generate()
        .unwrap_or_else(|_| panic!("Failed to generate {} bindings", header));

    // Write bindings to file
    let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
    let bindings_path = out_dir.join(header.replace(".h", ".rs"));

    bindings
        .write_to_file(&bindings_path)
        .unwrap_or_else(|_| panic!("Failed to write {}", bindings_path.display()));
}

fn main() {
    let mut pkgcfg = pkg_config::Config::new();
    pkgcfg.cargo_metadata(false);

    // Find libfuse

    let fuse_lib = pkgcfg
        .cargo_metadata(true)
        .probe(FUSE_LIBNAME)
        .unwrap_or_else(|err| panic!("Failed to find pkg-config module {FUSE_LIBNAME} ({err})"));

    // Generate highlevel bindings
    generate_fuse_bindings("fuse.h", &fuse_lib, fuse_binding_filter);

    // Generate lowlevel bindings
    generate_fuse_bindings("fuse_lowlevel.h", &fuse_lib, fuse_binding_filter);

    // Generate lowlevel cuse bindings
    generate_fuse_bindings("cuse_lowlevel.h", &fuse_lib, cuse_binding_filter);
}

Note: you need to have both pkg-config and libfuse-dev installed.

Run this, and it will generate three bindings files in target. There you can find many structs with the problem I mentioned.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants