diff --git a/bindgen-cli/Cargo.toml b/bindgen-cli/Cargo.toml index b900d9406d..10b4d4cd28 100644 --- a/bindgen-cli/Cargo.toml +++ b/bindgen-cli/Cargo.toml @@ -21,7 +21,7 @@ path = "main.rs" name = "bindgen" [dependencies] -bindgen = { path = "../bindgen", version = "=0.63.0", features = ["cli"] } +bindgen = { path = "../bindgen", version = "=0.63.0", features = ["cli", "experimental"] } shlex = "1" clap = { version = "4", features = ["derive"] } env_logger = { version = "0.9.0", optional = true } diff --git a/bindgen-cli/options.rs b/bindgen-cli/options.rs index 2b09d85fd9..458c7bab1f 100644 --- a/bindgen-cli/options.rs +++ b/bindgen-cli/options.rs @@ -353,6 +353,20 @@ struct BindgenCommand { /// Derive custom traits on a `union`. The value must be of the shape = where is a coma-separated list of derive macros. #[arg(long, value_name = "CUSTOM")] with_derive_custom_union: Vec, + /// Generate wrappers for `static` and `static inline` functions. + #[arg(long, requires = "experimental")] + wrap_static_fns: bool, + /// Sets the path for the source file that must be created due to the presence of `static` and + /// `static inline` functions. + #[arg(long, requires = "experimental", value_name = "PATH")] + wrap_static_fns_path: Option, + /// Sets the suffix added to the extern wrapper functions generated for `static` and `static + /// inline` functions. + #[arg(long, requires = "experimental", value_name = "SUFFIX")] + wrap_static_fns_suffix: Option, + /// Enables experimental features. + #[arg(long)] + experimental: bool, /// Prints the version, and exits #[arg(short = 'V', long)] version: bool, @@ -473,6 +487,10 @@ where with_derive_custom_struct, with_derive_custom_enum, with_derive_custom_union, + wrap_static_fns, + wrap_static_fns_path, + wrap_static_fns_suffix, + experimental: _, version, clang_args, } = command; @@ -978,5 +996,17 @@ where } } + if wrap_static_fns { + builder = builder.wrap_static_fns(true); + } + + if let Some(path) = wrap_static_fns_path { + builder = builder.wrap_static_fns_path(path); + } + + if let Some(suffix) = wrap_static_fns_suffix { + builder = builder.wrap_static_fns_suffix(suffix); + } + Ok((builder, output, verbose)) } diff --git a/bindgen-integration/Cargo.toml b/bindgen-integration/Cargo.toml index e2abb6e2ed..60f0426a76 100644 --- a/bindgen-integration/Cargo.toml +++ b/bindgen-integration/Cargo.toml @@ -7,7 +7,7 @@ publish = false build = "build.rs" [build-dependencies] -bindgen = { path = "../bindgen" } +bindgen = { path = "../bindgen", features = ["experimental"] } cc = "1.0" [features] diff --git a/bindgen-integration/build.rs b/bindgen-integration/build.rs index 0f30ad470f..3cc0edb99b 100644 --- a/bindgen-integration/build.rs +++ b/bindgen-integration/build.rs @@ -4,7 +4,7 @@ extern crate cc; use bindgen::callbacks::{ DeriveInfo, IntKind, MacroParsingBehavior, ParseCallbacks, }; -use bindgen::{Builder, EnumVariation}; +use bindgen::{Builder, CargoCallbacks, EnumVariation}; use std::collections::HashSet; use std::env; use std::path::PathBuf; @@ -28,21 +28,14 @@ impl ParseCallbacks for MacroCallback { MacroParsingBehavior::Default } - fn item_name(&self, original_item_name: &str) -> Option { - if original_item_name.starts_with("my_prefixed_") { - Some( - original_item_name - .trim_start_matches("my_prefixed_") - .to_string(), - ) - } else if original_item_name.starts_with("MY_PREFIXED_") { - Some( - original_item_name - .trim_start_matches("MY_PREFIXED_") - .to_string(), - ) - } else { - None + fn int_macro(&self, name: &str, _value: i64) -> Option { + match name { + "TESTMACRO_CUSTOMINTKIND_PATH" => Some(IntKind::Custom { + name: "crate::MacroInteger", + is_signed: true, + }), + + _ => None, } } @@ -67,17 +60,6 @@ impl ParseCallbacks for MacroCallback { } } - fn int_macro(&self, name: &str, _value: i64) -> Option { - match name { - "TESTMACRO_CUSTOMINTKIND_PATH" => Some(IntKind::Custom { - name: "crate::MacroInteger", - is_signed: true, - }), - - _ => None, - } - } - fn func_macro(&self, name: &str, value: &[&[u8]]) { match name { "TESTMACRO_NONFUNCTIONAL" => { @@ -122,6 +104,24 @@ impl ParseCallbacks for MacroCallback { } } + fn item_name(&self, original_item_name: &str) -> Option { + if original_item_name.starts_with("my_prefixed_") { + Some( + original_item_name + .trim_start_matches("my_prefixed_") + .to_string(), + ) + } else if original_item_name.starts_with("MY_PREFIXED_") { + Some( + original_item_name + .trim_start_matches("MY_PREFIXED_") + .to_string(), + ) + } else { + None + } + } + // Test the "custom derives" capability by adding `PartialEq` to the `Test` struct. fn add_derives(&self, info: &DeriveInfo<'_>) -> Vec { if info.name == "Test" { @@ -149,7 +149,7 @@ impl Drop for MacroCallback { } } -fn main() { +fn setup_macro_test() { cc::Build::new() .cpp(true) .file("cpp/Test.cc") @@ -204,3 +204,75 @@ fn main() { "including stub via include dir must produce correct dep path", ); } + +fn setup_wrap_static_fns_test() { + // GH-1090: https://github.com/rust-lang/rust-bindgen/issues/1090 + // set output directory under /target so it is easy to clean generated files + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + let out_rust_file = out_path.join("extern.rs"); + + let input_header_dir = PathBuf::from("../bindgen-tests/tests/headers/") + .canonicalize() + .expect("Cannot canonicalize libdir path"); + let input_header_file_path = input_header_dir.join("wrap-static-fns.h"); + let input_header_file_path_str = input_header_file_path + .to_str() + .expect("Path could not be converted to a str"); + + // generate external bindings with the external .c and .h files + let bindings = Builder::default() + .header(input_header_file_path_str) + .parse_callbacks(Box::new(CargoCallbacks)) + .wrap_static_fns(true) + .wrap_static_fns_path( + out_path.join("wrap_static_fns").display().to_string(), + ) + .generate() + .expect("Unable to generate bindings"); + + println!("cargo:rustc-link-lib=static=wrap_static_fns"); // tell cargo to link libextern + println!("bindings generated: {}", bindings); + + let obj_path = out_path.join("wrap_static_fns.o"); + let lib_path = out_path.join("libwrap_static_fns.a"); + + // build the external files to check if they work + let clang_output = std::process::Command::new("clang") + .arg("-c") + .arg("-o") + .arg(&obj_path) + .arg(out_path.join("wrap_static_fns.c")) + .arg("-include") + .arg(input_header_file_path) + .output() + .expect("`clang` command error"); + if !clang_output.status.success() { + panic!( + "Could not compile object file:\n{}", + String::from_utf8_lossy(&clang_output.stderr) + ); + } + + let ar_output = std::process::Command::new("ar") + .arg("rcs") + .arg(lib_path) + .arg(obj_path) + .output() + .expect("`ar` command error"); + + if !ar_output.status.success() { + panic!( + "Could not emit library file:\n{}", + String::from_utf8_lossy(&ar_output.stderr) + ); + } + + bindings + .write_to_file(out_rust_file) + .expect("Cound not write bindings to the Rust file"); +} + +fn main() { + setup_macro_test(); + setup_wrap_static_fns_test(); +} diff --git a/bindgen-integration/src/lib.rs b/bindgen-integration/src/lib.rs index 43f71580d2..e89351c3b3 100755 --- a/bindgen-integration/src/lib.rs +++ b/bindgen-integration/src/lib.rs @@ -4,6 +4,10 @@ mod bindings { include!(concat!(env!("OUT_DIR"), "/test.rs")); } +mod extern_bindings { + include!(concat!(env!("OUT_DIR"), "/extern.rs")); +} + use std::ffi::CStr; use std::mem; use std::os::raw::c_int; @@ -286,3 +290,35 @@ fn test_custom_derive() { assert!(meter < lightyear); assert!(meter > micron); } + +#[test] +fn test_wrap_static_fns() { + // GH-1090: https://github.com/rust-lang/rust-bindgen/issues/1090 + unsafe { + let f = extern_bindings::foo(); + assert_eq!(11, f); + + let b = extern_bindings::bar(); + assert_eq!(1, b); + + let t = extern_bindings::takes_ptr(&mut 1); + assert_eq!(2, t); + + extern "C" fn function(x: i32) -> i32 { + x + 1 + } + + let tp = extern_bindings::takes_fn_ptr(Some(function)); + assert_eq!(2, tp); + + let tf = extern_bindings::takes_fn(Some(function)); + assert_eq!(3, tf); + + let ta = extern_bindings::takes_alias(Some(function)); + assert_eq!(4, ta); + + let tq = + extern_bindings::takes_qualified(&(&5 as *const _) as *const _); + assert_eq!(5, tq); + } +} diff --git a/bindgen-tests/Cargo.toml b/bindgen-tests/Cargo.toml index 0678274fd6..6df84e8e9b 100644 --- a/bindgen-tests/Cargo.toml +++ b/bindgen-tests/Cargo.toml @@ -5,7 +5,7 @@ version = "0.1.0" publish = false [dev-dependencies] -bindgen = { path = "../bindgen", features = ["cli"] } +bindgen = { path = "../bindgen", features = ["cli", "experimental"] } diff = "0.1" shlex = "1" clap = { version = "4", features = ["derive"] } diff --git a/bindgen-tests/tests/expectations/tests/generated/README.md b/bindgen-tests/tests/expectations/tests/generated/README.md new file mode 100644 index 0000000000..b4e8cabbf6 --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/generated/README.md @@ -0,0 +1,4 @@ +# Generated C, C++, Header files + +This directory contains files for features where extra files are generated +as a part of the feature. For example, `--wrap-static-fns`. diff --git a/bindgen-tests/tests/expectations/tests/generated/wrap_static_fns.c b/bindgen-tests/tests/expectations/tests/generated/wrap_static_fns.c new file mode 100644 index 0000000000..22b2f67f75 --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/generated/wrap_static_fns.c @@ -0,0 +1,14 @@ +int foo__extern(void) asm("foo__extern"); +int foo__extern() { return foo(); } +int bar__extern(void) asm("bar__extern"); +int bar__extern() { return bar(); } +int takes_ptr__extern(int *arg) asm("takes_ptr__extern"); +int takes_ptr__extern(int *arg) { return takes_ptr(arg); } +int takes_fn_ptr__extern(int (*f) (int)) asm("takes_fn_ptr__extern"); +int takes_fn_ptr__extern(int (*f) (int)) { return takes_fn_ptr(f); } +int takes_fn__extern(int (f) (int)) asm("takes_fn__extern"); +int takes_fn__extern(int (f) (int)) { return takes_fn(f); } +int takes_alias__extern(func f) asm("takes_alias__extern"); +int takes_alias__extern(func f) { return takes_alias(f); } +int takes_qualified__extern(const int *const *arg) asm("takes_qualified__extern"); +int takes_qualified__extern(const int *const *arg) { return takes_qualified(arg); } diff --git a/bindgen-tests/tests/expectations/tests/wrap-static-fns.rs b/bindgen-tests/tests/expectations/tests/wrap-static-fns.rs new file mode 100644 index 0000000000..54ed9fd4dd --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/wrap-static-fns.rs @@ -0,0 +1,52 @@ +#![allow( + dead_code, + non_snake_case, + non_camel_case_types, + non_upper_case_globals +)] + +extern "C" { + #[link_name = "\u{1}foo__extern"] + pub fn foo() -> ::std::os::raw::c_int; +} +extern "C" { + #[link_name = "\u{1}bar__extern"] + pub fn bar() -> ::std::os::raw::c_int; +} +extern "C" { + #[link_name = "\u{1}takes_ptr__extern"] + pub fn takes_ptr(arg: *mut ::std::os::raw::c_int) -> ::std::os::raw::c_int; +} +extern "C" { + #[link_name = "\u{1}takes_fn_ptr__extern"] + pub fn takes_fn_ptr( + f: ::std::option::Option< + unsafe extern "C" fn( + arg1: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int, + >, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[link_name = "\u{1}takes_fn__extern"] + pub fn takes_fn( + f: ::std::option::Option< + unsafe extern "C" fn( + arg1: ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int, + >, + ) -> ::std::os::raw::c_int; +} +pub type func = ::std::option::Option< + unsafe extern "C" fn(arg1: ::std::os::raw::c_int) -> ::std::os::raw::c_int, +>; +extern "C" { + #[link_name = "\u{1}takes_alias__extern"] + pub fn takes_alias(f: func) -> ::std::os::raw::c_int; +} +extern "C" { + #[link_name = "\u{1}takes_qualified__extern"] + pub fn takes_qualified( + arg: *const *const ::std::os::raw::c_int, + ) -> ::std::os::raw::c_int; +} diff --git a/bindgen-tests/tests/headers/wrap-static-fns.h b/bindgen-tests/tests/headers/wrap-static-fns.h new file mode 100644 index 0000000000..8b90c7bc38 --- /dev/null +++ b/bindgen-tests/tests/headers/wrap-static-fns.h @@ -0,0 +1,33 @@ +// bindgen-flags: --experimental --wrap-static-fns + +static inline int foo() { + return 11; +} +static int bar() { + return 1; +} +inline int baz() { + return 2; +} + +static inline int takes_ptr(int* arg) { + return *arg + 1; +} + +static inline int takes_fn_ptr(int (*f)(int)) { + return f(1); +} + +static inline int takes_fn(int (f)(int)) { + return f(2); +} + +typedef int (func)(int); + +static inline int takes_alias(func f) { + return f(3); +} + +static inline int takes_qualified(const int *const *arg) { + return **arg; +} diff --git a/bindgen-tests/tests/tests.rs b/bindgen-tests/tests/tests.rs index 25c073cc84..ed8566c607 100644 --- a/bindgen-tests/tests/tests.rs +++ b/bindgen-tests/tests/tests.rs @@ -713,3 +713,40 @@ fn commandline_multiple_headers() { .header("tests/headers/16-byte-alignment.h"); build_flags_output_helper(&bindings); } + +#[test] +fn test_wrap_static_fns() { + // This test is for testing diffs of the generated C source and header files + // TODO: If another such feature is added, convert this test into a more generic + // test that looks at `tests/headers/generated` directory. + let expect_path = PathBuf::from("tests/expectations/tests/generated") + .join("wrap_static_fns"); + println!("In path is ::: {}", expect_path.display()); + + let generated_path = + PathBuf::from(env::var("OUT_DIR").unwrap()).join("wrap_static_fns"); + println!("Out path is ::: {}", generated_path.display()); + + let _bindings = Builder::default() + .header("tests/headers/wrap-static-fns.h") + .wrap_static_fns(true) + .wrap_static_fns_path(generated_path.display().to_string()) + .generate() + .expect("Failed to generate bindings"); + + let expected_c = fs::read_to_string(expect_path.with_extension("c")) + .expect("Could not read generated wrap_static_fns.c"); + + let actual_c = fs::read_to_string(generated_path.with_extension("c")) + .expect("Could not read actual wrap_static_fns.c"); + + if expected_c != actual_c { + error_diff_mismatch( + &actual_c, + &expected_c, + None, + &expect_path.with_extension("c"), + ) + .unwrap(); + } +} diff --git a/bindgen/Cargo.toml b/bindgen/Cargo.toml index 2292a5efe4..6d4902d452 100644 --- a/bindgen/Cargo.toml +++ b/bindgen/Cargo.toml @@ -48,6 +48,7 @@ runtime = ["clang-sys/runtime"] # Dynamically discover a `rustfmt` binary using the `which` crate which-rustfmt = ["which"] cli = [] +experimental = [] # These features only exist for CI testing -- don't use them if you're not hacking # on bindgen! diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index 6b24ae1bd0..b6fb70eb01 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -4,6 +4,7 @@ mod helpers; mod impl_debug; mod impl_partialeq; mod postprocessing; +mod serialize; pub mod struct_layout; #[cfg(test)] @@ -59,6 +60,29 @@ use std::iter; use std::ops; use std::str::FromStr; +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum CodegenError { + Serialize { msg: String, loc: String }, + Io(String), +} + +impl From for CodegenError { + fn from(err: std::io::Error) -> Self { + Self::Io(err.to_string()) + } +} + +impl std::fmt::Display for CodegenError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CodegenError::Serialize { msg, loc } => { + write!(f, "serialization error at {}: {}", loc, msg) + } + CodegenError::Io(err) => err.fmt(f), + } + } +} + // Name of type defined in constified enum module pub static CONSTIFIED_ENUM_MODULE_REPR_NAME: &str = "Type"; @@ -241,6 +265,8 @@ struct CodegenResult<'a> { /// function name to the number of overloads we have already codegen'd for /// that name. This lets us give each overload a unique suffix. overload_counters: HashMap, + + items_to_serialize: Vec, } impl<'a> CodegenResult<'a> { @@ -258,6 +284,7 @@ impl<'a> CodegenResult<'a> { functions_seen: Default::default(), vars_seen: Default::default(), overload_counters: Default::default(), + items_to_serialize: Default::default(), } } @@ -4000,11 +4027,16 @@ impl CodeGenerator for Function { debug!("::codegen: item = {:?}", item); debug_assert!(item.is_enabled_for_codegen(ctx)); - // We can't currently do anything with Internal functions so just - // avoid generating anything for them. - match self.linkage() { - Linkage::Internal => return None, - Linkage::External => {} + let is_internal = matches!(self.linkage(), Linkage::Internal); + + if is_internal { + if ctx.options().wrap_static_fns { + result.items_to_serialize.push(item.id()); + } else { + // We can't do anything with Internal functions if we are not wrapping them so just + // avoid generating anything for them. + return None; + } } // Pure virtual methods have no actual symbol, so we can't generate @@ -4114,6 +4146,7 @@ impl CodeGenerator for Function { write!(&mut canonical_name, "{}", times_seen).unwrap(); } + let mut has_link_name_attr = false; let link_name = mangled_name.unwrap_or(name); if !is_dynamic_function && !utils::names_will_be_identical_after_mangling( @@ -4123,6 +4156,7 @@ impl CodeGenerator for Function { ) { attributes.push(attributes::link_name(link_name)); + has_link_name_attr = true; } // Unfortunately this can't piggyback on the `attributes` list because @@ -4133,6 +4167,11 @@ impl CodeGenerator for Function { quote! { #[link(wasm_import_module = #name)] } }); + if is_internal && ctx.options().wrap_static_fns && !has_link_name_attr { + let name = canonical_name.clone() + ctx.wrap_static_fns_suffix(); + attributes.push(attributes::link_name(&name)); + } + let ident = ctx.rust_ident(canonical_name); let tokens = quote! { #wasm_link_attribute @@ -4437,7 +4476,8 @@ impl CodeGenerator for ObjCInterface { pub(crate) fn codegen( context: BindgenContext, -) -> (proc_macro2::TokenStream, BindgenOptions, Vec) { +) -> Result<(proc_macro2::TokenStream, BindgenOptions, Vec), CodegenError> +{ context.gen(|context| { let _t = context.timer("codegen"); let counter = Cell::new(0); @@ -4487,21 +4527,73 @@ pub(crate) fn codegen( result.push(dynamic_items_tokens); } - postprocessing::postprocessing(result.items, context.options()) + utils::serialize_items(&result, context)?; + + Ok(postprocessing::postprocessing( + result.items, + context.options(), + )) }) } pub mod utils { - use super::{error, ToRustTyOrOpaque}; + use super::serialize::CSerialize; + use super::{error, CodegenError, CodegenResult, ToRustTyOrOpaque}; use crate::ir::context::BindgenContext; use crate::ir::function::{Abi, ClangAbi, FunctionSig}; use crate::ir::item::{Item, ItemCanonicalPath}; use crate::ir::ty::TypeKind; + use crate::{args_are_cpp, file_is_cpp}; use proc_macro2; use std::borrow::Cow; use std::mem; + use std::path::PathBuf; use std::str::FromStr; + pub(super) fn serialize_items( + result: &CodegenResult, + context: &BindgenContext, + ) -> Result<(), CodegenError> { + if result.items_to_serialize.is_empty() { + return Ok(()); + } + + let path = context + .options() + .wrap_static_fns_path + .as_ref() + .map(PathBuf::from) + .unwrap_or_else(|| { + std::env::temp_dir().join("bindgen").join("extern") + }); + + let dir = path.parent().unwrap(); + + if !dir.exists() { + std::fs::create_dir_all(&dir)?; + } + + let is_cpp = args_are_cpp(&context.options().clang_args) || + context + .options() + .input_headers + .iter() + .any(|h| file_is_cpp(h)); + + let source_path = path.with_extension(if is_cpp { "cpp" } else { "c" }); + + let mut code = Vec::new(); + + for &id in &result.items_to_serialize { + let item = context.resolve_item(id); + item.serialize(context, (), &mut vec![], &mut code)?; + } + + std::fs::write(source_path, code)?; + + Ok(()) + } + pub fn prepend_bitfield_unit_type( ctx: &BindgenContext, result: &mut Vec, diff --git a/bindgen/codegen/serialize.rs b/bindgen/codegen/serialize.rs new file mode 100644 index 0000000000..217098e590 --- /dev/null +++ b/bindgen/codegen/serialize.rs @@ -0,0 +1,356 @@ +use std::io::Write; + +use crate::callbacks::IntKind; + +use crate::ir::comp::CompKind; +use crate::ir::context::{BindgenContext, TypeId}; +use crate::ir::function::{Function, FunctionKind}; +use crate::ir::item::Item; +use crate::ir::item::ItemCanonicalName; +use crate::ir::item_kind::ItemKind; +use crate::ir::ty::{FloatKind, Type, TypeKind}; + +use super::CodegenError; + +fn get_loc(item: &Item) -> String { + item.location() + .map(|x| x.to_string()) + .unwrap_or_else(|| "unknown".to_owned()) +} + +pub(crate) trait CSerialize<'a> { + type Extra; + + fn serialize( + &self, + ctx: &BindgenContext, + extra: Self::Extra, + stack: &mut Vec, + writer: &mut W, + ) -> Result<(), CodegenError>; +} + +impl<'a> CSerialize<'a> for Item { + type Extra = (); + + fn serialize( + &self, + ctx: &BindgenContext, + (): Self::Extra, + stack: &mut Vec, + writer: &mut W, + ) -> Result<(), CodegenError> { + match self.kind() { + ItemKind::Function(func) => { + func.serialize(ctx, self, stack, writer) + } + kind => { + return Err(CodegenError::Serialize { + msg: format!("Cannot serialize item kind {:?}", kind), + loc: get_loc(self), + }); + } + } + } +} + +impl<'a> CSerialize<'a> for Function { + type Extra = &'a Item; + + fn serialize( + &self, + ctx: &BindgenContext, + item: Self::Extra, + stack: &mut Vec, + writer: &mut W, + ) -> Result<(), CodegenError> { + if self.kind() != FunctionKind::Function { + return Err(CodegenError::Serialize { + msg: format!( + "Cannot serialize function kind {:?}", + self.kind(), + ), + loc: get_loc(item), + }); + } + + let signature = match ctx.resolve_type(self.signature()).kind() { + TypeKind::Function(signature) => signature, + _ => unreachable!(), + }; + + let name = self.name(); + + // Function argoments stored as `(name, type_id)` tuples. + let args = { + let mut count = 0; + + signature + .argument_types() + .iter() + .cloned() + .map(|(opt_name, type_id)| { + ( + opt_name.unwrap_or_else(|| { + let name = format!("arg_{}", count); + count += 1; + name + }), + type_id, + ) + }) + .collect::>() + }; + + // The name used for the wrapper self. + let wrap_name = format!("{}{}", name, ctx.wrap_static_fns_suffix()); + // The function's return type + let ret_ty = signature.return_type(); + + // Write `ret_ty wrap_name(args) asm("wrap_name");` + ret_ty.serialize(ctx, (), stack, writer)?; + write!(writer, " {}(", wrap_name)?; + if args.is_empty() { + write!(writer, "void")?; + } else { + serialize_sep( + ", ", + args.iter(), + ctx, + writer, + |(name, type_id), ctx, buf| { + type_id.serialize(ctx, (), &mut vec![name.clone()], buf) + }, + )?; + } + writeln!(writer, ") asm(\"{}\");", wrap_name)?; + + // Write `ret_ty wrap_name(args) { return name(arg_names)' }` + ret_ty.serialize(ctx, (), stack, writer)?; + write!(writer, " {}(", wrap_name)?; + serialize_sep( + ", ", + args.iter(), + ctx, + writer, + |(name, type_id), _, buf| { + type_id.serialize(ctx, (), &mut vec![name.clone()], buf) + }, + )?; + write!(writer, ") {{ return {}(", name)?; + serialize_sep(", ", args.iter(), ctx, writer, |(name, _), _, buf| { + write!(buf, "{}", name).map_err(From::from) + })?; + writeln!(writer, "); }}")?; + + Ok(()) + } +} + +impl<'a> CSerialize<'a> for TypeId { + type Extra = (); + + fn serialize( + &self, + ctx: &BindgenContext, + (): Self::Extra, + stack: &mut Vec, + writer: &mut W, + ) -> Result<(), CodegenError> { + let item = ctx.resolve_item(*self); + item.expect_type().serialize(ctx, item, stack, writer) + } +} + +impl<'a> CSerialize<'a> for Type { + type Extra = &'a Item; + + fn serialize( + &self, + ctx: &BindgenContext, + item: Self::Extra, + stack: &mut Vec, + writer: &mut W, + ) -> Result<(), CodegenError> { + match self.kind() { + TypeKind::Void => { + if self.is_const() { + write!(writer, "const ")?; + } + write!(writer, "void")? + } + TypeKind::NullPtr => { + if self.is_const() { + write!(writer, "const ")?; + } + write!(writer, "nullptr_t")? + } + TypeKind::Int(int_kind) => { + if self.is_const() { + write!(writer, "const ")?; + } + match int_kind { + IntKind::Bool => write!(writer, "bool")?, + IntKind::SChar => write!(writer, "signed char")?, + IntKind::UChar => write!(writer, "unsigned char")?, + IntKind::WChar => write!(writer, "wchar_t")?, + IntKind::Short => write!(writer, "short")?, + IntKind::UShort => write!(writer, "unsigned short")?, + IntKind::Int => write!(writer, "int")?, + IntKind::UInt => write!(writer, "unsigned int")?, + IntKind::Long => write!(writer, "long")?, + IntKind::ULong => write!(writer, "unsigned long")?, + IntKind::LongLong => write!(writer, "long long")?, + IntKind::ULongLong => write!(writer, "unsigned long long")?, + IntKind::Char { .. } => write!(writer, "char")?, + int_kind => { + return Err(CodegenError::Serialize { + msg: format!( + "Cannot serialize integer kind {:?}", + int_kind + ), + loc: get_loc(item), + }) + } + } + } + TypeKind::Float(float_kind) => { + if self.is_const() { + write!(writer, "const ")?; + } + match float_kind { + FloatKind::Float => write!(writer, "float")?, + FloatKind::Double => write!(writer, "double")?, + FloatKind::LongDouble => write!(writer, "long double")?, + FloatKind::Float128 => write!(writer, "__float128")?, + } + } + TypeKind::Complex(float_kind) => { + if self.is_const() { + write!(writer, "const ")?; + } + match float_kind { + FloatKind::Float => write!(writer, "float complex")?, + FloatKind::Double => write!(writer, "double complex")?, + FloatKind::LongDouble => { + write!(writer, "long double complex")? + } + FloatKind::Float128 => write!(writer, "__complex128")?, + } + } + TypeKind::Alias(type_id) => { + if let Some(name) = self.name() { + if self.is_const() { + write!(writer, "const {}", name)?; + } else { + write!(writer, "{}", name)?; + } + } else { + type_id.serialize(ctx, (), stack, writer)?; + } + } + TypeKind::Array(type_id, length) => { + type_id.serialize(ctx, (), stack, writer)?; + write!(writer, " [{}]", length)? + } + TypeKind::Function(signature) => { + if self.is_const() { + stack.push("const ".to_string()); + } + + signature.return_type().serialize( + ctx, + (), + &mut vec![], + writer, + )?; + + write!(writer, " (")?; + while let Some(item) = stack.pop() { + write!(writer, "{}", item)?; + } + write!(writer, ")")?; + + write!(writer, " (")?; + serialize_sep( + ", ", + signature.argument_types().iter(), + ctx, + writer, + |(name, type_id), ctx, buf| { + let mut stack = vec![]; + if let Some(name) = name { + stack.push(name.clone()); + } + type_id.serialize(ctx, (), &mut stack, buf) + }, + )?; + write!(writer, ")")? + } + TypeKind::ResolvedTypeRef(type_id) => { + if self.is_const() { + write!(writer, "const ")?; + } + type_id.serialize(ctx, (), stack, writer)? + } + TypeKind::Pointer(type_id) => { + if self.is_const() { + stack.push("*const ".to_owned()); + } else { + stack.push("*".to_owned()); + } + type_id.serialize(ctx, (), stack, writer)? + } + TypeKind::Comp(comp_info) => { + if self.is_const() { + write!(writer, "const ")?; + } + + let name = item.canonical_name(ctx); + + match comp_info.kind() { + CompKind::Struct => write!(writer, "struct {}", name)?, + CompKind::Union => write!(writer, "union {}", name)?, + }; + } + ty => { + return Err(CodegenError::Serialize { + msg: format!("Cannot serialize type kind {:?}", ty), + loc: get_loc(item), + }) + } + }; + + if !stack.is_empty() { + write!(writer, " ")?; + while let Some(item) = stack.pop() { + write!(writer, "{}", item)?; + } + } + + Ok(()) + } +} + +fn serialize_sep< + W: Write, + F: FnMut(I::Item, &BindgenContext, &mut W) -> Result<(), CodegenError>, + I: Iterator, +>( + sep: &str, + mut iter: I, + ctx: &BindgenContext, + buf: &mut W, + mut f: F, +) -> Result<(), CodegenError> { + if let Some(item) = iter.next() { + f(item, ctx, buf)?; + let sep = sep.as_bytes(); + for item in iter { + buf.write_all(sep)?; + f(item, ctx, buf)?; + } + } + + Ok(()) +} diff --git a/bindgen/ir/context.rs b/bindgen/ir/context.rs index c5df37d7e9..b693a7047e 100644 --- a/bindgen/ir/context.rs +++ b/bindgen/ir/context.rs @@ -20,6 +20,7 @@ use super::template::{TemplateInstantiation, TemplateParameters}; use super::traversal::{self, Edge, ItemTraversal}; use super::ty::{FloatKind, Type, TypeKind}; use crate::clang::{self, Cursor}; +use crate::codegen::CodegenError; use crate::BindgenOptions; use crate::{Entry, HashMap, HashSet}; use cexpr; @@ -1146,9 +1147,9 @@ If you encounter an error missing from this list, please file an issue or a PR!" pub(crate) fn gen( mut self, cb: F, - ) -> (Out, BindgenOptions, Vec) + ) -> Result<(Out, BindgenOptions, Vec), CodegenError> where - F: FnOnce(&Self) -> Out, + F: FnOnce(&Self) -> Result, { self.in_codegen = true; @@ -1183,8 +1184,8 @@ If you encounter an error missing from this list, please file an issue or a PR!" self.compute_cannot_derive_hash(); self.compute_cannot_derive_partialord_partialeq_or_eq(); - let ret = cb(&self); - (ret, self.options, self.warnings) + let ret = cb(&self)?; + Ok((ret, self.options, self.warnings)) } /// When the `testing_only_extra_assertions` feature is enabled, this @@ -2792,6 +2793,13 @@ If you encounter an error missing from this list, please file an issue or a PR!" tokens.into_token_stream() } } + + pub(crate) fn wrap_static_fns_suffix(&self) -> &str { + self.options() + .wrap_static_fns_suffix + .as_deref() + .unwrap_or(crate::DEFAULT_NON_EXTERN_FNS_SUFFIX) + } } /// A builder struct for configuring item resolution options. diff --git a/bindgen/ir/function.rs b/bindgen/ir/function.rs index 8e83d980bc..baa2c36ca4 100644 --- a/bindgen/ir/function.rs +++ b/bindgen/ir/function.rs @@ -664,7 +664,6 @@ impl ClangSubItemParser for Function { }; debug!("Function::parse({:?}, {:?})", cursor, cursor.cur_type()); - let visibility = cursor.visibility(); if visibility != CXVisibility_Default { return Err(ParseError::Continue); @@ -674,25 +673,36 @@ impl ClangSubItemParser for Function { return Err(ParseError::Continue); } + let linkage = cursor.linkage(); + let linkage = match linkage { + CXLinkage_External | CXLinkage_UniqueExternal => Linkage::External, + CXLinkage_Internal => Linkage::Internal, + _ => return Err(ParseError::Continue), + }; + if cursor.is_inlined_function() || cursor .definition() .map_or(false, |x| x.is_inlined_function()) { - if !context.options().generate_inline_functions { + if !context.options().generate_inline_functions && + !context.options().wrap_static_fns + { return Err(ParseError::Continue); } + if cursor.is_deleted_function() { return Err(ParseError::Continue); } - } - let linkage = cursor.linkage(); - let linkage = match linkage { - CXLinkage_External | CXLinkage_UniqueExternal => Linkage::External, - CXLinkage_Internal => Linkage::Internal, - _ => return Err(ParseError::Continue), - }; + // We cannot handle `inline` functions that are not `static`. + if context.options().wrap_static_fns && + cursor.is_inlined_function() && + matches!(linkage, Linkage::External) + { + return Err(ParseError::Continue); + } + } // Grab the signature using Item::from_ty. let sig = Item::from_ty(&cursor.cur_type(), cursor, None, context)?; @@ -727,7 +737,8 @@ impl ClangSubItemParser for Function { let comment = cursor.raw_comment(); let function = - Self::new(name, mangled_name, sig, comment, kind, linkage); + Self::new(name.clone(), mangled_name, sig, comment, kind, linkage); + Ok(ParseResult::New(function, Some(cursor))) } } diff --git a/bindgen/lib.rs b/bindgen/lib.rs index cf1486c2d2..5f995c4e8c 100644 --- a/bindgen/lib.rs +++ b/bindgen/lib.rs @@ -78,6 +78,7 @@ doc_mod!(ir, ir_docs); doc_mod!(parse, parse_docs); doc_mod!(regex_set, regex_set_docs); +use codegen::CodegenError; use ir::comment; pub use crate::codegen::{ @@ -108,6 +109,7 @@ pub(crate) use std::collections::hash_map::Entry; /// Default prefix for the anon fields. pub const DEFAULT_ANON_FIELDS_PREFIX: &str = "__bindgen_anon_"; +const DEFAULT_NON_EXTERN_FNS_SUFFIX: &str = "__extern"; fn file_is_cpp(name_file: &str) -> bool { name_file.ends_with(".hpp") || @@ -657,6 +659,23 @@ impl Builder { for callbacks in &self.options.parse_callbacks { output_vector.extend(callbacks.cli_args()); } + if self.options.wrap_static_fns { + output_vector.push("--wrap-static-fns".into()) + } + + if let Some(ref path) = self.options.wrap_static_fns_path { + output_vector.push("--wrap-static-fns-path".into()); + output_vector.push(path.display().to_string()); + } + + if let Some(ref suffix) = self.options.wrap_static_fns_suffix { + output_vector.push("--wrap-static-fns-suffix".into()); + output_vector.push(suffix.clone()); + } + + if cfg!(feature = "experimental") { + output_vector.push("--experimental".into()); + } // Add clang arguments @@ -1553,33 +1572,25 @@ impl Builder { } /// Generate the Rust bindings using the options built up thus far. - pub fn generate(self) -> Result { - let mut options = self.options.clone(); + pub fn generate(mut self) -> Result { // Add any extra arguments from the environment to the clang command line. - options.clang_args.extend(get_extra_clang_args()); + self.options.clang_args.extend(get_extra_clang_args()); // Transform input headers to arguments on the clang command line. - options.clang_args.extend( - options.input_headers - [..options.input_headers.len().saturating_sub(1)] + self.options.clang_args.extend( + self.options.input_headers + [..self.options.input_headers.len().saturating_sub(1)] .iter() .flat_map(|header| ["-include".into(), header.to_string()]), ); let input_unsaved_files = - std::mem::take(&mut options.input_header_contents) + std::mem::take(&mut self.options.input_header_contents) .into_iter() .map(|(name, contents)| clang::UnsavedFile::new(name, contents)) .collect::>(); - match Bindings::generate(options, input_unsaved_files) { - GenerateResult::Ok(bindings) => Ok(*bindings), - GenerateResult::ShouldRestart { header } => self - .header(header) - .generate_inline_functions(false) - .generate(), - GenerateResult::Err(err) => Err(err), - } + Bindings::generate(self.options, input_unsaved_files) } /// Preprocess and dump the input header files to disk. @@ -1796,6 +1807,32 @@ impl Builder { self.options.wrap_unsafe_ops = doit; self } + + #[cfg(feature = "experimental")] + /// Whether to generate extern wrappers for `static` and `static inline` functions. Defaults to + /// false. + pub fn wrap_static_fns(mut self, doit: bool) -> Self { + self.options.wrap_static_fns = doit; + self + } + + #[cfg(feature = "experimental")] + /// Set the path for the source code file that would be created if any wrapper functions must + /// be generated due to the presence of static functions. + /// + /// Bindgen will automatically add the right extension to the header and source code files. + pub fn wrap_static_fns_path>(mut self, path: T) -> Self { + self.options.wrap_static_fns_path = Some(path.as_ref().to_owned()); + self + } + + #[cfg(feature = "experimental")] + /// Set the suffix added to the extern wrapper functions generated for `static` and `static + /// inline` functions. + pub fn wrap_static_fns_suffix>(mut self, suffix: T) -> Self { + self.options.wrap_static_fns_suffix = Some(suffix.as_ref().to_owned()); + self + } } /// Configuration options for generated bindings. @@ -2136,6 +2173,12 @@ struct BindgenOptions { /// Whether to wrap unsafe operations in unsafe blocks or not. wrap_unsafe_ops: bool, + + wrap_static_fns: bool, + + wrap_static_fns_suffix: Option, + + wrap_static_fns_path: Option, } impl BindgenOptions { @@ -2328,6 +2371,9 @@ impl Default for BindgenOptions { merge_extern_blocks, abi_overrides, wrap_unsafe_ops, + wrap_static_fns, + wrap_static_fns_suffix, + wrap_static_fns_path, } } } @@ -2358,18 +2404,6 @@ fn ensure_libclang_is_loaded() { #[cfg(not(feature = "runtime"))] fn ensure_libclang_is_loaded() {} -#[derive(Debug)] -enum GenerateResult { - Ok(Box), - /// Error variant raised when bindgen requires to run again with a newly generated header - /// input. - #[allow(dead_code)] - ShouldRestart { - header: String, - }, - Err(BindgenError), -} - /// Error type for rust-bindgen. #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[non_exhaustive] @@ -2382,6 +2416,8 @@ pub enum BindgenError { NotExist(PathBuf), /// Clang diagnosed an error. ClangDiagnostic(String), + /// Code generation reported an error. + Codegen(CodegenError), } impl std::fmt::Display for BindgenError { @@ -2399,6 +2435,9 @@ impl std::fmt::Display for BindgenError { BindgenError::ClangDiagnostic(message) => { write!(f, "clang diagnosed error: {}", message) } + BindgenError::Codegen(err) => { + write!(f, "codegen error: {}", err) + } } } } @@ -2463,7 +2502,7 @@ impl Bindings { pub(crate) fn generate( mut options: BindgenOptions, input_unsaved_files: Vec, - ) -> GenerateResult { + ) -> Result { ensure_libclang_is_loaded(); #[cfg(feature = "runtime")] @@ -2584,21 +2623,17 @@ impl Bindings { let path = Path::new(h); if let Ok(md) = std::fs::metadata(path) { if md.is_dir() { - return GenerateResult::Err(BindgenError::FolderAsHeader( - path.into(), - )); + return Err(BindgenError::FolderAsHeader(path.into())); } if !can_read(&md.permissions()) { - return GenerateResult::Err( - BindgenError::InsufficientPermissions(path.into()), - ); + return Err(BindgenError::InsufficientPermissions( + path.into(), + )); } let h = h.clone(); options.clang_args.push(h); } else { - return GenerateResult::Err(BindgenError::NotExist( - path.into(), - )); + return Err(BindgenError::NotExist(path.into())); } } @@ -2626,18 +2661,17 @@ impl Bindings { { let _t = time::Timer::new("parse").with_output(time_phases); - if let Err(err) = parse(&mut context) { - return GenerateResult::Err(err); - } + parse(&mut context)?; } - let (module, options, warnings) = codegen::codegen(context); + let (module, options, warnings) = + codegen::codegen(context).map_err(BindgenError::Codegen)?; - GenerateResult::Ok(Box::new(Bindings { + Ok(Bindings { options, warnings, module, - })) + }) } /// Write these bindings as source text to a file.