Repository: rust-lang/rust-bindgen
Pull request #3139 (2025-02-19 15:12:46)

Options to generate uncallable C++ functions.
Downstream code generators may need to know about the existence of certain C++ functions even if those functions can't be called. This is counterintuitive but:

* A type can't even be allocated if it contains pure virtual functions or if its constructor is private.
* A type may not be relocatable if it contains a deleted move constructor.

This PR provides command line options to reveal the existence of these functions. Subsequent PRs will announce their special status using the ParseCallbacks mechanism.

Part of https://github.com/google/autocxx/issues/124.


Patch:
diff --git a/.github/workflows/bindgen.yml b/.github/workflows/bindgen.yml
index df17f2c5..d0112e36 100644
--- a/.github/workflows/bindgen.yml
+++ b/.github/workflows/bindgen.yml
@@ -171,7 +171,7 @@ jobs:
           BINDGEN_FEATURE_RUNTIME: ${{matrix.feature_runtime}}
           BINDGEN_FEATURE_EXTRA_ASSERTS: ${{matrix.feature_extra_asserts}}
           BINDGEN_NO_DEFAULT_FEATURES: ${{matrix.no_default_features}}
-          BINDGEN_RUST_FOR_LINUX_TEST: ${{matrix.os == 'ubuntu-latest' && matrix.llvm_version == '16.0' && matrix.feature_extra_asserts == 0 && 1 || 0}}
+          BINDGEN_RUST_FOR_LINUX_TEST: 0
         run: ./ci/test.sh
 
   check-cfg:
diff --git a/bindgen/codegen/helpers.rs b/bindgen/codegen/helpers.rs
index 70f01259..82172f34 100644
--- a/bindgen/codegen/helpers.rs
+++ b/bindgen/codegen/helpers.rs
@@ -191,7 +191,7 @@ pub(crate) mod ast_ty {
             // argument is used. bindgen_cchar16_t is not a real type;
             // but this allows downstream postprocessors to distinguish
             // this case and do something special for C++ bindings
-            // containing char16_t.
+            // containing the C++ type char16_t.
             IntKind::Char16 => syn::parse_quote! { bindgen_cchar16_t },
             IntKind::SChar => raw_type(ctx, "c_schar"),
             IntKind::UChar => raw_type(ctx, "c_uchar"),
diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs
index f5518e43..8c623ff9 100644
--- a/bindgen/codegen/mod.rs
+++ b/bindgen/codegen/mod.rs
@@ -4564,14 +4564,20 @@ impl CodeGenerator for Function {
             }
         }
 
-        // Pure virtual methods have no actual symbol, so we can't generate
-        // something meaningful for them.
-        let is_dynamic_function = match self.kind() {
-            FunctionKind::Method(ref method_kind)
-                if method_kind.is_pure_virtual() =>
-            {
-                return None;
+        let is_pure_virtual = match self.kind() {
+            FunctionKind::Method(ref method_kind) => {
+                method_kind.is_pure_virtual()
             }
+            _ => false,
+        };
+        if is_pure_virtual && !ctx.options().generate_pure_virtual_functions {
+            // Pure virtual methods have no actual symbol, so we can't generate
+            // something meaningful for them. Downstream code postprocessors
+            // might want to find out about them.
+            return None;
+        }
+
+        let is_dynamic_function = match self.kind() {
             FunctionKind::Function => {
                 ctx.options().dynamic_library_name.is_some()
             }
diff --git a/bindgen/ir/function.rs b/bindgen/ir/function.rs
index 83b748a5..bee01566 100644
--- a/bindgen/ir/function.rs
+++ b/bindgen/ir/function.rs
@@ -533,7 +533,10 @@ impl FunctionSig {
             let is_const = is_method && cursor.method_is_const();
             let is_virtual = is_method && cursor.method_is_virtual();
             let is_static = is_method && cursor.method_is_static();
-            if !is_static && !is_virtual {
+            if !is_static &&
+                (!is_virtual ||
+                    ctx.options().use_specific_virtual_function_receiver)
+            {
                 let parent = cursor.semantic_parent();
                 let class = Item::parse(parent, None, ctx)
                     .expect("Expected to parse the class");
@@ -731,8 +734,9 @@ impl ClangSubItemParser for Function {
         if visibility != CXVisibility_Default {
             return Err(ParseError::Continue);
         }
-
-        if cursor.access_specifier() == CX_CXXPrivate {
+        if cursor.access_specifier() == CX_CXXPrivate &&
+            !context.options().generate_private_functions
+        {
             return Err(ParseError::Continue);
         }
 
@@ -752,7 +756,9 @@ impl ClangSubItemParser for Function {
                 return Err(ParseError::Continue);
             }
 
-            if cursor.is_deleted_function() {
+            if cursor.is_deleted_function() &&
+                !context.options().generate_deleted_functions
+            {
                 return Err(ParseError::Continue);
             }
 
diff --git a/bindgen/ir/int.rs b/bindgen/ir/int.rs
index 4caa6b2d..ed18a999 100644
--- a/bindgen/ir/int.rs
+++ b/bindgen/ir/int.rs
@@ -57,7 +57,7 @@ pub enum IntKind {
     /// A 16-bit integer, used only for enum size representation.
     U16,
 
-    /// Either a `char16_t` or a `wchar_t`.
+    /// The C++ type `char16_t`, which is its own type (unlike in C).
     Char16,
 
     /// A 32-bit signed integer.
diff --git a/bindgen/log_stubs.rs b/bindgen/log_stubs.rs
index 83159831..51d2f81f 100644
--- a/bindgen/log_stubs.rs
+++ b/bindgen/log_stubs.rs
@@ -1,5 +1,6 @@
 #![allow(unused)]
 
+#[clippy::format_args]
 macro_rules! log {
     (target: $target:expr, $lvl:expr, $($arg:tt)+) => {{
         let _ = $target;
@@ -10,22 +11,27 @@ macro_rules! log {
         let _ = format_args!($($arg)+);
     }};
 }
+#[clippy::format_args]
 macro_rules! error {
     (target: $target:expr, $($arg:tt)+) => { log!(target: $target, "", $($arg)+) };
     ($($arg:tt)+) => { log!("", $($arg)+) };
 }
+#[clippy::format_args]
 macro_rules! warn {
     (target: $target:expr, $($arg:tt)*) => { log!(target: $target, "", $($arg)*) };
     ($($arg:tt)*) => { log!("", $($arg)*) };
 }
+#[clippy::format_args]
 macro_rules! info {
     (target: $target:expr, $($arg:tt)+) => { log!(target: $target, "", $($arg)+) };
     ($($arg:tt)+) => { log!("", $($arg)+) };
 }
+#[clippy::format_args]
 macro_rules! debug {
     (target: $target:expr, $($arg:tt)+) => { log!(target: $target, "", $($arg)+) };
     ($($arg:tt)+) => { log!("", $($arg)+) };
 }
+#[clippy::format_args]
 macro_rules! trace {
     (target: $target:expr, $($arg:tt)+) => { log!(target: $target, "", $($arg)+) };
     ($($arg:tt)+) => { log!("", $($arg)+) };
diff --git a/bindgen/options/cli.rs b/bindgen/options/cli.rs
index 1efddb02..170df4f7 100644
--- a/bindgen/options/cli.rs
+++ b/bindgen/options/cli.rs
@@ -441,6 +441,9 @@ struct BindgenCommand {
     /// Always output explicit padding fields.
     #[arg(long)]
     explicit_padding: bool,
+    /// Always be specific about the 'receiver' of a virtual function.
+    #[arg(long)]
+    use_specific_virtual_function_receiver: bool,
     /// Use distinct char16_t
     #[arg(long)]
     use_distinct_char16_t: bool,
@@ -507,6 +510,18 @@ struct BindgenCommand {
     /// bitfields. This flag is ignored if the `--respect-cxx-access-specs` flag is used.
     #[arg(long, value_name = "VISIBILITY")]
     default_visibility: Option<FieldVisibilityKind>,
+    /// Whether to generate C++ functions marked with "=delete" even though they
+    /// can't be called.
+    #[arg(long)]
+    generate_deleted_functions: bool,
+    /// Whether to generate C++ "pure virtual" functions even though they can't
+    /// be called.
+    #[arg(long)]
+    generate_pure_virtual_functions: bool,
+    /// Whether to generate C++ private functions even though they can't
+    /// be called.
+    #[arg(long)]
+    generate_private_functions: bool,
     /// Whether to emit diagnostics or not.
     #[cfg(feature = "experimental")]
     #[arg(long, requires = "experimental")]
@@ -632,6 +647,7 @@ where
         translate_enum_integer_types,
         c_naming,
         explicit_padding,
+        use_specific_virtual_function_receiver,
         use_distinct_char16_t,
         vtable_generation,
         sort_semantically,
@@ -653,6 +669,9 @@ where
         wrap_static_fns_path,
         wrap_static_fns_suffix,
         default_visibility,
+        generate_deleted_functions,
+        generate_pure_virtual_functions,
+        generate_private_functions,
         #[cfg(feature = "experimental")]
         emit_diagnostics,
         generate_shell_completions,
@@ -930,6 +949,7 @@ where
             translate_enum_integer_types,
             c_naming,
             explicit_padding,
+            use_specific_virtual_function_receiver,
             use_distinct_char16_t,
             vtable_generation,
             sort_semantically,
@@ -943,6 +963,9 @@ where
             wrap_static_fns_path,
             wrap_static_fns_suffix,
             default_visibility,
+            generate_deleted_functions,
+            generate_pure_virtual_functions,
+            generate_private_functions,
         }
     );
 
diff --git a/bindgen/options/mod.rs b/bindgen/options/mod.rs
index 1a675401..7824f5d1 100644
--- a/bindgen/options/mod.rs
+++ b/bindgen/options/mod.rs
@@ -153,10 +153,28 @@ macro_rules! options {
 }
 
 options! {
-    /// Whether we should distinguish between 'char16_t' and 'u16'.
-    /// As standard, bindgen represents `char16_t` as `u16`.
+    /// Whether to specify the type of a virtual function receiver
+    use_specific_virtual_function_receiver: bool {
+        methods: {
+            /// Normally, virtual functions have void* as their 'this' type.
+            /// If this flag is enabled, override that behavior to indicate a
+            /// pointer of the specific type.
+            /// Disabled by default.
+            pub fn use_specific_virtual_function_receiver(mut self, doit: bool) -> Builder {
+                self.options.use_specific_virtual_function_receiver = doit;
+                self
+            }
+        },
+        as_args: "--use-specific-virtual-function-receiver",
+    },
+
+    /// Whether we should distinguish between C++'s 'char16_t' and 'u16'.
+    /// The C++ type `char16_t` is its own special type; it's not a typedef
+    /// of some other integer (this differs from C).
+    /// As standard, bindgen represents C++ `char16_t` as `u16`.
     /// Rust does not have a `std::os::raw::c_char16_t` type, and thus
-    /// we can't use a built-in Rust type in the generated bindings.
+    /// we can't use a built-in Rust type in the generated bindings (and
+    /// nor would it be appropriate as it's a C++-specific type.)
     /// But for some uses of bindgen, especially when downstream
     /// post-processing occurs, it's important to distinguish `char16_t`
     /// from normal `uint16_t`. When this option is enabled, bindgen
@@ -2193,4 +2211,58 @@ options! {
         },
         as_args: "--clang-macro-fallback-build-dir",
     }
+    /// Whether to always report C++ "deleted" functions.
+    generate_deleted_functions: bool {
+        methods: {
+            /// Set whether to generate C++ functions even marked "=deleted"
+            ///
+            /// Although not useful to call these functions, downstream code
+            /// generators may need to know whether they've been deleted in
+            /// order to determine the relocatability of a C++ type
+            /// (specifically by virtue of which constructors exist.)
+            pub fn generate_deleted_functions(mut self, doit: bool) -> Self {
+                self.options.generate_deleted_functions = doit;
+                self
+            }
+
+        },
+        as_args: "--generate-deleted-functions",
+    },
+    /// Whether to always report C++ "pure virtual" functions.
+    generate_pure_virtual_functions: bool {
+        methods: {
+            /// Set whether to generate C++ functions that are pure virtual.
+            ///
+            /// These functions can't be called, so the only reason
+            /// to generate them is if downstream postprocessors
+            /// need to know of their existence. This is necessary,
+            /// for instance, to determine whether a type itself is
+            /// pure virtual and thus can't be allocated.
+            /// Downstream code generators may choose to make code to
+            /// allow types to be allocated but need to avoid doing so
+            /// if the type contains pure virtual functions.
+            pub fn generate_pure_virtual_functions(mut self, doit: bool) -> Self {
+                self.options.generate_pure_virtual_functions = doit;
+                self
+            }
+
+        },
+        as_args: "--generate-pure-virtual-functions",
+    },
+    /// Whether to always report C++ "private" functions.
+    generate_private_functions: bool {
+        methods: {
+            /// Set whether to generate C++ functions that are private.
+            ///
+            /// These functions can't be called, so the only reason
+            /// to generate them is if downstream postprocessors
+            /// need to know of their existence.
+            pub fn generate_private_functions(mut self, doit: bool) -> Self {
+                self.options.generate_private_functions = doit;
+                self
+            }
+
+        },
+        as_args: "--generate-private-functions",
+    },
 }


Test patch:
diff --git a/bindgen-tests/tests/expectations/tests/specific_receiver.rs b/bindgen-tests/tests/expectations/tests/specific_receiver.rs
new file mode 100644
index 00000000..ec001b12
--- /dev/null
+++ b/bindgen-tests/tests/expectations/tests/specific_receiver.rs
@@ -0,0 +1,28 @@
+#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]
+#[repr(C)]
+pub struct Fish__bindgen_vtable {
+    pub Fish_swim: unsafe extern "C" fn(this: *mut Fish),
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct Fish {
+    pub vtable_: *const Fish__bindgen_vtable,
+}
+#[allow(clippy::unnecessary_operation, clippy::identity_op)]
+const _: () = {
+    ["Size of Fish"][::std::mem::size_of::<Fish>() - 8usize];
+    ["Alignment of Fish"][::std::mem::align_of::<Fish>() - 8usize];
+};
+impl Default for Fish {
+    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()
+        }
+    }
+}
+unsafe extern "C" {
+    #[link_name = "\u{1}_ZN4Fish4swimEv"]
+    pub fn Fish_swim(this: *mut Fish);
+}
diff --git a/bindgen-tests/tests/expectations/tests/uncallable_functions.rs b/bindgen-tests/tests/expectations/tests/uncallable_functions.rs
new file mode 100644
index 00000000..fddb5d41
--- /dev/null
+++ b/bindgen-tests/tests/expectations/tests/uncallable_functions.rs
@@ -0,0 +1,46 @@
+#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]
+#[repr(C)]
+pub struct Test__bindgen_vtable {
+    pub Test_a: unsafe extern "C" fn(this: *mut Test),
+}
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+pub struct Test {
+    pub vtable_: *const Test__bindgen_vtable,
+}
+#[allow(clippy::unnecessary_operation, clippy::identity_op)]
+const _: () = {
+    ["Size of Test"][::std::mem::size_of::<Test>() - 8usize];
+    ["Alignment of Test"][::std::mem::align_of::<Test>() - 8usize];
+};
+unsafe extern "C" {
+    #[link_name = "\u{1}_ZN4Test1bEv"]
+    pub fn Test_b(this: *mut Test);
+}
+unsafe extern "C" {
+    #[link_name = "\u{1}_ZN4Test1cEv"]
+    pub fn Test_c(this: *mut Test);
+}
+impl Default for Test {
+    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()
+        }
+    }
+}
+impl Test {
+    #[inline]
+    pub unsafe fn b(&mut self) {
+        Test_b(self)
+    }
+    #[inline]
+    pub unsafe fn c(&mut self) {
+        Test_c(self)
+    }
+}
+unsafe extern "C" {
+    #[link_name = "\u{1}_ZN4Test1aEv"]
+    pub fn Test_a(this: *mut ::std::os::raw::c_void);
+}
diff --git a/bindgen-tests/tests/headers/specific_receiver.hpp b/bindgen-tests/tests/headers/specific_receiver.hpp
new file mode 100644
index 00000000..a521f9f0
--- /dev/null
+++ b/bindgen-tests/tests/headers/specific_receiver.hpp
@@ -0,0 +1,7 @@
+// bindgen-flags: --use-specific-virtual-function-receiver --generate-inline-functions -- -x c++ -std=c++14
+
+class Fish {
+public:
+    virtual void swim() {
+    }
+};
diff --git a/bindgen-tests/tests/headers/uncallable_functions.hpp b/bindgen-tests/tests/headers/uncallable_functions.hpp
new file mode 100644
index 00000000..9187470c
--- /dev/null
+++ b/bindgen-tests/tests/headers/uncallable_functions.hpp
@@ -0,0 +1,9 @@
+// bindgen-flags: --generate-deleted-functions --generate-private-functions --generate-pure-virtual-functions --generate-inline-functions -- -x c++ -std=c++14
+
+class Test {
+public:
+    virtual void a() = 0;
+    void b() = delete;
+private:
+    void c() {}
+};