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 index b2bb04f15f..eb44e5fc58 100644 --- 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 @@ -25,4 +25,8 @@ enum NamedEnum { Fish, }; -typedef enum NamedEnum AliasOfNamedEnum; \ No newline at end of file +typedef enum NamedEnum AliasOfNamedEnum; + +// Functions + +void named_function(); diff --git a/bindgen-tests/tests/parse_callbacks/item_discovery_callback/header_item_discovery.hpp b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/header_item_discovery.hpp new file mode 100644 index 0000000000..0fb70d9396 --- /dev/null +++ b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/header_item_discovery.hpp @@ -0,0 +1,16 @@ +// Methods + +class SomeClass { +public: + SomeClass() = delete; + SomeClass(const SomeClass&) = default; + SomeClass(SomeClass&&); + void named_method(); + virtual void virtual_method(); + virtual void pure_virtual_method() = 0; +private: + void private_method(); +protected: + void protected_method(); + +}; \ No newline at end of file diff --git a/bindgen-tests/tests/parse_callbacks/item_discovery_callback/header_item_discovery_with_namespaces.hpp b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/header_item_discovery_with_namespaces.hpp new file mode 100644 index 0000000000..ad8d4a524f --- /dev/null +++ b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/header_item_discovery_with_namespaces.hpp @@ -0,0 +1,30 @@ +void a(); + +namespace B { + void c(); + + namespace D { + void e(); + } + + // We should not report empty namespaces + namespace F { + } + + namespace { + void g(); + } + + inline namespace H { + void i(); + namespace J { + void k(); + } + } + + struct L { + struct M { + + }; + }; +}; \ 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 index 93a2b029d7..ead1663a2c 100644 --- a/bindgen-tests/tests/parse_callbacks/item_discovery_callback/mod.rs +++ b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/mod.rs @@ -1,98 +1,528 @@ use std::cell::RefCell; use std::collections::HashMap; +use std::convert::identity; use std::rc::Rc; use regex::Regex; -use bindgen::callbacks::{DiscoveredItem, DiscoveredItemId, ParseCallbacks}; +use bindgen::callbacks::{ + DiscoveredItem, DiscoveredItemId, ParseCallbacks, SourceLocation, + SpecialMemberKind, Visibility, +}; use bindgen::Builder; #[derive(Debug, Default)] struct ItemDiscovery(Rc>); -pub type ItemCache = HashMap; +pub type ItemCache = HashMap; + +#[derive(Debug)] +pub struct DiscoveredInformation( + DiscoveredItem, + Option, + Option, +); impl ParseCallbacks for ItemDiscovery { - fn new_item_found(&self, _id: DiscoveredItemId, _item: DiscoveredItem) { - self.0.borrow_mut().insert(_id, _item); + fn new_item_found( + &self, + id: DiscoveredItemId, + item: DiscoveredItem, + source_location: Option<&SourceLocation>, + parent: Option, + ) { + self.0.borrow_mut().insert( + id, + DiscoveredInformation(item, source_location.cloned(), parent), + ); } } -#[test] -pub fn test_item_discovery_callback() { + +#[derive(Debug)] +pub struct ItemExpectations { + item: DiscoveredItem, + source_location: Option<(usize, usize, usize)>, + parent: Option, +} + +impl ItemExpectations { + fn new( + item: DiscoveredItem, + line: usize, + col: usize, + byte_offset: usize, + parent: Option, + ) -> Self { + Self { + item, + source_location: Some((line, col, byte_offset)), + parent, + } + } + + fn new_no_source_location( + item: DiscoveredItem, + parent: Option, + ) -> Self { + Self { + item, + source_location: None, + parent, + } + } +} + +type ExpectationMap = HashMap; + +fn test_item_discovery_callback Builder>( + header: &str, + expected: HashMap, + builder_adjuster: F, +) { 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 mut header_path = env!("CARGO_MANIFEST_DIR").to_string(); + header_path.push_str(header); + + let b = Builder::default() + .header(header_path) + .parse_callbacks(Box::new(discovery)); + builder_adjuster(b).generate().expect("TODO: panic message"); - let expected = ItemCache::from([ + compare_item_caches(&info.borrow(), &expected, header); +} + +#[test] +fn test_item_discovery_callback_c() { + let expected = ExpectationMap::from([ ( DiscoveredItemId::new(10), - DiscoveredItem::Struct { - original_name: Some("NamedStruct".to_string()), - final_name: "NamedStruct".to_string(), - }, + ItemExpectations::new( + DiscoveredItem::Struct { + original_name: Some("NamedStruct".to_string()), + final_name: "NamedStruct".to_string(), + cpp_visibility: Visibility::Public, + }, + 4, + 8, + 73, + None, + ), ), ( DiscoveredItemId::new(11), - DiscoveredItem::Alias { - alias_name: "AliasOfNamedStruct".to_string(), - alias_for: DiscoveredItemId::new(10), - }, + ItemExpectations::new( + DiscoveredItem::Alias { + alias_name: "AliasOfNamedStruct".to_string(), + alias_for: DiscoveredItemId::new(10), + }, + 7, + 28, + 118, + None, + ), ), ( DiscoveredItemId::new(20), - DiscoveredItem::Union { - original_name: Some("NamedUnion".to_string()), - final_name: "NamedUnion".to_string(), - }, + ItemExpectations::new( + DiscoveredItem::Union { + original_name: Some("NamedUnion".to_string()), + final_name: "NamedUnion".to_string(), + cpp_visibility: Visibility::Public, + }, + 13, + 7, + 209, + None, + ), ), ( DiscoveredItemId::new(21), - DiscoveredItem::Alias { - alias_name: "AliasOfNamedUnion".to_string(), - alias_for: DiscoveredItemId::new(20), - }, + ItemExpectations::new( + DiscoveredItem::Alias { + alias_name: "AliasOfNamedUnion".to_string(), + alias_for: DiscoveredItemId::new(20), + }, + 16, + 26, + 251, + None, + ), ), ( DiscoveredItemId::new(27), - DiscoveredItem::Alias { - alias_name: "AliasOfNamedEnum".to_string(), - alias_for: DiscoveredItemId::new(24), - }, + ItemExpectations::new( + DiscoveredItem::Alias { + alias_name: "AliasOfNamedEnum".to_string(), + alias_for: DiscoveredItemId::new(24), + }, + 28, + 24, + 515, + None, + ), ), ( DiscoveredItemId::new(24), - DiscoveredItem::Enum { - final_name: "NamedEnum".to_string(), - }, + ItemExpectations::new( + DiscoveredItem::Enum { + final_name: "NamedEnum".to_string(), + cpp_visibility: Visibility::Public, + }, + 24, + 6, + 466, + None, + ), ), ( DiscoveredItemId::new(30), - DiscoveredItem::Struct { - original_name: None, - final_name: "_bindgen_ty_*".to_string(), - }, + ItemExpectations::new( + DiscoveredItem::Struct { + original_name: None, + final_name: "_bindgen_ty_*".to_string(), + cpp_visibility: Visibility::Public, + }, + 2, + 38, + 48, + None, + ), ), ( DiscoveredItemId::new(40), - DiscoveredItem::Union { - original_name: None, - final_name: "_bindgen_ty_*".to_string(), - }, + ItemExpectations::new( + DiscoveredItem::Union { + original_name: None, + final_name: "_bindgen_ty_*".to_string(), + cpp_visibility: Visibility::Public, + }, + 11, + 37, + 186, + None, + ), + ), + ( + DiscoveredItemId::new(41), + ItemExpectations::new( + DiscoveredItem::Function { + final_name: "named_function".to_string(), + }, + 32, + 6, + 553, + None, + ), + ), + ]); + test_item_discovery_callback( + "/tests/parse_callbacks/item_discovery_callback/header_item_discovery.h", expected, identity); +} + +#[test] +fn test_item_discovery_callback_cpp_features() { + let expected = ExpectationMap::from([ + ( + DiscoveredItemId::new(1), + ItemExpectations::new( + DiscoveredItem::Struct { + original_name: Some("SomeClass".to_string()), + final_name: "SomeClass".to_string(), + cpp_visibility: Visibility::Public, + }, + 3, + 7, + 18, + None, + ), + ), + ( + DiscoveredItemId::new(2), + ItemExpectations::new( + DiscoveredItem::Method { + final_name: "named_method".to_string(), + parent: DiscoveredItemId::new(1), + cpp_visibility: Visibility::Public, + cpp_special_member: None, + cpp_virtual: None, + cpp_explicit: None, + }, + 8, + 10, + 144, + None, + ), + ), + ( + DiscoveredItemId::new(48), + ItemExpectations::new( + DiscoveredItem::Method { + final_name: "protected_method".to_string(), + parent: DiscoveredItemId::new(1), + cpp_visibility: Visibility::Protected, + cpp_special_member: None, + cpp_virtual: None, + cpp_explicit: None, + }, + 14, + 10, + 295, + None, + ), + ), + ( + DiscoveredItemId::new(19), + ItemExpectations::new( + DiscoveredItem::Method { + final_name: "new".to_string(), + parent: DiscoveredItemId::new(1), + cpp_visibility: Visibility::Public, + cpp_special_member: Some( + SpecialMemberKind::MoveConstructor, + ), + cpp_virtual: None, + cpp_explicit: None, + }, + 7, + 5, + 111, + None, + ), ), ]); - compare_item_caches(&info.borrow(), &expected); + test_item_discovery_callback( + "/tests/parse_callbacks/item_discovery_callback/header_item_discovery.hpp", expected, |b| b.clang_arg("--std=c++11")); } -pub fn compare_item_caches(generated: &ItemCache, expected: &ItemCache) { +/// Returns the expectations corresponding to header_item_discovery_with_namespaces.hpp, +/// other than those items whose behavior changes based on the setting for +/// conservative inline namespaces, which we test each way. +fn cpp_expectation_map() -> ExpectationMap { + ExpectationMap::from([ + ( + DiscoveredItemId::new(0), + ItemExpectations::new_no_source_location( + DiscoveredItem::Mod { + final_name: "".to_string(), + anonymous: false, + inline: false, + }, + None, + ), + ), + ( + DiscoveredItemId::new(4), + ItemExpectations::new( + DiscoveredItem::Function { + final_name: "a".to_string(), + }, + 1, + 6, + 5, + Some(DiscoveredItemId::new(0)), + ), + ), + ( + DiscoveredItemId::new(5), + ItemExpectations::new( + DiscoveredItem::Mod { + final_name: "B".to_string(), + anonymous: false, + inline: false, + }, + 3, + 11, + 21, + Some(DiscoveredItemId::new(0)), + ), + ), + ( + DiscoveredItemId::new(9), + ItemExpectations::new( + DiscoveredItem::Function { + final_name: "c".to_string(), + }, + 4, + 10, + 34, + Some(DiscoveredItemId::new(5)), + ), + ), + ( + DiscoveredItemId::new(10), + ItemExpectations::new( + DiscoveredItem::Mod { + final_name: "D".to_string(), + anonymous: false, + inline: false, + }, + 6, + 15, + 54, + Some(DiscoveredItemId::new(5)), + ), + ), + ( + DiscoveredItemId::new(14), + ItemExpectations::new( + DiscoveredItem::Function { + final_name: "e".to_string(), + }, + 7, + 14, + 71, + Some(DiscoveredItemId::new(10)), + ), + ), + ( + DiscoveredItemId::new(16), + ItemExpectations::new( + DiscoveredItem::Mod { + final_name: "".to_string(), + anonymous: true, + inline: false, + }, + 14, + 15, + 167, + Some(DiscoveredItemId::new(5)), + ), + ), + ( + DiscoveredItemId::new(30), + ItemExpectations::new( + DiscoveredItem::Function { + final_name: "k".to_string(), + }, + 21, + 18, + 276, + Some(DiscoveredItemId::new(26)), + ), + ), + ( + DiscoveredItemId::new(31), + ItemExpectations::new( + DiscoveredItem::Struct { + final_name: "L".to_string(), + original_name: Some("L".to_string()), + cpp_visibility: Visibility::Public, + }, + 25, + 12, + 309, + Some(DiscoveredItemId::new(5)), + ), + ), + ( + DiscoveredItemId::new(32), + ItemExpectations::new( + DiscoveredItem::Struct { + final_name: "L_M".to_string(), + original_name: Some("M".to_string()), + cpp_visibility: Visibility::Public, + }, + 26, + 16, + 328, + Some(DiscoveredItemId::new(31)), + ), + ), + ]) +} + +#[test] +fn test_item_discovery_callback_cpp_namespaces_no_inline_namespaces() { + let mut expected = cpp_expectation_map(); + expected.insert( + DiscoveredItemId::new(25), + ItemExpectations::new( + DiscoveredItem::Function { + final_name: "i".to_string(), + }, + 19, + 14, + 232, + Some(DiscoveredItemId::new(5)), + ), + ); + expected.insert( + DiscoveredItemId::new(26), + ItemExpectations::new( + DiscoveredItem::Mod { + final_name: "J".to_string(), + anonymous: false, + inline: false, + }, + 20, + 19, + 255, + Some(DiscoveredItemId::new(5)), + ), + ); + + // C++11 for inline namespace + test_item_discovery_callback( + "/tests/parse_callbacks/item_discovery_callback/header_item_discovery_with_namespaces.hpp", expected, |b| b.enable_cxx_namespaces().clang_arg("--std=c++11")); +} + +#[test] +fn test_item_discovery_callback_cpp_namespaces_with_inline_namespaces() { + let mut expected = cpp_expectation_map(); + expected.insert( + DiscoveredItemId::new(21), + ItemExpectations::new( + DiscoveredItem::Mod { + final_name: "H".to_string(), + anonymous: false, + inline: true, + }, + 18, + 22, + 215, + Some(DiscoveredItemId::new(5)), + ), + ); + expected.insert( + DiscoveredItemId::new(25), + ItemExpectations::new( + DiscoveredItem::Function { + final_name: "i".to_string(), + }, + 19, + 14, + 232, + Some(DiscoveredItemId::new(21)), + ), + ); + expected.insert( + DiscoveredItemId::new(26), + ItemExpectations::new( + DiscoveredItem::Mod { + final_name: "J".to_string(), + anonymous: false, + inline: false, + }, + 20, + 19, + 255, + Some(DiscoveredItemId::new(21)), + ), + ); + + // C++11 for inline namespace + test_item_discovery_callback( + "/tests/parse_callbacks/item_discovery_callback/header_item_discovery_with_namespaces.hpp", expected, |b| b.enable_cxx_namespaces().conservative_inline_namespaces().clang_arg("--std=c++11")); +} + +fn compare_item_caches( + generated: &ItemCache, + expected: &ExpectationMap, + expected_filename: &str, +) { // 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 @@ -104,6 +534,7 @@ pub fn compare_item_caches(generated: &ItemCache, expected: &ItemCache) { generated_item, expected, generated, + expected_filename, ) }); @@ -115,34 +546,90 @@ pub fn compare_item_caches(generated: &ItemCache, expected: &ItemCache) { } fn compare_item_info( - expected_item: &DiscoveredItem, - generated_item: &DiscoveredItem, - expected: &ItemCache, + expected_item: &ItemExpectations, + generated_item: &DiscoveredInformation, + expected: &ExpectationMap, generated: &ItemCache, + expected_filename: &str, ) -> bool { - if std::mem::discriminant(expected_item) != - std::mem::discriminant(generated_item) + if std::mem::discriminant(&expected_item.item) != + std::mem::discriminant(&generated_item.0) { return false; } - match generated_item { + let is_a_match = match generated_item.0 { DiscoveredItem::Struct { .. } => { - compare_struct_info(expected_item, generated_item) + compare_struct_info(&expected_item.item, &generated_item.0) } DiscoveredItem::Union { .. } => { - compare_union_info(expected_item, generated_item) + compare_union_info(&expected_item.item, &generated_item.0) } DiscoveredItem::Alias { .. } => compare_alias_info( - expected_item, - generated_item, + &expected_item.item, + &generated_item.0, expected, generated, + expected_filename, ), DiscoveredItem::Enum { .. } => { - compare_enum_info(expected_item, generated_item) + compare_enum_info(&expected_item.item, &generated_item.0) + } + DiscoveredItem::Function { .. } => { + compare_function_info(&expected_item.item, &generated_item.0) + } + DiscoveredItem::Method { .. } => { + compare_method_info(&expected_item.item, &generated_item.0) + } + DiscoveredItem::Mod { .. } => { + compare_mod_info(&expected_item.item, &generated_item.0) + } + }; + + if is_a_match { + // Compare source location + assert!( + generated_item.1.is_some() == + expected_item.source_location.is_some(), + "Source location wasn't as expected for generated={generated_item:?}, expected={expected_item:?}" + ); + if let Some(generated_location) = generated_item.1.as_ref() { + let expected_location = expected_item.source_location.unwrap(); + assert!( + generated_location + .file_name + .as_ref() + .expect("No filename provided") + .ends_with(expected_filename), + "Filename differed" + ); + assert_eq!( + ( + generated_location.line, + generated_location.col, + generated_location.byte_offset + ), + expected_location, + "Line/col/offsets differ" + ); + } + + // Compare C++ name info + assert!( + generated_item.2.is_some() == + expected_item.parent.is_some(), + "Parent information didn't match: generated item {generated_item:?}" + ); + + if let Some(generated_parent) = generated_item.2.as_ref() { + let expected_parent = expected_item.parent.as_ref().unwrap(); + assert_eq!( + generated_parent, expected_parent, + "Parent didn't match for {expected_item:?}" + ); } } + is_a_match } pub fn compare_names(expected_name: &str, generated_name: &str) -> bool { @@ -160,6 +647,7 @@ pub fn compare_struct_info( let DiscoveredItem::Struct { original_name: expected_original_name, final_name: expected_final_name, + cpp_visibility: expected_cpp_visibility, } = expected_item else { unreachable!() @@ -168,6 +656,7 @@ pub fn compare_struct_info( let DiscoveredItem::Struct { original_name: generated_original_name, final_name: generated_final_name, + cpp_visibility: generated_cpp_visibility, } = generated_item else { unreachable!() @@ -177,6 +666,10 @@ pub fn compare_struct_info( return false; } + if expected_cpp_visibility != generated_cpp_visibility { + return false; + } + match (expected_original_name, generated_original_name) { (None, None) => true, (Some(expected_original_name), Some(generated_original_name)) => { @@ -193,6 +686,7 @@ pub fn compare_union_info( let DiscoveredItem::Union { original_name: expected_original_name, final_name: expected_final_name, + cpp_visibility: expected_cpp_visibility, } = expected_item else { unreachable!() @@ -201,6 +695,7 @@ pub fn compare_union_info( let DiscoveredItem::Union { original_name: generated_original_name, final_name: generated_final_name, + cpp_visibility: generated_cpp_visibility, } = generated_item else { unreachable!() @@ -210,6 +705,10 @@ pub fn compare_union_info( return false; } + if expected_cpp_visibility != generated_cpp_visibility { + return false; + } + match (expected_original_name, generated_original_name) { (None, None) => true, (Some(expected_original_name), Some(generated_original_name)) => { @@ -225,6 +724,7 @@ pub fn compare_enum_info( ) -> bool { let DiscoveredItem::Enum { final_name: expected_final_name, + cpp_visibility: expected_cpp_visibility, } = expected_item else { unreachable!() @@ -232,6 +732,7 @@ pub fn compare_enum_info( let DiscoveredItem::Enum { final_name: generated_final_name, + cpp_visibility: generated_cpp_visibility, } = generated_item else { unreachable!() @@ -240,14 +741,20 @@ pub fn compare_enum_info( if !compare_names(expected_final_name, generated_final_name) { return false; } + + if expected_cpp_visibility != generated_cpp_visibility { + return false; + } + true } pub fn compare_alias_info( expected_item: &DiscoveredItem, generated_item: &DiscoveredItem, - expected: &ItemCache, + expected: &ExpectationMap, generated: &ItemCache, + expected_filename: &str, ) -> bool { let DiscoveredItem::Alias { alias_name: expected_alias_name, @@ -277,5 +784,117 @@ pub fn compare_alias_info( return false; }; - compare_item_info(expected_aliased, generated_aliased, expected, generated) + compare_item_info( + expected_aliased, + generated_aliased, + expected, + generated, + expected_filename, + ) +} + +pub fn compare_function_info( + expected_item: &DiscoveredItem, + generated_item: &DiscoveredItem, +) -> bool { + let DiscoveredItem::Function { + final_name: expected_final_name, + } = expected_item + else { + unreachable!() + }; + + let DiscoveredItem::Function { + final_name: generated_final_name, + } = generated_item + else { + unreachable!() + }; + + if !compare_names(expected_final_name, generated_final_name) { + return false; + } + true +} + +pub fn compare_method_info( + expected_item: &DiscoveredItem, + generated_item: &DiscoveredItem, +) -> bool { + let DiscoveredItem::Method { + final_name: expected_final_name, + parent: expected_parent, + cpp_visibility: expected_cpp_visibility, + cpp_special_member: expected_cpp_special_member, + cpp_virtual: expected_cpp_virtual, + cpp_explicit: expected_cpp_explicit, + } = expected_item + else { + unreachable!() + }; + + let DiscoveredItem::Method { + final_name: generated_final_name, + parent: generated_parent, + cpp_visibility: generated_cpp_visibility, + cpp_special_member: generated_cpp_special_member, + cpp_virtual: generated_cpp_virtual, + cpp_explicit: generated_cpp_explicit, + } = generated_item + else { + unreachable!() + }; + + if expected_parent != generated_parent || + expected_cpp_explicit != generated_cpp_explicit || + expected_cpp_special_member != generated_cpp_special_member || + expected_cpp_virtual != generated_cpp_virtual || + expected_cpp_visibility != generated_cpp_visibility + { + return false; + } + + if !compare_names(expected_final_name, generated_final_name) { + return false; + } + true +} + +pub fn compare_mod_info( + expected_item: &DiscoveredItem, + generated_item: &DiscoveredItem, +) -> bool { + let DiscoveredItem::Mod { + final_name: expected_final_name, + anonymous: expected_anonymous, + inline: expected_inline, + } = expected_item + else { + unreachable!() + }; + + let DiscoveredItem::Mod { + final_name: generated_final_name, + anonymous: generated_anonymous, + inline: generated_inline, + } = generated_item + else { + unreachable!() + }; + + if expected_anonymous != generated_anonymous || + *expected_inline != *generated_inline + { + return false; + } + + // We generate arbitrary names for anonymous namespaces - do not compare + if !expected_anonymous { + // Do not use regexes to compare mod names since the root mod + // has an empty name and would match everything + if expected_final_name != generated_final_name { + return false; + } + } + true } diff --git a/bindgen/callbacks.rs b/bindgen/callbacks.rs index c2be66828a..44421f606a 100644 --- a/bindgen/callbacks.rs +++ b/bindgen/callbacks.rs @@ -1,8 +1,11 @@ //! A public API for more fine-grained customization of bindgen behavior. pub use crate::ir::analysis::DeriveTrait; +pub use crate::ir::comp::SpecialMemberKind; pub use crate::ir::derive::CanDerive as ImplementsTrait; pub use crate::ir::enum_ty::{EnumVariantCustomBehavior, EnumVariantValue}; +pub use crate::ir::function::Explicitness; +pub use crate::ir::function::Visibility; pub use crate::ir::int::IntKind; use std::fmt; @@ -163,8 +166,24 @@ pub trait ParseCallbacks: fmt::Debug { 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) {} + /// This will get called everytime an item is found with some information about it. + /// `_parent` is the location in which the item has been found, if any. + /// This is guaranteed to be a [`DiscoveredItem`] as reported + /// by [`ParseCallbacks::new_item_found`], most likely a + /// [`DiscoveredItem::Mod`] but perhaps something else such as a + /// [`DiscoveredItem::Struct`]. + /// If C++ namespace support has not been enabled in bindgen's options, + /// most items will have no declared `_parent`. If C++ namespace support + /// has been enabled, all items should have a parent other than the root + /// namespace. + fn new_item_found( + &self, + _id: DiscoveredItemId, + _item: DiscoveredItem, + _source_location: Option<&SourceLocation>, + _parent: Option, + ) { + } // TODO add callback for ResolvedTypeRef } @@ -192,6 +211,10 @@ pub enum DiscoveredItem { /// The name of the generated binding final_name: String, + + /// Its C++ visibility. [`Visibility::Public`] unless this is nested + /// in another type. + cpp_visibility: Visibility, }, /// Represents a union with its original name in C and its generated binding name @@ -202,6 +225,10 @@ pub enum DiscoveredItem { /// The name of the generated binding final_name: String, + + /// Its C++ visibility. [`Visibility::Public`] unless this is nested + /// in another type. + cpp_visibility: Visibility, }, /// Represents an alias like a typedef @@ -223,8 +250,55 @@ pub enum DiscoveredItem { Enum { /// The final name of the generated binding final_name: String, + + /// Its C++ visibility. [`Visibility::Public`] unless this is nested + /// in another type. + cpp_visibility: Visibility, + }, + + /// A module, representing a C++ namespace. + /// The root module can be identified by the fact that it has a `None` + /// parent declared within [`ParseCallbacks::new_item_found`]. + /// Inline namespaces won't be reported at all unless the + /// "enable conservative inline namespaces" option is enabled. + Mod { + /// The final name used. + final_name: String, + /// Whether this was originally an anonymous namespace. + /// bindgen will have assigned a name within `final_name`. + anonymous: bool, + /// Whether this is an inline namespace. + inline: bool, + }, + + /// A function or method. + Function { + /// The final name used. + final_name: String, + }, + + /// A method. + Method { + /// The final name used. + final_name: String, + + /// Type to which this method belongs. + parent: DiscoveredItemId, + + /// Its C++ visibility. + cpp_visibility: Visibility, + + /// Whether this is a C++ "special member". + cpp_special_member: Option, + + /// Whether this is a C++ virtual function. + cpp_virtual: Option, + + /// Whether this is a C++ function which has been marked + /// `=default` or `=deleted`. Note that deleted functions aren't + /// normally generated without special bindgen options. + cpp_explicit: Option, }, - // functions, modules, etc. } /// Relevant information about a type to which new derive attributes will be added using @@ -290,3 +364,26 @@ pub struct FieldInfo<'a> { /// The name of the type of the field. pub field_type_name: Option<&'a str>, } + +/// Whether a method is virtual or pure virtual. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Virtualness { + /// Not pure virtual. + Virtual, + /// Pure virtual. + PureVirtual, +} + +/// Location in the source code. Roughly equivalent to the same type +/// within `clang_sys`. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SourceLocation { + /// Line number. + pub line: usize, + /// Column number within line. + pub col: usize, + /// Byte offset within file. + pub byte_offset: usize, + /// Filename, if known. + pub file_name: Option, +} diff --git a/bindgen/clang.rs b/bindgen/clang.rs index 04fe3e1538..e97e8b6181 100644 --- a/bindgen/clang.rs +++ b/bindgen/clang.rs @@ -927,6 +927,21 @@ impl Cursor { unsafe { clang_isVirtualBase(self.x) != 0 } } + // Is this cursor's referent a default constructor? + pub fn is_default_constructor(&self) -> bool { + unsafe { clang_CXXConstructor_isDefaultConstructor(self.x) != 0 } + } + + // Is this cursor's referent a copy constructor? + pub fn is_copy_constructor(&self) -> bool { + unsafe { clang_CXXConstructor_isCopyConstructor(self.x) != 0 } + } + + // Is this cursor's referent a move constructor? + pub fn is_move_constructor(&self) -> bool { + unsafe { clang_CXXConstructor_isMoveConstructor(self.x) != 0 } + } + /// Try to evaluate this cursor. pub(crate) fn evaluate(&self) -> Option { EvalResult::new(*self) diff --git a/bindgen/codegen/mod.rs b/bindgen/codegen/mod.rs index f5518e432d..ed92cee1d6 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, + TypeKind as DeriveTypeKind, Virtualness, }; use crate::codegen::error::Error; use crate::ir::analysis::{HasVtable, Sizedness}; @@ -627,6 +627,19 @@ impl CodeGenerator for Module { } let name = item.canonical_name(ctx); + + utils::call_discovered_item_callback(ctx, item, || { + DiscoveredItem::Mod { + final_name: if item.id() == item.parent_id() { + "".to_string() // root module + } else { + name.clone() + }, + anonymous: self.name().is_none(), + inline: self.is_inline(), + } + }); + let ident = ctx.rust_ident(name); result.push(if item.id() == ctx.root_module() { quote! { @@ -995,16 +1008,13 @@ 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(), - ), - }, - ); + utils::call_discovered_item_callback(ctx, item, || { + 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) { @@ -2481,30 +2491,25 @@ 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, - ); + utils::call_discovered_item_callback(ctx, item, || match self.kind() { + CompKind::Struct => DiscoveredItem::Struct { + original_name: item + .kind() + .expect_type() + .name() + .map(String::from), + final_name: canonical_ident.to_string(), + cpp_visibility: self.visibility(), + }, + CompKind::Union => DiscoveredItem::Union { + original_name: item + .kind() + .expect_type() + .name() + .map(String::from), + final_name: canonical_ident.to_string(), + cpp_visibility: self.visibility(), + }, }); // The custom derives callback may return a list of derive attributes; @@ -2702,6 +2707,7 @@ impl CodeGenerator for CompInfo { } let mut method_names = Default::default(); + let discovered_id = DiscoveredItemId::new(item.id().as_usize()); if ctx.options().codegen_config.methods() { for method in self.methods() { assert_ne!(method.kind(), MethodKind::Constructor); @@ -2711,6 +2717,7 @@ impl CodeGenerator for CompInfo { &mut method_names, result, self, + discovered_id, ); } } @@ -2729,6 +2736,7 @@ impl CodeGenerator for CompInfo { &mut method_names, result, self, + discovered_id, ); } } @@ -2742,6 +2750,7 @@ impl CodeGenerator for CompInfo { &mut method_names, result, self, + discovered_id, ); } } @@ -2999,6 +3008,7 @@ impl Method { method_names: &mut HashSet, result: &mut CodegenResult<'_>, _parent: &CompInfo, + parent_id: DiscoveredItemId, ) { assert!({ let cc = &ctx.options().codegen_config; @@ -3065,6 +3075,29 @@ impl Method { method_names.insert(name.clone()); + utils::call_discovered_item_callback(ctx, function_item, || { + let cpp_virtual = match function.kind() { + FunctionKind::Function => None, + FunctionKind::Method(method_kind) => { + if method_kind.is_pure_virtual() { + Some(Virtualness::PureVirtual) + } else if method_kind.is_virtual() { + Some(Virtualness::Virtual) + } else { + None + } + } + }; + DiscoveredItem::Method { + parent: parent_id, + final_name: name.clone(), + cpp_visibility: function.visibility(), + cpp_special_member: function.special_member(), + cpp_virtual, + cpp_explicit: function.explicitness(), + } + }); + let mut function_name = function_item.canonical_name(ctx); if times_seen > 0 { write!(&mut function_name, "{times_seen}").unwrap(); @@ -3770,13 +3803,11 @@ impl CodeGenerator for Enum { let repr = repr.to_rust_ty_or_opaque(ctx, item); let has_typedef = ctx.is_enum_typedef_combo(item.id()); - ctx.options().for_each_callback(|cb| { - cb.new_item_found( - DiscoveredItemId::new(item.id().as_usize()), - DiscoveredItem::Enum { - final_name: name.to_string(), - }, - ); + utils::call_discovered_item_callback(ctx, item, || { + DiscoveredItem::Enum { + final_name: name.to_string(), + cpp_visibility: self.visibility, + } }); let mut builder = @@ -4650,6 +4681,11 @@ impl CodeGenerator for Function { if times_seen > 0 { write!(&mut canonical_name, "{times_seen}").unwrap(); } + utils::call_discovered_item_callback(ctx, item, || { + DiscoveredItem::Function { + final_name: canonical_name.to_string(), + } + }); let link_name_attr = self.link_name().or_else(|| { let mangled_name = mangled_name.unwrap_or(name); @@ -5189,10 +5225,12 @@ 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::ir::context::BindgenContext; use crate::ir::context::TypeId; use crate::ir::function::{Abi, ClangAbi, FunctionSig}; use crate::ir::item::{Item, ItemCanonicalPath}; + use crate::ir::item_kind::ItemKind; use crate::ir::ty::TypeKind; use crate::{args_are_cpp, file_is_cpp}; use std::borrow::Cow; @@ -5918,4 +5956,80 @@ pub(crate) mod utils { true } + + pub(super) fn call_discovered_item_callback( + ctx: &BindgenContext, + item: &Item, + discovered_item_creator: impl Fn() -> crate::callbacks::DiscoveredItem, + ) { + let source_location = item.location().map(|clang_location| { + let (file, line, col, byte_offset) = clang_location.location(); + let file_name = file.name(); + crate::callbacks::SourceLocation { + line, + col, + byte_offset, + file_name, + } + }); + + ctx.options().for_each_callback(|cb| { + cb.new_item_found( + DiscoveredItemId::new(item.id().as_usize()), + discovered_item_creator(), + source_location.as_ref(), + find_reportable_parent(ctx, item), + ); + }); + } + + /// Identify a suitable parent, the details of which will have + /// been passed to `ParseCallbacks`. We don't inform + /// [`crate::callbacks::ParseCallbacks::new_item_found`] + /// about everything - notably, not usually inline namespaces - and always + /// want to ensure that the `parent_id` we report within `new_item_found` + /// always corresponds to some other item which we'll have + /// told the client about. This function hops back through the ancestor + /// chain until it finds a reportable ID. + pub(super) fn find_reportable_parent( + ctx: &BindgenContext, + item: &Item, + ) -> Option { + // We choose never to report parents if C++ namespaces are not + // enabled. Sometimes a struct might be within another struct, but + // for now we simply don't report parentage at all. + if !ctx.options().enable_cxx_namespaces { + return None; + } + let mut parent_item = ctx.resolve_item(item.parent_id()); + while !is_reportable_parent(ctx, &parent_item) { + let parent_id = parent_item.parent_id(); + if parent_id == parent_item.id() { + return None; + } + parent_item = ctx.resolve_item(parent_id); + } + if parent_item.id() == item.id() { + // This is itself the root module. + None + } else { + Some(DiscoveredItemId::new(parent_item.id().as_usize())) + } + } + + /// Returns whether a given [`Item`] will have been reported, or will + /// be reported, in [`crate::callbacks::ParseCallbacks::new_item_found`]. + fn is_reportable_parent(ctx: &BindgenContext, item: &Item) -> bool { + match item.kind() { + ItemKind::Module(ref module) => { + !module.is_inline() || + ctx.options().conservative_inline_namespaces + } + ItemKind::Type(t) => match t.kind() { + TypeKind::Comp(..) | TypeKind::Enum(..) => true, + _ => false, + }, + _ => false, + } + } } diff --git a/bindgen/ir/comp.rs b/bindgen/ir/comp.rs index 15f9cb4655..15d91328ed 100644 --- a/bindgen/ir/comp.rs +++ b/bindgen/ir/comp.rs @@ -6,6 +6,7 @@ use super::analysis::Sizedness; use super::annotations::Annotations; use super::context::{BindgenContext, FunctionId, ItemId, TypeId, VarId}; use super::dot::DotAttributes; +use super::function::Visibility; use super::item::{IsOpaque, Item}; use super::layout::Layout; use super::template::TemplateParameters; @@ -63,6 +64,14 @@ impl MethodKind { ) } + /// Is this virtual (pure or otherwise?) + pub(crate) fn is_virtual(self) -> bool { + matches!( + self, + MethodKind::Virtual { .. } | MethodKind::VirtualDestructor { .. } + ) + } + /// Is this a pure virtual method? pub(crate) fn is_pure_virtual(self) -> bool { match self { @@ -73,6 +82,23 @@ impl MethodKind { } } +/// The kind of C++ special member. +// TODO: We don't currently cover copy assignment or move assignment operator +// because libclang doesn't provide a way to query for them. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum SpecialMemberKind { + /// The default constructor. + DefaultConstructor, + /// A copy constructor. + CopyConstructor, + /// A move constructor. + MoveConstructor, + /// A destructor. + Destructor, + /// The assignment operator. + AssignmentOperator, +} + /// A struct representing a C++ method, either static, normal, or virtual. #[derive(Debug)] pub(crate) struct Method { @@ -993,6 +1019,10 @@ pub(crate) struct CompInfo { /// Whether this is a struct or a union. kind: CompKind, + /// The visibility of this struct or union if it was declared inside of + /// another type. Top-level types always have public visibility. + visibility: Visibility, + /// The members of this struct or union. fields: CompFields, @@ -1074,6 +1104,7 @@ impl CompInfo { pub(crate) fn new(kind: CompKind) -> Self { CompInfo { kind, + visibility: Visibility::Public, fields: CompFields::default(), template_params: vec![], methods: vec![], @@ -1193,6 +1224,11 @@ impl CompInfo { } } + /// Returns the visibility of the type. + pub fn visibility(&self) -> Visibility { + self.visibility + } + /// Returns whether we have a too large bitfield unit, in which case we may /// not be able to derive some of the things we should be able to normally /// derive. @@ -1282,6 +1318,7 @@ impl CompInfo { debug!("CompInfo::from_ty({kind:?}, {cursor:?})"); let mut ci = CompInfo::new(kind); + ci.visibility = Visibility::from(cursor.access_specifier()); ci.is_forward_declaration = location.map_or(true, |cur| match cur.kind() { CXCursor_ParmDecl => true, diff --git a/bindgen/ir/enum_ty.rs b/bindgen/ir/enum_ty.rs index 9b08da3bce..3d3b713047 100644 --- a/bindgen/ir/enum_ty.rs +++ b/bindgen/ir/enum_ty.rs @@ -2,6 +2,7 @@ use super::super::codegen::EnumVariation; use super::context::{BindgenContext, TypeId}; +use super::function::Visibility; use super::item::Item; use super::ty::{Type, TypeKind}; use crate::clang; @@ -32,6 +33,10 @@ pub(crate) struct Enum { /// The different variants, with explicit values. variants: Vec, + + /// The visibility of this enum if it was declared inside of + /// another type. Top-level types always have public visibility. + pub(crate) visibility: Visibility, } impl Enum { @@ -39,8 +44,13 @@ impl Enum { pub(crate) fn new( repr: Option, variants: Vec, + visibility: Visibility, ) -> Self { - Enum { repr, variants } + Enum { + repr, + variants, + visibility, + } } /// Get this enumeration's representation. @@ -56,6 +66,7 @@ impl Enum { /// Construct an enumeration from the given Clang type. pub(crate) fn from_ty( ty: &clang::Type, + visibility: Visibility, ctx: &mut BindgenContext, ) -> Result { use clang_sys::*; @@ -147,7 +158,7 @@ impl Enum { } CXChildVisit_Continue }); - Ok(Enum::new(repr, variants)) + Ok(Enum::new(repr, variants, visibility)) } fn is_matching_enum( diff --git a/bindgen/ir/function.rs b/bindgen/ir/function.rs index 83b748a5f4..df1f97912d 100644 --- a/bindgen/ir/function.rs +++ b/bindgen/ir/function.rs @@ -1,6 +1,6 @@ //! Intermediate representation for C/C++ functions and methods. -use super::comp::MethodKind; +use super::comp::{MethodKind, SpecialMemberKind}; use super::context::{BindgenContext, TypeId}; use super::dot::DotAttributes; use super::item::Item; @@ -9,7 +9,9 @@ use super::ty::TypeKind; use crate::callbacks::{ItemInfo, ItemKind}; use crate::clang::{self, ABIKind, Attribute}; use crate::parse::{ClangSubItemParser, ParseError, ParseResult}; -use clang_sys::CXCallingConv; +use clang_sys::{ + CXCallingConv, CX_CXXAccessSpecifier, CX_CXXPrivate, CX_CXXProtected, +}; use quote::TokenStreamExt; use std::io; @@ -70,6 +72,38 @@ pub(crate) enum Linkage { Internal, } +/// C++ visibility. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Visibility { + /// `public` visibility. + Public, + /// `protected` visibility. + Protected, + /// `private` visibility. + Private, +} + +impl From for Visibility { + fn from(access_specifier: CX_CXXAccessSpecifier) -> Self { + if access_specifier == CX_CXXPrivate { + Visibility::Private + } else if access_specifier == CX_CXXProtected { + Visibility::Protected + } else { + Visibility::Public + } + } +} + +/// Whether a C++ method has been explicitly defaulted or deleted. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Explicitness { + /// Defaulted function, i.e. `=default` + Defaulted, + /// Deleted function, i.e. `=delete` + Deleted, +} + /// A function declaration, with a signature, arguments, and argument names. /// /// The argument names vector must be the same length as the ones in the @@ -93,6 +127,15 @@ pub(crate) struct Function { /// The linkage of the function. linkage: Linkage, + + /// C++ Special member kind, if applicable + special_member: Option, + + /// Whether it is private + visibility: Visibility, + + // Whether it is `=delete` or `=default` + explicitness: Option, } impl Function { @@ -104,6 +147,9 @@ impl Function { signature: TypeId, kind: FunctionKind, linkage: Linkage, + special_member: Option, + visibility: Visibility, + explicitness: Option, ) -> Self { Function { name, @@ -112,6 +158,9 @@ impl Function { signature, kind, linkage, + special_member, + visibility, + explicitness, } } @@ -144,6 +193,22 @@ impl Function { pub(crate) fn linkage(&self) -> Linkage { self.linkage } + + /// Get this function's C++ special member kind. + pub fn special_member(&self) -> Option { + self.special_member + } + + /// Whether it is private, protected or public + pub fn visibility(&self) -> Visibility { + self.visibility + } + + /// Whether this is a function that's been deleted (=delete) + /// or defaulted (=default) + pub fn explicitness(&self) -> Option { + self.explicitness + } } impl DotAttributes for Function { @@ -736,6 +801,8 @@ impl ClangSubItemParser for Function { return Err(ParseError::Continue); } + let visibility = Visibility::from(cursor.access_specifier()); + let linkage = cursor.linkage(); let linkage = match linkage { CXLinkage_External | CXLinkage_UniqueExternal => Linkage::External, @@ -803,6 +870,26 @@ impl ClangSubItemParser for Function { }) }); + let special_member = if cursor.is_default_constructor() { + Some(SpecialMemberKind::DefaultConstructor) + } else if cursor.is_copy_constructor() { + Some(SpecialMemberKind::CopyConstructor) + } else if cursor.is_move_constructor() { + Some(SpecialMemberKind::MoveConstructor) + } else if cursor.kind() == CXCursor_Destructor { + Some(SpecialMemberKind::Destructor) + } else { + None + }; + + let explicitness = if cursor.is_deleted_function() { + Some(Explicitness::Deleted) + } else if cursor.is_defaulted_function() { + Some(Explicitness::Defaulted) + } else { + None + }; + let function = Self::new( name.clone(), mangled_name, @@ -810,6 +897,9 @@ impl ClangSubItemParser for Function { sig, kind, linkage, + special_member, + visibility, + explicitness, ); Ok(ParseResult::New(function, Some(cursor))) diff --git a/bindgen/ir/ty.rs b/bindgen/ir/ty.rs index 2589a56fca..42c25cea8f 100644 --- a/bindgen/ir/ty.rs +++ b/bindgen/ir/ty.rs @@ -13,6 +13,7 @@ use super::template::{ }; use super::traversal::{EdgeKind, Trace, Tracer}; use crate::clang::{self, Cursor}; +use crate::ir::function::Visibility; use crate::parse::{ParseError, ParseResult}; use std::borrow::Cow; use std::io; @@ -1086,7 +1087,10 @@ impl Type { } } CXType_Enum => { - let enum_ = Enum::from_ty(ty, ctx).expect("Not an enum?"); + let visibility = + Visibility::from(cursor.access_specifier()); + let enum_ = Enum::from_ty(ty, visibility, ctx) + .expect("Not an enum?"); if !is_anonymous { let pretty_name = ty.spelling();