Repository: rust-lang/rust-bindgen
Pull request #2502 (2023-04-12 15:11:27)

Wrap `va_list` function as variadic function
~~Opening a draft before doing more to get opinion on it.~~

This PR add a way to wrap `va_list` function as variadic function. See below for an example.

Input:
```c
#include <stdarg.h>

static inline int vfoo(int flags, va_list va) {
    // ...
    return flags;
}
```

Output:
```c
int vfoo__extern(int flags, ...) {
int ret;
va_list ap;

va_start(ap, flags);
ret = vfoo(flags, ap);
va_end(ap);
return ret;
}
```
```rust
extern "C" {
    #[link_name = "vfoo__extern"]
    pub fn vfoo(flags: ::std::os::raw::c_int, ...) -> ::std::os::raw::c_int;
}
```


Related to https://github.com/rust-lang/rust-bindgen/issues/2498

cc @pvdrz

bors-servo: :umbrella: The latest upstream changes (presumably 4f9fcbfb596c53dcce3f32194f6218220ae2d6a8) made this pull request unmergeable. Please resolve the merge conflicts.
Urgau: @pvdrz Could I get a pre-review of this?
pvdrz: I think it would be best if this is not done for every single function that takes `valist` as a last argument but instead let it be configurable using `ParseCallbacks`:

```rust
trait ParseCallbacks {
    ...
    fn wrap_as_variadic_fn(&mut self, name: &str) -> Option<String> {
        None
    }
}
```

That would mean that the user can decide which functions should be wrapped and would be their responsibility to check the signature instead of us. 

For example, if `callbacks.wrap_as_variadic_fn("vfoo")` returns `Some("foo")`, we emit the following code:
```c
int foo(int flags, ...) {
int ret;
va_list ap;

va_start(ap, flags);
ret = vfoo(flags, ap);
va_end(ap);
return ret;
}
```

```rust
extern "C" {
    pub fn foo(flags: ::std::os::raw::c_int, ...) -> ::std::os::raw::c_int;
}
```

What do you think?

Urgau: > I think it would be best if this is not done for every single function that takes `valist` as a last argument but instead let it be configurable using `ParseCallbacks`:
> 
> [...]
> 
> What do you think?

Sure, this seems like a good idea. I will do the necessary work and make the PR ready for review.

Btw, my logic works with any function that has 1 (and only 1) `va_list` no matter his position. (I went trough great length to ensure that)
Urgau: As discussed I've added `ParseCallbacks::wrap_as_variadic_fn`. I also did some cleanup and improvement to the added code. Should be ready to review.

Btw, I ended up only changing the name of the Rust function because changing the other one felt unnecessary complicated and also it's the only one that matters (the other one are implementation details).
Urgau: @pvdrz Let me know if there is something I can do to help with the review ;-)
pvdrz: > @pvdrz Let me know if there is something I can do to help with the review ;-)

Hey :) I have this on my todo list but I only will be able to review it later this week. Thanks for the patience 
Urgau: @pvdrz I've updated as requested the PR (bindgen-integration + changelog).
pvdrz: Hey! the test suite broke due to an unrelated issue that has already been fixed in the main branch, could you rebase your PR against it so we can be sure the tests are passing?
tshepang: can you rebase against current `main`... I expect that to result in passing tests
Urgau: I've rebased, the tests suite now passes (had to add an exception for `stdarg.h` which I don't think is an issue, it's avalaible since C89 and even a little before) ;-)
bors-servo: :umbrella: The latest upstream changes (presumably 4faa3662c6999efc7b4d91e81e166e52f3e717bf) made this pull request unmergeable. Please resolve the merge conflicts.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index aac313e7..93072cb4 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 e03f7f9a..ce4a3200 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<String> {
+        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 e89351c3..04f6a5c9 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/callbacks.rs b/bindgen/callbacks.rs
index 96c1b198..a9d315dc 100644
--- a/bindgen/callbacks.rs
+++ b/bindgen/callbacks.rs
@@ -146,6 +146,16 @@ pub trait ParseCallbacks: fmt::Debug {
     ) -> Option<crate::FieldVisibilityKind> {
         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<String> {
+        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 c9cad86b..c381f57c 100644
--- a/bindgen/codegen/mod.rs
+++ b/bindgen/codegen/mod.rs
@@ -221,6 +221,11 @@ impl From<DerivableTraits> for Vec<&'static str> {
     }
 }
 
+struct WrapAsVariadic {
+    new_name: String,
+    idx_of_va_list_arg: usize,
+}
+
 struct CodegenResult<'a> {
     items: Vec<proc_macro2::TokenStream>,
     dynamic_items: DynamicItems,
@@ -269,7 +274,9 @@ struct CodegenResult<'a> {
     /// that name. This lets us give each overload a unique suffix.
     overload_counters: HashMap<String, u32>,
 
-    items_to_serialize: Vec<ItemId>,
+    /// List of items to serialize. With optionally the argument for the wrap as
+    /// variadic transformation to be applied.
+    items_to_serialize: Vec<(ItemId, Option<WrapAsVariadic>)>,
 }
 
 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::<true>(&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<super::WrapAsVariadic> {
+        // 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<proc_macro2::TokenStream>,
@@ -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<Item = &'a (Option<String>, crate::ir::context::TypeId)>,
+    >(
         ctx: &BindgenContext,
-        sig: &FunctionSig,
+        args_iter: I,
+        is_variadic: bool,
     ) -> Vec<proc_macro2::TokenStream> {
         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::<Vec<_>>();
 
-        if sig.is_variadic() {
+        if is_variadic {
             args.push(quote! { ... })
         }
 
         args
     }
 
+    pub(crate) fn fnsig_arguments(
+        ctx: &BindgenContext,
+        sig: &FunctionSig,
+    ) -> Vec<proc_macro2::TokenStream> {
+        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 1136234e..58e0882f 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<W: Write>(
@@ -31,18 +31,18 @@ pub(crate) trait CSerialize<'a> {
 }
 
 impl<'a> CSerialize<'a> for Item {
-    type Extra = ();
+    type Extra = &'a Option<WrapAsVariadic>;
 
     fn serialize<W: Write>(
         &self,
         ctx: &BindgenContext,
-        (): Self::Extra,
+        extra: Self::Extra,
         stack: &mut Vec<String>,
         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<WrapAsVariadic>);
 
     fn serialize<W: Write>(
         &self,
         ctx: &BindgenContext,
-        item: Self::Extra,
+        (item, wrap_as_variadic): Self::Extra,
         stack: &mut Vec<String>,
         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::<Vec<_>>()
         };
@@ -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 97966cb0..32c20059 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
 }


Test patch:
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 8d6e97aa..cf1106ec 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 122f4c9c..03f3907e 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 85dfeac2..8dcabe7b 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 <stdarg.h>
+#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 fe4c4e5c..820432ba 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<String> {
+        Some(name.to_owned() + "_wrapped")
+    }
+}
+
 pub fn lookup(cb: &str) -> Box<dyn ParseCallbacks> {
     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<dyn ParseCallbacks> {
         "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 f83c1a7a..895e1cac 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");