diff --git a/Cargo.lock b/Cargo.lock index f7accc9e68..142112a213 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,6 +81,7 @@ dependencies = [ "owo-colors", "prettyplease", "proc-macro2", + "regex", "shlex", "similar", "syn 2.0.90", diff --git a/bindgen-tests/Cargo.toml b/bindgen-tests/Cargo.toml index 3a13c59cfa..77a28ca3cb 100644 --- a/bindgen-tests/Cargo.toml +++ b/bindgen-tests/Cargo.toml @@ -12,6 +12,7 @@ bindgen = { workspace = true, default-features = true, features = ["__cli", "exp owo-colors.workspace = true prettyplease = { workspace = true, features = ["verbatim"] } proc-macro2.workspace = true +regex.workspace = true shlex.workspace = true similar = { workspace = true, features = ["inline"] } syn.workspace = true diff --git a/bindgen-tests/tests/parse_callbacks/item_discovery_callback/header_item_discovery.h b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/header_item_discovery.h new file mode 100644 index 0000000000..10e97ea480 --- /dev/null +++ b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/header_item_discovery.h @@ -0,0 +1,16 @@ +// Unions +void function_using_anonymous_struct(struct {} arg0); + +struct NamedStruct { +}; + +typedef struct NamedStruct AliasOfNamedStruct; + + +// Unions +void function_using_anonymous_union(union {} arg0); + +union NamedUnion { +}; + +typedef union NamedUnion AliasOfNamedUnion; \ No newline at end of file diff --git a/bindgen-tests/tests/parse_callbacks/item_discovery_callback/mod.rs b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/mod.rs new file mode 100644 index 0000000000..da37eeec97 --- /dev/null +++ b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/mod.rs @@ -0,0 +1,246 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; + +use regex::Regex; + +use bindgen::callbacks::{DiscoveredItem, DiscoveredItemId, ParseCallbacks}; +use bindgen::Builder; + +#[derive(Debug, Default)] +struct ItemDiscovery(Rc>); + +pub type ItemCache = HashMap; + +impl ParseCallbacks for ItemDiscovery { + fn new_item_found(&self, _id: DiscoveredItemId, _item: DiscoveredItem) { + self.0.borrow_mut().insert(_id, _item); + } +} +#[test] +pub fn test_item_discovery_callback() { + let discovery = ItemDiscovery::default(); + let info = Rc::clone(&discovery.0); + + Builder::default() + .header(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/parse_callbacks/item_discovery_callback/header_item_discovery.h" + )) + .parse_callbacks(Box::new(discovery)) + .generate() + .expect("TODO: panic message"); + + let expected = ItemCache::from([ + ( + DiscoveredItemId::new(10), + DiscoveredItem::Struct { + original_name: Some("NamedStruct".to_string()), + final_name: "NamedStruct".to_string(), + }, + ), + ( + DiscoveredItemId::new(11), + DiscoveredItem::Alias { + alias_name: "AliasOfNamedStruct".to_string(), + alias_for: DiscoveredItemId::new(10), + }, + ), + ( + DiscoveredItemId::new(20), + DiscoveredItem::Union { + original_name: Some("NamedUnion".to_string()), + final_name: "NamedUnion".to_string(), + }, + ), + ( + DiscoveredItemId::new(21), + DiscoveredItem::Alias { + alias_name: "AliasOfNamedUnion".to_string(), + alias_for: DiscoveredItemId::new(20), + }, + ), + ( + DiscoveredItemId::new(30), + DiscoveredItem::Struct { + original_name: None, + final_name: "_bindgen_ty_*".to_string(), + }, + ), + ( + DiscoveredItemId::new(40), + DiscoveredItem::Union { + original_name: None, + final_name: "_bindgen_ty_*".to_string(), + }, + ), + ]); + + compare_item_caches(info.borrow().clone(), expected); +} + +pub fn compare_item_caches(generated: ItemCache, expected: ItemCache) { + // We can't use a simple Eq::eq comparison because of two reasons: + // - anonymous structs/unions will have a final name generated by bindgen which may change + // if the header file or the bindgen logic is altered + // - aliases have a DiscoveredItemId that we can't directly compare for the same instability reasons + for expected_item in expected.values() { + let found = generated.iter().find(|(_generated_id, generated_item)| { + compare_item_info( + expected_item, + generated_item, + &expected, + &generated, + ) + }); + + if found.is_none() { + panic!( + "Missing Expected Item: {:#?}\n in {:#?}", + expected_item, generated + ); + } + } +} + +fn compare_item_info( + expected_item: &DiscoveredItem, + generated_item: &DiscoveredItem, + expected: &ItemCache, + generated: &ItemCache, +) -> bool { + if std::mem::discriminant(expected_item) != + std::mem::discriminant(generated_item) + { + return false; + } + + match generated_item { + DiscoveredItem::Struct { .. } => { + compare_struct_info(expected_item, generated_item) + } + DiscoveredItem::Union { .. } => { + compare_union_info(expected_item, generated_item) + } + DiscoveredItem::Alias { .. } => compare_alias_info( + expected_item, + generated_item, + expected, + generated, + ), + } +} + +pub fn compare_names(expected_name: &str, generated_name: &str) -> bool { + if let Ok(regex) = Regex::new(expected_name) { + regex.is_match(generated_name) + } else { + false + } +} + +pub fn compare_struct_info( + expected_item: &DiscoveredItem, + generated_item: &DiscoveredItem, +) -> bool { + let DiscoveredItem::Struct { + original_name: expected_original_name, + final_name: expected_final_name, + } = expected_item + else { + unreachable!() + }; + + let DiscoveredItem::Struct { + original_name: generated_original_name, + final_name: generated_final_name, + } = generated_item + else { + unreachable!() + }; + + if !compare_names(expected_final_name, generated_final_name) { + return false; + } + + match (expected_original_name, generated_original_name) { + (None, None) => true, + (Some(expected_original_name), Some(generated_original_name)) => { + compare_names(expected_original_name, generated_original_name) + } + _ => false, + } +} + +pub fn compare_union_info( + expected_item: &DiscoveredItem, + generated_item: &DiscoveredItem, +) -> bool { + let DiscoveredItem::Union { + original_name: expected_original_name, + final_name: expected_final_name, + } = expected_item + else { + unreachable!() + }; + + let DiscoveredItem::Union { + original_name: generated_original_name, + final_name: generated_final_name, + } = generated_item + else { + unreachable!() + }; + + if !compare_names(expected_final_name, generated_final_name) { + return false; + } + + match (expected_original_name, generated_original_name) { + (None, None) => true, + (Some(expected_original_name), Some(generated_original_name)) => { + compare_names(expected_original_name, generated_original_name) + } + _ => false, + } +} + +pub fn compare_alias_info( + expected_item: &DiscoveredItem, + generated_item: &DiscoveredItem, + expected: &ItemCache, + generated: &ItemCache, +) -> bool { + let DiscoveredItem::Alias { + alias_name: expected_alias_name, + alias_for: expected_alias_for, + } = expected_item + else { + unreachable!() + }; + + let DiscoveredItem::Alias { + alias_name: generated_alias_name, + alias_for: generated_alias_for, + } = generated_item + else { + unreachable!() + }; + + if !compare_names(expected_alias_name, generated_alias_name) { + return false; + } + + // Assumes correct test definition + let expected_aliased = expected.get(expected_alias_for).unwrap(); + + // We must have the aliased type in the cache + let generated_aliased = + if let Some(generated_aliased) = generated.get(generated_alias_for) { + generated_aliased + } else { + return false; + }; + + compare_item_info(expected_aliased, generated_aliased, expected, generated) +} diff --git a/bindgen-tests/tests/parse_callbacks/mod.rs b/bindgen-tests/tests/parse_callbacks/mod.rs index 2fba5f11a2..7aca0fd1a1 100644 --- a/bindgen-tests/tests/parse_callbacks/mod.rs +++ b/bindgen-tests/tests/parse_callbacks/mod.rs @@ -1,3 +1,5 @@ +mod item_discovery_callback; + use bindgen::callbacks::*; use bindgen::FieldVisibilityKind; diff --git a/bindgen/callbacks.rs b/bindgen/callbacks.rs index 43dc37d595..71ed325405 100644 --- a/bindgen/callbacks.rs +++ b/bindgen/callbacks.rs @@ -162,6 +162,62 @@ pub trait ParseCallbacks: fmt::Debug { fn wrap_as_variadic_fn(&self, _name: &str) -> Option { None } + + /// 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) {} + + // TODO add callback for ResolvedTypeRef +} + +/// An identifier for a discovered item. Used to identify an aliased type (see [DiscoveredItem::Alias]) +#[derive(Ord, PartialOrd, PartialEq, Eq, Hash, Debug, Clone, Copy)] +pub struct DiscoveredItemId(usize); + +impl DiscoveredItemId { + /// Constructor + pub fn new(value: usize) -> Self { + Self(value) + } +} + +/// Struct passed to [ParseCallbacks::new_item_found] containing information about discovered +/// items (struct, union, and alias) +#[derive(Debug, Hash, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub enum DiscoveredItem { + /// Represents a struct with its original name in C and its generated binding name + Struct { + /// The original name (learnt from C) of the structure + /// Can be None if the union is anonymous. + original_name: Option, + + /// The name of the generated binding + final_name: String, + }, + + /// Represents a union with its original name in C and its generated binding name + Union { + /// The original name (learnt from C) of the structure. + /// Can be None if the union is anonymous. + original_name: Option, + + /// The name of the generated binding + final_name: String, + }, + + /// Represents an alias like a typedef + /// ```c + /// typedef struct MyStruct { + /// ... + /// } StructAlias; + /// ``` + /// Here, the name of the alias is `StructAlias` and it's an alias for `MyStruct` + Alias { + /// The name of the alias in C (`StructAlias`) + alias_name: String, + + /// The identifier of the discovered type + alias_for: DiscoveredItemId, + }, // functions, modules, etc. } /// 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 82dd8b7199..1561d4d8cd 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -21,7 +21,8 @@ use self::struct_layout::StructLayoutTracker; use super::BindgenOptions; use crate::callbacks::{ - AttributeInfo, DeriveInfo, FieldInfo, TypeKind as DeriveTypeKind, + AttributeInfo, DeriveInfo, DiscoveredItem, DiscoveredItemId, FieldInfo, + TypeKind as DeriveTypeKind, }; use crate::codegen::error::Error; use crate::ir::analysis::{HasVtable, Sizedness}; @@ -983,6 +984,18 @@ impl CodeGenerator for Type { let rust_name = ctx.rust_ident(&name); + ctx.options().for_each_callback(|cb| { + cb.new_item_found( + DiscoveredItemId::new(item.id().as_usize()), + DiscoveredItem::Alias { + alias_name: rust_name.to_string(), + alias_for: DiscoveredItemId::new( + inner_item.id().as_usize(), + ), + }, + ); + }); + let mut tokens = if let Some(comment) = item.comment(ctx) { attributes::doc(comment) } else { @@ -2449,6 +2462,32 @@ impl CodeGenerator for CompInfo { let is_rust_union = is_union && struct_layout.is_rust_union(); + ctx.options().for_each_callback(|cb| { + let discovered_item = match self.kind() { + CompKind::Struct => DiscoveredItem::Struct { + original_name: item + .kind() + .expect_type() + .name() + .map(String::from), + final_name: canonical_ident.to_string(), + }, + CompKind::Union => DiscoveredItem::Union { + original_name: item + .kind() + .expect_type() + .name() + .map(String::from), + final_name: canonical_ident.to_string(), + }, + }; + + cb.new_item_found( + DiscoveredItemId::new(item.id().as_usize()), + discovered_item, + ); + }); + // The custom derives callback may return a list of derive attributes; // add them to the end of the list. let custom_derives = ctx.options().all_callbacks(|cb| {