diff --git a/CHANGELOG.md b/CHANGELOG.md index aac313e76a..93072cb4e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -170,6 +170,8 @@ instead of `&[u8]`. (Requires Rust 1.59 or higher.) * Added the `--generate-shell-completions` CLI flag to generate completions for different shells. +* The `--wrap-static-fns` option can now wrap `va_list` functions as variadic functions + with the experimental `wrap_as_variadic_fn` callback. ## Changed diff --git a/bindgen-integration/build.rs b/bindgen-integration/build.rs index e03f7f9a79..ce4a3200af 100644 --- a/bindgen-integration/build.rs +++ b/bindgen-integration/build.rs @@ -149,6 +149,15 @@ impl Drop for MacroCallback { } } +#[derive(Debug)] +struct WrappedVaListCallback; + +impl ParseCallbacks for WrappedVaListCallback { + fn wrap_as_variadic_fn(&self, name: &str) -> Option { + Some(name.to_owned() + "_wrapped") + } +} + fn setup_macro_test() { cc::Build::new() .cpp(true) @@ -223,10 +232,12 @@ fn setup_wrap_static_fns_test() { let bindings = Builder::default() .header(input_header_file_path_str) .parse_callbacks(Box::new(CargoCallbacks)) + .parse_callbacks(Box::new(WrappedVaListCallback)) .wrap_static_fns(true) .wrap_static_fns_path( out_path.join("wrap_static_fns").display().to_string(), ) + .clang_arg("-DUSE_VA_HEADER") .generate() .expect("Unable to generate bindings"); @@ -242,6 +253,7 @@ fn setup_wrap_static_fns_test() { .arg("-o") .arg(&obj_path) .arg(out_path.join("wrap_static_fns.c")) + .arg("-DUSE_VA_HEADER") .output() .expect("`clang` command error"); if !clang_output.status.success() { diff --git a/bindgen-integration/src/lib.rs b/bindgen-integration/src/lib.rs index e89351c3b3..04f6a5c9de 100755 --- a/bindgen-integration/src/lib.rs +++ b/bindgen-integration/src/lib.rs @@ -320,5 +320,13 @@ fn test_wrap_static_fns() { let tq = extern_bindings::takes_qualified(&(&5 as *const _) as *const _); assert_eq!(5, tq); + + let wv1 = extern_bindings::wrap_as_variadic_fn1_wrapped(0); + assert_eq!(0, wv1); + + let wv1 = extern_bindings::wrap_as_variadic_fn1_wrapped(2, 5, 3); + assert_eq!(8, wv1); + + extern_bindings::wrap_as_variadic_fn2_wrapped(1, 2); } } diff --git a/bindgen-tests/tests/expectations/tests/generated/wrap_static_fns.c b/bindgen-tests/tests/expectations/tests/generated/wrap_static_fns.c index 8d6e97aad1..cf1106ec76 100644 --- a/bindgen-tests/tests/expectations/tests/generated/wrap_static_fns.c +++ b/bindgen-tests/tests/expectations/tests/generated/wrap_static_fns.c @@ -11,3 +11,21 @@ int takes_alias__extern(func f) { return takes_alias(f); } int takes_qualified__extern(const int *const *arg) { return takes_qualified(arg); } enum foo takes_enum__extern(const enum foo f) { return takes_enum(f); } void nevermore__extern(void) { nevermore(); } +void no_extra_argument__extern(__builtin_va_list va) { no_extra_argument(va); } +int many_va_list__extern(int i, __builtin_va_list va1, __builtin_va_list va2) { return many_va_list(i, va1, va2); } +int wrap_as_variadic_fn1__extern(int i, ...) { + int ret; + va_list ap; + + va_start(ap, i); + ret = wrap_as_variadic_fn1(i, ap); + va_end(ap); + return ret; +} +void wrap_as_variadic_fn2__extern(int i, ...) { + va_list ap; + + va_start(ap, i); + wrap_as_variadic_fn2(i, ap); + va_end(ap); +} diff --git a/bindgen-tests/tests/expectations/tests/wrap-static-fns.rs b/bindgen-tests/tests/expectations/tests/wrap-static-fns.rs index 122f4c9cc8..03f3907ed2 100644 --- a/bindgen-tests/tests/expectations/tests/wrap-static-fns.rs +++ b/bindgen-tests/tests/expectations/tests/wrap-static-fns.rs @@ -50,3 +50,36 @@ extern "C" { #[link_name = "nevermore__extern"] pub fn nevermore(); } +extern "C" { + #[link_name = "no_extra_argument__extern"] + pub fn no_extra_argument(va: *mut __va_list_tag); +} +extern "C" { + #[link_name = "many_va_list__extern"] + pub fn many_va_list( + i: ::std::os::raw::c_int, + va1: *mut __va_list_tag, + va2: *mut __va_list_tag, + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[link_name = "wrap_as_variadic_fn1__extern"] + pub fn wrap_as_variadic_fn1_wrapped( + i: ::std::os::raw::c_int, + ... + ) -> ::std::os::raw::c_int; +} +extern "C" { + #[link_name = "wrap_as_variadic_fn2__extern"] + pub fn wrap_as_variadic_fn2_wrapped(i: ::std::os::raw::c_int, ...); +} +pub type __builtin_va_list = [__va_list_tag; 1usize]; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct __va_list_tag { + pub gp_offset: ::std::os::raw::c_uint, + pub fp_offset: ::std::os::raw::c_uint, + pub overflow_arg_area: *mut ::std::os::raw::c_void, + pub reg_save_area: *mut ::std::os::raw::c_void, + _unused: [u8; 0], +} diff --git a/bindgen-tests/tests/headers/wrap-static-fns.h b/bindgen-tests/tests/headers/wrap-static-fns.h index 85dfeac2af..8dcabe7b30 100644 --- a/bindgen-tests/tests/headers/wrap-static-fns.h +++ b/bindgen-tests/tests/headers/wrap-static-fns.h @@ -1,4 +1,11 @@ // bindgen-flags: --experimental --wrap-static-fns +// bindgen-parse-callbacks: wrap-as-variadic-fn + +// to avoid poluting theexpectation tests we put the stdarg.h behind a conditional +// variable only used in bindgen-integration +#ifdef USE_VA_HEADER +#include +#endif static inline int foo() { return 11; @@ -48,3 +55,28 @@ static inline void nevermore() { static inline int variadic(int x, ...) { return x; } + +static inline void no_extra_argument(__builtin_va_list va) {} + +static inline int many_va_list(int i, __builtin_va_list va1, __builtin_va_list va2) { + return i; +} + +#ifndef USE_VA_HEADER +static inline int wrap_as_variadic_fn1(int i, __builtin_va_list va) { + return i; +} + +static inline void wrap_as_variadic_fn2(int i, __builtin_va_list va) {} +#else +static inline int wrap_as_variadic_fn1(int i, va_list va) { + int res = 0; + + for (int j = 0; j < i; j++) + res += (int) va_arg(va, int); + + return res; +} + +static inline void wrap_as_variadic_fn2(int i, va_list va) {} +#endif diff --git a/bindgen-tests/tests/parse_callbacks/mod.rs b/bindgen-tests/tests/parse_callbacks/mod.rs index fe4c4e5cf2..820432baa8 100644 --- a/bindgen-tests/tests/parse_callbacks/mod.rs +++ b/bindgen-tests/tests/parse_callbacks/mod.rs @@ -113,6 +113,15 @@ impl ParseCallbacks for FieldVisibility { } } +#[derive(Debug)] +pub(super) struct WrapAsVariadicFn; + +impl ParseCallbacks for WrapAsVariadicFn { + fn wrap_as_variadic_fn(&self, name: &str) -> Option { + Some(name.to_owned() + "_wrapped") + } +} + pub fn lookup(cb: &str) -> Box { fn try_strip_prefix<'a>(s: &'a str, prefix: &str) -> Option<&'a str> { if s.starts_with(prefix) { @@ -127,6 +136,7 @@ pub fn lookup(cb: &str) -> Box { "blocklisted-type-implements-trait" => { Box::new(BlocklistedTypeImplementsTrait) } + "wrap-as-variadic-fn" => Box::new(WrapAsVariadicFn), call_back => { if let Some(prefix) = try_strip_prefix(call_back, "remove-function-prefix-") diff --git a/bindgen-tests/tests/tests.rs b/bindgen-tests/tests/tests.rs index f83c1a7a8d..895e1cac2c 100644 --- a/bindgen-tests/tests/tests.rs +++ b/bindgen-tests/tests/tests.rs @@ -618,6 +618,7 @@ fn test_wrap_static_fns() { .header("tests/headers/wrap-static-fns.h") .wrap_static_fns(true) .wrap_static_fns_path(generated_path.display().to_string()) + .parse_callbacks(Box::new(parse_callbacks::WrapAsVariadicFn)) .generate() .expect("Failed to generate bindings"); diff --git a/bindgen/callbacks.rs b/bindgen/callbacks.rs index 96c1b19834..a9d315dca7 100644 --- a/bindgen/callbacks.rs +++ b/bindgen/callbacks.rs @@ -146,6 +146,16 @@ pub trait ParseCallbacks: fmt::Debug { ) -> Option { None } + + /// Process a function name that as exactly one `va_list` argument + /// to be wrapped as a variadic function with the wrapped static function + /// feature. + /// + /// The returned string is new function name. + #[cfg(feature = "experimental")] + fn wrap_as_variadic_fn(&self, _name: &str) -> Option { + None + } } /// Relevant information about a type to which new derive attributes will be added using diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index c9cad86bb6..c381f57cb8 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -221,6 +221,11 @@ impl From for Vec<&'static str> { } } +struct WrapAsVariadic { + new_name: String, + idx_of_va_list_arg: usize, +} + struct CodegenResult<'a> { items: Vec, dynamic_items: DynamicItems, @@ -269,7 +274,9 @@ struct CodegenResult<'a> { /// that name. This lets us give each overload a unique suffix. overload_counters: HashMap, - items_to_serialize: Vec, + /// List of items to serialize. With optionally the argument for the wrap as + /// variadic transformation to be applied. + items_to_serialize: Vec<(ItemId, Option)>, } impl<'a> CodegenResult<'a> { @@ -4246,9 +4253,6 @@ impl CodeGenerator for Function { result.saw_function(seen_symbol_name); } - let args = utils::fnsig_arguments(ctx, signature); - let ret = utils::fnsig_return_ty(ctx, signature); - let mut attributes = vec![]; if ctx.options().rust_features().must_use_function { @@ -4333,10 +4337,6 @@ impl CodeGenerator for Function { abi => abi, }; - if is_internal && ctx.options().wrap_static_fns { - result.items_to_serialize.push(item.id()); - } - // Handle overloaded functions by giving each overload its own unique // suffix. let times_seen = result.overload_number(&canonical_name); @@ -4370,12 +4370,49 @@ impl CodeGenerator for Function { quote! { #[link(wasm_import_module = #name)] } }); - if is_internal && ctx.options().wrap_static_fns && !has_link_name_attr { + let should_wrap = + is_internal && ctx.options().wrap_static_fns && !has_link_name_attr; + + if should_wrap { let name = canonical_name.clone() + ctx.wrap_static_fns_suffix(); attributes.push(attributes::link_name::(&name)); } - let ident = ctx.rust_ident(canonical_name); + let wrap_as_variadic = if should_wrap && !signature.is_variadic() { + utils::wrap_as_variadic_fn(ctx, signature, name) + } else { + None + }; + + let (ident, args) = if let Some(WrapAsVariadic { + idx_of_va_list_arg, + new_name, + }) = &wrap_as_variadic + { + ( + new_name, + utils::fnsig_arguments_iter( + ctx, + // Prune argument at index (idx_of_va_list_arg) + signature.argument_types().iter().enumerate().filter_map( + |(idx, t)| { + if idx == *idx_of_va_list_arg { + None + } else { + Some(t) + } + }, + ), + // and replace it by a `...` (variadic symbol and the end of the signature) + true, + ), + ) + } else { + (&canonical_name, utils::fnsig_arguments(ctx, signature)) + }; + let ret = utils::fnsig_return_ty(ctx, signature); + + let ident = ctx.rust_ident(ident); let tokens = quote! { #wasm_link_attribute extern #abi { @@ -4384,6 +4421,13 @@ impl CodeGenerator for Function { } }; + // Add the item to the serialization list if necessary + if should_wrap { + result + .items_to_serialize + .push((item.id(), wrap_as_variadic)); + } + // If we're doing dynamic binding generation, add to the dynamic items. if is_dynamic_function { let args_identifiers = @@ -4886,9 +4930,9 @@ pub(crate) mod utils { writeln!(code, "// Static wrappers\n")?; - for &id in &result.items_to_serialize { - let item = context.resolve_item(id); - item.serialize(context, (), &mut vec![], &mut code)?; + for (id, wrap_as_variadic) in &result.items_to_serialize { + let item = context.resolve_item(*id); + item.serialize(context, wrap_as_variadic, &mut vec![], &mut code)?; } std::fs::write(source_path, code)?; @@ -4896,6 +4940,62 @@ pub(crate) mod utils { Ok(()) } + pub(super) fn wrap_as_variadic_fn( + ctx: &BindgenContext, + signature: &FunctionSig, + name: &str, + ) -> Option { + // Fast path, exclude because: + // - with 0 args: no va_list possible, so no point searching for one + // - with 1 args: cannot have a `va_list` and another arg (required by va_start) + if signature.argument_types().len() <= 1 { + return None; + } + + let mut it = signature.argument_types().iter().enumerate().filter_map( + |(idx, (_name, mut type_id))| { + // Hand rolled visitor that checks for the presence of `va_list` + loop { + let ty = ctx.resolve_type(type_id); + if Some("__builtin_va_list") == ty.name() { + return Some(idx); + } + match ty.kind() { + TypeKind::Alias(type_id_alias) => { + type_id = *type_id_alias + } + TypeKind::ResolvedTypeRef(type_id_typedef) => { + type_id = *type_id_typedef + } + _ => break, + } + } + None + }, + ); + + // Return THE idx (by checking that there is no idx after) + // This is done since we cannot handle multiple `va_list` + it.next().filter(|_| it.next().is_none()).and_then(|idx| { + // Call the `wrap_as_variadic_fn` callback + #[cfg(feature = "experimental")] + { + ctx.options() + .last_callback(|c| c.wrap_as_variadic_fn(name)) + .map(|new_name| super::WrapAsVariadic { + new_name, + idx_of_va_list_arg: idx, + }) + } + #[cfg(not(feature = "experimental"))] + { + let _ = name; + let _ = idx; + None + } + }) + } + pub(crate) fn prepend_bitfield_unit_type( ctx: &BindgenContext, result: &mut Vec, @@ -5260,16 +5360,18 @@ pub(crate) mod utils { fnsig_return_ty_internal(ctx, sig, /* include_arrow = */ true) } - pub(crate) fn fnsig_arguments( + pub(crate) fn fnsig_arguments_iter< + 'a, + I: Iterator, crate::ir::context::TypeId)>, + >( ctx: &BindgenContext, - sig: &FunctionSig, + args_iter: I, + is_variadic: bool, ) -> Vec { use super::ToPtr; let mut unnamed_arguments = 0; - let mut args = sig - .argument_types() - .iter() + let mut args = args_iter .map(|&(ref name, ty)| { let arg_item = ctx.resolve_item(ty); let arg_ty = arg_item.kind().expect_type(); @@ -5326,13 +5428,24 @@ pub(crate) mod utils { }) .collect::>(); - if sig.is_variadic() { + if is_variadic { args.push(quote! { ... }) } args } + pub(crate) fn fnsig_arguments( + ctx: &BindgenContext, + sig: &FunctionSig, + ) -> Vec { + fnsig_arguments_iter( + ctx, + sig.argument_types().iter(), + sig.is_variadic(), + ) + } + pub(crate) fn fnsig_argument_identifiers( ctx: &BindgenContext, sig: &FunctionSig, diff --git a/bindgen/codegen/serialize.rs b/bindgen/codegen/serialize.rs index 1136234e05..58e0882faf 100644 --- a/bindgen/codegen/serialize.rs +++ b/bindgen/codegen/serialize.rs @@ -10,7 +10,7 @@ use crate::ir::item::ItemCanonicalName; use crate::ir::item_kind::ItemKind; use crate::ir::ty::{FloatKind, Type, TypeKind}; -use super::CodegenError; +use super::{CodegenError, WrapAsVariadic}; fn get_loc(item: &Item) -> String { item.location() @@ -18,7 +18,7 @@ fn get_loc(item: &Item) -> String { .unwrap_or_else(|| "unknown".to_owned()) } -pub(crate) trait CSerialize<'a> { +pub(super) trait CSerialize<'a> { type Extra; fn serialize( @@ -31,18 +31,18 @@ pub(crate) trait CSerialize<'a> { } impl<'a> CSerialize<'a> for Item { - type Extra = (); + type Extra = &'a Option; fn serialize( &self, ctx: &BindgenContext, - (): Self::Extra, + extra: Self::Extra, stack: &mut Vec, writer: &mut W, ) -> Result<(), CodegenError> { match self.kind() { ItemKind::Function(func) => { - func.serialize(ctx, self, stack, writer) + func.serialize(ctx, (self, extra), stack, writer) } kind => Err(CodegenError::Serialize { msg: format!("Cannot serialize item kind {:?}", kind), @@ -53,12 +53,12 @@ impl<'a> CSerialize<'a> for Item { } impl<'a> CSerialize<'a> for Function { - type Extra = &'a Item; + type Extra = (&'a Item, &'a Option); fn serialize( &self, ctx: &BindgenContext, - item: Self::Extra, + (item, wrap_as_variadic): Self::Extra, stack: &mut Vec, writer: &mut W, ) -> Result<(), CodegenError> { @@ -85,19 +85,30 @@ impl<'a> CSerialize<'a> for Function { let args = { let mut count = 0; + let idx_to_prune = wrap_as_variadic.as_ref().map( + |WrapAsVariadic { + idx_of_va_list_arg, .. + }| *idx_of_va_list_arg, + ); + 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, - ) + .enumerate() + .filter_map(|(idx, (opt_name, type_id))| { + if Some(idx) == idx_to_prune { + None + } else { + Some(( + opt_name.unwrap_or_else(|| { + let name = format!("arg_{}", count); + count += 1; + name + }), + type_id, + )) + } }) .collect::>() }; @@ -106,33 +117,82 @@ impl<'a> CSerialize<'a> for Function { let wrap_name = format!("{}{}", name, ctx.wrap_static_fns_suffix()); // The function's return type - let ret_ty = { + let (ret_item, ret_ty) = { let type_id = signature.return_type(); - let item = ctx.resolve_item(type_id); - let ret_ty = item.expect_type(); + let ret_item = ctx.resolve_item(type_id); + let ret_ty = ret_item.expect_type(); // Write `ret_ty`. - ret_ty.serialize(ctx, item, stack, writer)?; + ret_ty.serialize(ctx, ret_item, stack, writer)?; - ret_ty + (ret_item, ret_ty) }; + const INDENT: &str = " "; + // Write `wrap_name(args`. write!(writer, " {}(", wrap_name)?; serialize_args(&args, ctx, writer)?; - // Write `) { name(` if the function returns void and `) { return name(` if it does not. - if ret_ty.is_void() { - write!(writer, ") {{ {}(", name)?; + if wrap_as_variadic.is_none() { + // Write `) { name(` if the function returns void and `) { return name(` if it does not. + if ret_ty.is_void() { + write!(writer, ") {{ {}(", name)?; + } else { + write!(writer, ") {{ return {}(", name)?; + } } else { - write!(writer, ") {{ return {}(", name)?; + // Write `, ...) {` + writeln!(writer, ", ...) {{")?; + + // Declare the return type `RET_TY ret;` if their is a need to do so + if !ret_ty.is_void() { + write!(writer, "{INDENT}")?; + ret_ty.serialize(ctx, ret_item, stack, writer)?; + writeln!(writer, " ret;")?; + } + + // Setup va_list + writeln!(writer, "{INDENT}va_list ap;\n")?; + writeln!( + writer, + "{INDENT}va_start(ap, {});", + args.last().unwrap().0 + )?; + + write!(writer, "{INDENT}")?; + // Write `ret = name(` or `name(` depending if the function returns something + if !ret_ty.is_void() { + write!(writer, "ret = ")?; + } + write!(writer, "{}(", name)?; } - // Write `arg_names); }`. - serialize_sep(", ", args.iter(), ctx, writer, |(name, _), _, buf| { + // Get the arguments names and insert at the right place if necessary `ap` + let mut args: Vec<_> = args.into_iter().map(|(name, _)| name).collect(); + if let Some(WrapAsVariadic { + idx_of_va_list_arg, .. + }) = wrap_as_variadic + { + args.insert(*idx_of_va_list_arg, "ap".to_owned()); + } + + // Write `arg_names);`. + serialize_sep(", ", args.iter(), ctx, writer, |name, _, buf| { write!(buf, "{}", name).map_err(From::from) })?; - writeln!(writer, "); }}")?; + #[rustfmt::skip] + write!(writer, ");{}", if wrap_as_variadic.is_none() { " " } else { "\n" })?; + + if wrap_as_variadic.is_some() { + // End va_list and return the result if their is one + writeln!(writer, "{INDENT}va_end(ap);")?; + if !ret_ty.is_void() { + writeln!(writer, "{INDENT}return ret;")?; + } + } + + writeln!(writer, "}}")?; Ok(()) } diff --git a/ci/no-includes.sh b/ci/no-includes.sh index 97966cb090..32c20059e0 100755 --- a/ci/no-includes.sh +++ b/ci/no-includes.sh @@ -1,13 +1,17 @@ #!/usr/bin/env bash # Don't allow any system include directives in tests. +# +# We make an exception for stdarg.h which is used for +# the wrapped va_list feature. stdarg.h is available since C89 +# therefor not having this header is a sign of a bigger issue. set -eu cd "$(dirname "$0")/.." echo "Checking for #include directives of system headers..." -grep -rn '#include\s*<.*>' bindgen-tests/tests/headers || { +grep -rn '#include\s*<(?!stdarg).*>' bindgen-tests/tests/headers || { echo "Found none; OK!" exit 0 }