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..aea5999cc3 --- /dev/null +++ b/bindgen-tests/tests/expectations/tests/allow-item-callback.rs @@ -0,0 +1,65 @@ +#![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; +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct list_allowed_my_struct { + pub a: ::std::os::raw::c_int, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + [ + "Size of list_allowed_my_struct", + ][::std::mem::size_of::() - 4usize]; + [ + "Alignment of list_allowed_my_struct", + ][::std::mem::align_of::() - 4usize]; + [ + "Offset of field: list_allowed_my_struct::a", + ][::std::mem::offset_of!(list_allowed_my_struct, a) - 0usize]; +}; 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..b727987398 --- /dev/null +++ b/bindgen-tests/tests/headers/allow-item-callback.h @@ -0,0 +1,38 @@ +// bindgen-flags: --allowlist-item 'list_allowed_.*' +// 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; + +struct list_allowed_my_struct { + int a; +}; 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..046f3475b1 --- /dev/null +++ b/bindgen-tests/tests/headers/block-item-callback.h @@ -0,0 +1,38 @@ +// bindgen-flags: --blocklist-item 'list_blocked_.*' +// 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; + +struct list_blocked_my_struct { + int a; +}; diff --git a/bindgen-tests/tests/parse_callbacks/mod.rs b/bindgen-tests/tests/parse_callbacks/mod.rs index 02d7fe8316..5bea1f10ef 100644 --- a/bindgen-tests/tests/parse_callbacks/mod.rs +++ b/bindgen-tests/tests/parse_callbacks/mod.rs @@ -160,6 +160,32 @@ impl ParseCallbacks for OperatorRename { } } +#[derive(Debug)] +struct AllowItem; + +impl ParseCallbacks for AllowItem { + fn allow_or_block_item(&self, item: &ItemInfo) -> Option { + if item.name.starts_with("allowed_") { + Some(AllowOrBlockItem::Allow) + } else { + None + } + } +} + +#[derive(Debug)] +struct BlockItem; + +impl ParseCallbacks for BlockItem { + fn allow_or_block_item(&self, item: &ItemInfo) -> Option { + if item.name.starts_with("blocked_") { + Some(AllowOrBlockItem::Block) + } else { + None + } + } +} + pub fn lookup(cb: &str) -> Box { match cb { "enum-variant-rename" => Box::new(EnumVariantRename), @@ -169,6 +195,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 630a306aec..71086bedef 100644 --- a/bindgen/callbacks.rs +++ b/bindgen/callbacks.rs @@ -19,6 +19,16 @@ pub enum MacroParsingBehavior { Default, } +/// Enum to indicate if the bindings for a given should be generated or blocked. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum AllowOrBlockItem { + /// Generate the bindings for the given item. + Allow, + + /// Block bindings for the given item. + Block, +} + /// A trait to allow configuring different kinds of types in different /// situations. pub trait ParseCallbacks: fmt::Debug { @@ -206,6 +216,26 @@ pub trait ParseCallbacks: fmt::Debug { ) { } + /// Generate or block the bindings for the given item. + /// + /// This method takes precedences over the `allowlist_*` options. + /// + /// If at least one of the parse callbacks returns `Block`, the generation of the bindings + /// for the item is blocked. + /// + /// If all the parse callbacks that don't return `None` return `Allow`, the bindings + /// for the item are generated. + /// + /// If all the parse callbacks return `None` (the default implementation), the `allowlist_*` + /// options are used instead. + /// + fn allow_or_block_item( + &self, + _item: &ItemInfo, + ) -> Option { + None + } + // TODO add callback for ResolvedTypeRef } diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index a28f5b33e5..4914c821ca 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -22,7 +22,8 @@ use super::BindgenOptions; use crate::callbacks::{ AttributeInfo, DeriveInfo, DiscoveredItem, DiscoveredItemId, - FieldAttributeInfo, FieldInfo, TypeKind as DeriveTypeKind, + FieldAttributeInfo, FieldInfo, ItemInfo, ItemKind as CallbackItemKind, + TypeKind as DeriveTypeKind, }; use crate::codegen::error::Error; use crate::ir::analysis::{HasVtable, Sizedness}; @@ -5008,7 +5009,18 @@ 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() + .cb_item_is_blocked(&item_info) + .unwrap_or_else(|| ctx.options().blocklisted_items.matches(&name)) + { + // Item is blocked through the parse callbacks or `blocklisted_items`. return; } @@ -5325,7 +5337,9 @@ pub(crate) mod utils { use super::helpers::BITFIELD_UNIT; use super::serialize::CSerialize; use super::{error, CodegenError, CodegenResult, ToRustTyOrOpaque}; - use crate::callbacks::DiscoveredItemId; + use crate::callbacks::{ + DiscoveredItemId, ItemInfo, ItemKind as CallbackItemKind, + }; use crate::ir::context::BindgenContext; use crate::ir::context::TypeId; use crate::ir::function::{Abi, ClangAbi, FunctionSig}; @@ -5456,9 +5470,20 @@ pub(crate) mod utils { ctx: &BindgenContext, result: &mut Vec, ) { - if ctx.options().blocklisted_items.matches(BITFIELD_UNIT) || - ctx.options().blocklisted_types.matches(BITFIELD_UNIT) + let item_info = ItemInfo { + name: BITFIELD_UNIT, + kind: CallbackItemKind::Type, + }; + + if ctx + .options() + .cb_item_is_blocked(&item_info) + .unwrap_or_else(|| { + ctx.options().blocklisted_items.matches(BITFIELD_UNIT) || + ctx.options().blocklisted_types.matches(BITFIELD_UNIT) + }) { + // Item is blocked through the parse callbacks or blocklists. return; } diff --git a/bindgen/ir/context.rs b/bindgen/ir/context.rs index 8e4163df5e..095ea6e07a 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}; @@ -2406,6 +2407,18 @@ If you encounter an error missing from this list, please file an issue or a PR!" // Only consider roots that are enabled for codegen. .filter(|&(_, item)| item.is_enabled_for_codegen(self)) .filter(|&(_, item)| { + let item_info = crate::callbacks::ItemInfo { + name: &item.canonical_name(self), + kind: item.callback_item_kind(), + }; + + if let Some(is_cb_allow) = + self.options().cb_item_is_allowed(&item_info) + { + // Item is allowed or not with the parse callbacks. + return is_cb_allow; + } + // If nothing is explicitly allowlisted, then everything is fair // game. if self.options().allowlisted_types.is_empty() && diff --git a/bindgen/ir/item.rs b/bindgen/ir/item.rs index eea02cce6c..42233676d3 100644 --- a/bindgen/ir/item.rs +++ b/bindgen/ir/item.rs @@ -641,6 +641,20 @@ impl Item { return true; } + let path = self.path_for_allowlisting(ctx); + let name = path[1..].join("::"); + let item_info = ItemInfo { + name: &name, + kind: self.callback_item_kind(), + }; + + if let Some(is_cb_blocked) = + ctx.options().cb_item_is_blocked(&item_info) + { + // The item is blocked or not with the parse callbacks. + return is_cb_blocked; + } + if !ctx.options().blocklisted_files.is_empty() { if let Some(location) = &self.location { let (file, _, _, _) = location.location(); @@ -652,8 +666,6 @@ impl Item { } } - let path = self.path_for_allowlisting(ctx); - let name = path[1..].join("::"); ctx.options().blocklisted_items.matches(&name) || match self.kind { ItemKind::Type(..) => { @@ -819,6 +831,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. /// @@ -926,14 +948,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 0305b5cd7b..9da7a0f8d2 100644 --- a/bindgen/lib.rs +++ b/bindgen/lib.rs @@ -586,6 +586,38 @@ impl BindgenOptions { self.parse_callbacks.iter().for_each(|cb| f(cb.as_ref())); } + fn cb_item_is_allowed( + &self, + item_info: &callbacks::ItemInfo, + ) -> Option { + let mut res = None; + + for cb in &self.parse_callbacks { + if let Some(allow_or_block) = cb.allow_or_block_item(item_info) { + match allow_or_block { + callbacks::AllowOrBlockItem::Allow => { + // Continue to check if some other callbacks returns `Block`. + res = Some(true); + } + callbacks::AllowOrBlockItem::Block => { + // Return `false` immediately to block the bindings generation. + return Some(false); + } + } + } + } + + res + } + + fn cb_item_is_blocked( + &self, + item_info: &callbacks::ItemInfo, + ) -> Option { + self.cb_item_is_allowed(item_info) + .map(|is_allowed| !is_allowed) + } + fn process_comment(&self, comment: &str) -> String { let comment = comment::preprocess(comment); self.last_callback(|cb| cb.process_comment(&comment))