From 6e906324b0034a030269abf08047354c547b3c77 Mon Sep 17 00:00:00 2001 From: Adrian Taylor Date: Fri, 21 Feb 2025 14:35:32 +0000 Subject: [PATCH] Report C++ visibility and other details. This change reports extra C++ information about items: * Whether methods are virtual or pure virtual or neither * Whether a method is a "special member", e.g. a move constructor * Whether a method is defaulted or deleted * C++ visibility (for structs, enums, unions and methods) It builds on top of #3145. This PR is not yet ready for merge, since we need to merge #3139 and then enhance the tests to cover those cases too. Part of /~https://github.com/google/autocxx/issues/124 --- .../header_item_discovery.hpp | 10 ++ .../item_discovery_callback/mod.rs | 92 +++++++++++++++++- bindgen/callbacks.rs | 38 ++++++++ bindgen/clang.rs | 15 +++ bindgen/codegen/mod.rs | 20 +++- bindgen/ir/comp.rs | 37 ++++++++ bindgen/ir/enum_ty.rs | 15 ++- bindgen/ir/function.rs | 94 ++++++++++++++++++- bindgen/ir/ty.rs | 6 +- 9 files changed, 315 insertions(+), 12 deletions(-) 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 index 1de8d99e4d..0fb70d9396 100644 --- 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 @@ -2,5 +2,15 @@ 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/mod.rs b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/mod.rs index 6659be62b9..63066ab590 100644 --- a/bindgen-tests/tests/parse_callbacks/item_discovery_callback/mod.rs +++ b/bindgen-tests/tests/parse_callbacks/item_discovery_callback/mod.rs @@ -7,6 +7,7 @@ use regex::Regex; use bindgen::callbacks::{ DiscoveredItem, DiscoveredItemId, ParseCallbacks, SourceLocation, + SpecialMemberKind, Visibility, }; use bindgen::Builder; @@ -101,6 +102,7 @@ fn test_item_discovery_callback_c() { DiscoveredItem::Struct { original_name: Some("NamedStruct".to_string()), final_name: "NamedStruct".to_string(), + cpp_visibility: Visibility::Public, }, 4, 8, @@ -127,6 +129,7 @@ fn test_item_discovery_callback_c() { DiscoveredItem::Union { original_name: Some("NamedUnion".to_string()), final_name: "NamedUnion".to_string(), + cpp_visibility: Visibility::Public, }, 13, 7, @@ -165,6 +168,7 @@ fn test_item_discovery_callback_c() { ItemExpectations::new( DiscoveredItem::Enum { final_name: "NamedEnum".to_string(), + cpp_visibility: Visibility::Public, }, 24, 6, @@ -178,6 +182,7 @@ fn test_item_discovery_callback_c() { DiscoveredItem::Struct { original_name: None, final_name: "_bindgen_ty_*".to_string(), + cpp_visibility: Visibility::Public, }, 2, 38, @@ -191,6 +196,7 @@ fn test_item_discovery_callback_c() { DiscoveredItem::Union { original_name: None, final_name: "_bindgen_ty_*".to_string(), + cpp_visibility: Visibility::Public, }, 11, 37, @@ -216,7 +222,7 @@ fn test_item_discovery_callback_c() { } #[test] -fn test_item_discovery_callback_cpp() { +fn test_item_discovery_callback_cpp_features() { let expected = ExpectationMap::from([ ( DiscoveredItemId::new(1), @@ -224,6 +230,7 @@ fn test_item_discovery_callback_cpp() { DiscoveredItem::Struct { original_name: Some("SomeClass".to_string()), final_name: "SomeClass".to_string(), + cpp_visibility: Visibility::Public, }, 3, 7, @@ -237,16 +244,57 @@ fn test_item_discovery_callback_cpp() { 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, }, - 5, + 8, 10, - 47, + 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, ), ), ]); + test_item_discovery_callback( - "/tests/parse_callbacks/item_discovery_callback/header_item_discovery.hpp", expected, identity); + "/tests/parse_callbacks/item_discovery_callback/header_item_discovery.hpp", expected, |b| b.clang_arg("--std=c++11")); } /// Returns the expectations corresponding to header_item_discovery_with_namespaces.hpp, @@ -361,6 +409,7 @@ fn cpp_expectation_map() -> ExpectationMap { DiscoveredItem::Struct { final_name: "L".to_string(), original_name: Some("L".to_string()), + cpp_visibility: Visibility::Public, }, 25, 12, @@ -374,6 +423,7 @@ fn cpp_expectation_map() -> ExpectationMap { DiscoveredItem::Struct { final_name: "L_M".to_string(), original_name: Some("M".to_string()), + cpp_visibility: Visibility::Public, }, 26, 16, @@ -597,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!() @@ -605,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!() @@ -614,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)) => { @@ -630,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!() @@ -638,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!() @@ -647,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)) => { @@ -662,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!() @@ -669,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!() @@ -677,6 +741,11 @@ 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 } @@ -755,6 +824,10 @@ pub fn compare_method_info( 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!() @@ -763,12 +836,21 @@ pub fn compare_method_info( 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 { + 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; } diff --git a/bindgen/callbacks.rs b/bindgen/callbacks.rs index 5cbc1e2b17..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; @@ -208,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 @@ -218,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 @@ -239,6 +250,10 @@ 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. @@ -269,6 +284,20 @@ pub enum DiscoveredItem { /// 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, }, } @@ -336,6 +365,15 @@ pub struct FieldInfo<'a> { 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)] 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 4fdc4aa094..68f4b714f8 100644 --- a/bindgen/codegen/mod.rs +++ b/bindgen/codegen/mod.rs @@ -21,8 +21,7 @@ use self::struct_layout::StructLayoutTracker; use super::BindgenOptions; use crate::callbacks::{ - AttributeInfo, DeriveInfo, DiscoveredItem, DiscoveredItemId, FieldInfo, - TypeKind as DeriveTypeKind, + AttributeInfo, DeriveInfo, DiscoveredItem, DiscoveredItemId, FieldInfo, TypeKind as DeriveTypeKind, Virtualness }; use crate::codegen::error::Error; use crate::ir::analysis::{HasVtable, Sizedness}; @@ -2499,6 +2498,7 @@ impl CodeGenerator for CompInfo { .name() .map(String::from), final_name: canonical_ident.to_string(), + cpp_visibility: self.visibility(), }, CompKind::Union => DiscoveredItem::Union { original_name: item @@ -2507,6 +2507,7 @@ impl CodeGenerator for CompInfo { .name() .map(String::from), final_name: canonical_ident.to_string(), + cpp_visibility: self.visibility(), }, }); @@ -3074,9 +3075,23 @@ 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(), } }); @@ -3788,6 +3803,7 @@ impl CodeGenerator for Enum { utils::call_discovered_item_callback(ctx, item, || { DiscoveredItem::Enum { final_name: name.to_string(), + cpp_visibility: self.visibility, } }); 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();