From 89385ad8fa36d0897daf98b99818c241460120c1 Mon Sep 17 00:00:00 2001 From: Nicolas Pauss Date: Mon, 7 Jul 2025 12:26:36 +0200 Subject: [PATCH] Add ParseCallbacks::allow_item() and ParseCallbacks::block_item(). Like `allowlist_item` and `blocklist_item` options, add new methods to `ParseCallbacks`, `ParseCallbacks::allow_item()` and `ParseCallbacks::block_item()`, to be able to allow and block items from being generated. `allowlist_*` and `blocklist_*` options work with regexes and are inserted in a RegexSet. There are two issues with this approach: 1. In some cases we want to have more flexibility than just using regexes. If we want to have dynamic conditions to allow or block items, using regexes can be limited. 2. RegexSet scales linearly with the number of elements inserted. This means that if we have a huge number of items that we want to allow or block, regexes and RegexSet are not necessarily the most appropriate data structures. Using new methods in `ParseCallbacks` solves these two issues. We can manually decide the appropriate rules and data structure to match the items. `ParseCallbacks::allow_item()` and `ParseCallbacks::block_item()` are always called after the `allowlist_*` and `blocklist_*` options, and allow or do not block the items by default respectively. --- .../expectations/tests/allow-item-callback.rs | 48 +++++++++++++++++ .../expectations/tests/block-item-callback.rs | 52 +++++++++++++++++++ .../tests/headers/allow-item-callback.h | 33 ++++++++++++ .../tests/headers/block-item-callback.h | 33 ++++++++++++ bindgen-tests/tests/parse_callbacks/mod.rs | 20 +++++++ bindgen/callbacks.rs | 18 +++++++ bindgen/codegen/mod.rs | 20 +++++-- bindgen/ir/context.rs | 9 ++++ bindgen/ir/item.rs | 27 ++++++---- bindgen/lib.rs | 14 +++++ 10 files changed, 262 insertions(+), 12 deletions(-) create mode 100644 bindgen-tests/tests/expectations/tests/allow-item-callback.rs create mode 100644 bindgen-tests/tests/expectations/tests/block-item-callback.rs create mode 100644 bindgen-tests/tests/headers/allow-item-callback.h create mode 100644 bindgen-tests/tests/headers/block-item-callback.h diff --git a/bindgen-tests/tests/expectations/tests/allow-item-callback.rs b/bindgen-tests/tests/expectations/tests/allow-item-callback.rs new file mode 100644 index 0000000000..c5656084e6 --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/allow-item-callback.rs @@ -0,0 +1,48 @@ +#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct allowed_my_struct { + pub a: ::std::os::raw::c_int, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of allowed_my_struct"][::std::mem::size_of::() - 4usize]; + [ + "Alignment of allowed_my_struct", + ][::std::mem::align_of::() - 4usize]; + [ + "Offset of field: allowed_my_struct::a", + ][::std::mem::offset_of!(allowed_my_struct, a) - 0usize]; +}; +#[repr(C)] +#[derive(Copy, Clone)] +pub union allowed_my_union { + pub a: ::std::os::raw::c_int, + pub b: f64, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of allowed_my_union"][::std::mem::size_of::() - 8usize]; + [ + "Alignment of allowed_my_union", + ][::std::mem::align_of::() - 8usize]; + [ + "Offset of field: allowed_my_union::a", + ][::std::mem::offset_of!(allowed_my_union, a) - 0usize]; + [ + "Offset of field: allowed_my_union::b", + ][::std::mem::offset_of!(allowed_my_union, b) - 0usize]; +}; +impl Default for allowed_my_union { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +pub const allowed_my_enum_ALLOWED_MY_ENUM_A: allowed_my_enum = 0; +pub const allowed_my_enum_ALLOWED_MY_ENUM_B: allowed_my_enum = 1; +pub type allowed_my_enum = ::std::os::raw::c_uint; +pub const allowed_my_const: ::std::os::raw::c_int = 10; diff --git a/bindgen-tests/tests/expectations/tests/block-item-callback.rs b/bindgen-tests/tests/expectations/tests/block-item-callback.rs new file mode 100644 index 0000000000..fbf2a4350a --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/block-item-callback.rs @@ -0,0 +1,52 @@ +#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)] +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct non_blocked_my_struct { + pub a: ::std::os::raw::c_int, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + [ + "Size of non_blocked_my_struct", + ][::std::mem::size_of::() - 4usize]; + [ + "Alignment of non_blocked_my_struct", + ][::std::mem::align_of::() - 4usize]; + [ + "Offset of field: non_blocked_my_struct::a", + ][::std::mem::offset_of!(non_blocked_my_struct, a) - 0usize]; +}; +#[repr(C)] +#[derive(Copy, Clone)] +pub union non_blocked_my_union { + pub a: ::std::os::raw::c_int, + pub b: f64, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + [ + "Size of non_blocked_my_union", + ][::std::mem::size_of::() - 8usize]; + [ + "Alignment of non_blocked_my_union", + ][::std::mem::align_of::() - 8usize]; + [ + "Offset of field: non_blocked_my_union::a", + ][::std::mem::offset_of!(non_blocked_my_union, a) - 0usize]; + [ + "Offset of field: non_blocked_my_union::b", + ][::std::mem::offset_of!(non_blocked_my_union, b) - 0usize]; +}; +impl Default for non_blocked_my_union { + fn default() -> Self { + let mut s = ::std::mem::MaybeUninit::::uninit(); + unsafe { + ::std::ptr::write_bytes(s.as_mut_ptr(), 0, 1); + s.assume_init() + } + } +} +pub const non_blocked_my_enum_NON_BLOCKED_MY_ENUM_A: non_blocked_my_enum = 0; +pub const non_blocked_my_enum_NON_BLOCKED_MY_ENUM_B: non_blocked_my_enum = 1; +pub type non_blocked_my_enum = ::std::os::raw::c_uint; +pub const non_blocked_my_const: ::std::os::raw::c_int = 10; diff --git a/bindgen-tests/tests/headers/allow-item-callback.h b/bindgen-tests/tests/headers/allow-item-callback.h new file mode 100644 index 0000000000..04610f5c6b --- /dev/null +++ b/bindgen-tests/tests/headers/allow-item-callback.h @@ -0,0 +1,33 @@ +// bindgen-parse-callbacks: allow-item + +struct allowed_my_struct { + int a; +}; + +union allowed_my_union { + int a; + double b; +}; + +enum allowed_my_enum { + ALLOWED_MY_ENUM_A, + ALLOWED_MY_ENUM_B, +}; + +static const int allowed_my_const = 10; + +struct non_allowed_my_struct { + int a; +}; + +union non_allowed_my_union { + int a; + double b; +}; + +enum non_allowed_my_enum { + NON_ALLOWED_MY_ENUM_A, + NON_ALLOWED_MY_ENUM_B, +}; + +static const int non_allowed_my_const = 10; diff --git a/bindgen-tests/tests/headers/block-item-callback.h b/bindgen-tests/tests/headers/block-item-callback.h new file mode 100644 index 0000000000..82ad1fba4b --- /dev/null +++ b/bindgen-tests/tests/headers/block-item-callback.h @@ -0,0 +1,33 @@ +// bindgen-parse-callbacks: block-item + +struct blocked_my_struct { + int a; +}; + +union blocked_my_union { + int a; + double b; +}; + +enum blocked_my_enum { + BLOCKED_MY_ENUM_A, + BLOCKED_MY_ENUM_B, +}; + +static const int blocked_my_const = 10; + +struct non_blocked_my_struct { + int a; +}; + +union non_blocked_my_union { + int a; + double b; +}; + +enum non_blocked_my_enum { + NON_BLOCKED_MY_ENUM_A, + NON_BLOCKED_MY_ENUM_B, +}; + +static const int non_blocked_my_const = 10; diff --git a/bindgen-tests/tests/parse_callbacks/mod.rs b/bindgen-tests/tests/parse_callbacks/mod.rs index 5fe8d90d4c..f75b777e7a 100644 --- a/bindgen-tests/tests/parse_callbacks/mod.rs +++ b/bindgen-tests/tests/parse_callbacks/mod.rs @@ -159,6 +159,24 @@ impl ParseCallbacks for OperatorRename { } } +#[derive(Debug)] +struct AllowItem; + +impl ParseCallbacks for AllowItem { + fn allow_item(&self, item: ItemInfo) -> bool { + item.name.starts_with("allowed_") + } +} + +#[derive(Debug)] +struct BlockItem; + +impl ParseCallbacks for BlockItem { + fn block_item(&self, item: ItemInfo) -> bool { + item.name.starts_with("blocked_") + } +} + pub fn lookup(cb: &str) -> Box { match cb { "enum-variant-rename" => Box::new(EnumVariantRename), @@ -168,6 +186,8 @@ pub fn lookup(cb: &str) -> Box { "wrap-as-variadic-fn" => Box::new(WrapAsVariadicFn), "type-visibility" => Box::new(TypeVisibility), "operator-rename" => Box::new(OperatorRename), + "allow-item" => Box::new(AllowItem), + "block-item" => Box::new(BlockItem), call_back => { if let Some(prefix) = call_back.strip_prefix("remove-function-prefix-") diff --git a/bindgen/callbacks.rs b/bindgen/callbacks.rs index 8ad06b3375..75720d9c07 100644 --- a/bindgen/callbacks.rs +++ b/bindgen/callbacks.rs @@ -171,6 +171,24 @@ pub trait ParseCallbacks: fmt::Debug { /// This will get called everytime an item (currently struct, union, and alias) is found with some information about it fn new_item_found(&self, _id: DiscoveredItemId, _item: DiscoveredItem) {} + /// Generate bindings for the given item. + /// + /// This method is called after processing the `allowlist_*` options. + /// + /// The default implementation is to allow the given item. + fn allow_item(&self, _item: ItemInfo) -> bool { + true + } + + /// Block bindings for the given item. + /// + /// This method is called after processing the `blocklist_*` options. + /// + /// The default implementation is to not block the given item. + fn block_item(&self, _item: ItemInfo) -> bool { + false + } + // TODO add callback for ResolvedTypeRef } diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index 433fc85f01..9e7f92b4a7 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -22,7 +22,7 @@ use super::BindgenOptions; use crate::callbacks::{ AttributeInfo, DeriveInfo, DiscoveredItem, DiscoveredItemId, FieldInfo, - TypeKind as DeriveTypeKind, + ItemInfo, ItemKind as CallbackItemKind, TypeKind as DeriveTypeKind, }; use crate::codegen::error::Error; use crate::ir::analysis::{HasVtable, Sizedness}; @@ -4942,7 +4942,14 @@ fn objc_method_codegen( // Item::process_before_codegen; however, ObjC methods are not currently // made into function items. let name = format!("{rust_class_name}::{prefix}{}", method.rust_name()); - if ctx.options().blocklisted_items.matches(name) { + let item_info = ItemInfo { + name: &name, + kind: CallbackItemKind::Function, + }; + if ctx.options().blocklisted_items.matches(&name) || + ctx.options() + .for_any_callback(|cb| cb.block_item(item_info)) + { return; } @@ -5259,6 +5266,7 @@ pub(crate) mod utils { use super::helpers::BITFIELD_UNIT; use super::serialize::CSerialize; use super::{error, CodegenError, CodegenResult, ToRustTyOrOpaque}; + use crate::callbacks::{ItemInfo, ItemKind as CallbackItemKind}; use crate::ir::context::BindgenContext; use crate::ir::context::TypeId; use crate::ir::function::{Abi, ClangAbi, FunctionSig}; @@ -5389,8 +5397,14 @@ pub(crate) mod utils { ctx: &BindgenContext, result: &mut Vec, ) { + let item_info = ItemInfo { + name: BITFIELD_UNIT, + kind: CallbackItemKind::Type, + }; if ctx.options().blocklisted_items.matches(BITFIELD_UNIT) || - ctx.options().blocklisted_types.matches(BITFIELD_UNIT) + ctx.options().blocklisted_types.matches(BITFIELD_UNIT) || + ctx.options() + .for_any_callback(|cb| cb.block_item(item_info)) { return; } diff --git a/bindgen/ir/context.rs b/bindgen/ir/context.rs index 6adbcd5989..bf3e6ae84d 100644 --- a/bindgen/ir/context.rs +++ b/bindgen/ir/context.rs @@ -21,6 +21,7 @@ use super::traversal::{self, Edge, ItemTraversal}; use super::ty::{FloatKind, Type, TypeKind}; use crate::clang::{self, ABIKind, Cursor}; use crate::codegen::CodegenError; +use crate::ir::item::ItemCanonicalName; use crate::BindgenOptions; use crate::{Entry, HashMap, HashSet}; @@ -2519,6 +2520,14 @@ If you encounter an error missing from this list, please file an issue or a PR!" } } }) + .filter(|&(_, item)| { + let item_info = crate::callbacks::ItemInfo { + name: &item.canonical_name(self), + kind: item.callback_item_kind(), + }; + self.options() + .for_all_callbacks(|cb| cb.allow_item(item_info)) + }) .map(|(id, _)| id) .collect::>(); diff --git a/bindgen/ir/item.rs b/bindgen/ir/item.rs index d38879f390..ac4e913cee 100644 --- a/bindgen/ir/item.rs +++ b/bindgen/ir/item.rs @@ -653,6 +653,10 @@ impl Item { let path = self.path_for_allowlisting(ctx); let name = path[1..].join("::"); + let item_info = ItemInfo { + name: &name, + kind: self.callback_item_kind(), + }; ctx.options().blocklisted_items.matches(&name) || match self.kind { ItemKind::Type(..) => { @@ -667,7 +671,9 @@ impl Item { } // TODO: Add namespace blocklisting? ItemKind::Module(..) => false, - } + } || + ctx.options() + .for_any_callback(|cb| cb.block_item(item_info)) } /// Take out item `NameOptions` @@ -818,6 +824,16 @@ impl Item { } } + /// Get the callback item kind of this item. + pub(crate) fn callback_item_kind(&self) -> crate::callbacks::ItemKind { + match self.kind() { + ItemKind::Module(..) => crate::callbacks::ItemKind::Module, + ItemKind::Type(..) => crate::callbacks::ItemKind::Type, + ItemKind::Function(..) => crate::callbacks::ItemKind::Function, + ItemKind::Var(..) => crate::callbacks::ItemKind::Var, + } + } + /// Get the canonical name without taking into account the replaces /// annotation. /// @@ -925,14 +941,7 @@ impl Item { let name = if opt.user_mangled == UserMangled::Yes { let item_info = ItemInfo { name: &name, - kind: match self.kind() { - ItemKind::Module(..) => crate::callbacks::ItemKind::Module, - ItemKind::Type(..) => crate::callbacks::ItemKind::Type, - ItemKind::Function(..) => { - crate::callbacks::ItemKind::Function - } - ItemKind::Var(..) => crate::callbacks::ItemKind::Var, - }, + kind: self.callback_item_kind(), }; ctx.options() .last_callback(|callbacks| callbacks.item_name(item_info)) diff --git a/bindgen/lib.rs b/bindgen/lib.rs index e4294c505a..22795e52d5 100644 --- a/bindgen/lib.rs +++ b/bindgen/lib.rs @@ -591,6 +591,20 @@ impl BindgenOptions { self.parse_callbacks.iter().for_each(|cb| f(cb.as_ref())); } + fn for_all_callbacks( + &self, + f: impl Fn(&dyn callbacks::ParseCallbacks) -> bool, + ) -> bool { + self.parse_callbacks.iter().all(|cb| f(cb.as_ref())) + } + + fn for_any_callback( + &self, + f: impl Fn(&dyn callbacks::ParseCallbacks) -> bool, + ) -> bool { + self.parse_callbacks.iter().any(|cb| f(cb.as_ref())) + } + fn process_comment(&self, comment: &str) -> String { let comment = comment::preprocess(comment); self.last_callback(|cb| cb.process_comment(&comment))