From 2500102be760e30e82364608e3fb5c1def6636cc Mon Sep 17 00:00:00 2001 From: Mason Reed Date: Thu, 11 Dec 2025 14:45:23 -0500 Subject: [PATCH 1/4] [Rust] Restructure type APIs into `types` module This helps with documentation, giving a single module for those working with types to find related APIs Also split out enumeration and structure APIs into their own file, since they have their own backing data separate from `Type`. --- .gitignore | 1 + rust/examples/bndb_to_type_library.rs | 3 +- rust/examples/create_type_library.rs | 3 +- rust/examples/dump_type_library.rs | 4 +- rust/examples/type_printer.rs | 2 +- rust/src/binary_view.rs | 11 +- rust/src/collaboration/sync.rs | 2 +- rust/src/component.rs | 23 +- rust/src/data_notification.rs | 3 +- rust/src/language_representation.rs | 3 +- rust/src/lib.rs | 5 - rust/src/platform.rs | 8 +- rust/src/string.rs | 9 - rust/src/types.rs | 923 +----------------- .../src/{type_archive.rs => types/archive.rs} | 15 +- .../{type_container.rs => types/container.rs} | 3 +- rust/src/types/enumeration.rs | 201 ++++ .../src/{type_library.rs => types/library.rs} | 0 rust/src/{type_parser.rs => types/parser.rs} | 3 +- .../src/{type_printer.rs => types/printer.rs} | 3 +- rust/src/types/structure.rs | 696 +++++++++++++ rust/tests/type_archive.rs | 3 +- rust/tests/type_library.rs | 3 +- rust/tests/type_parser.rs | 3 +- rust/tests/type_printer.rs | 8 +- 25 files changed, 982 insertions(+), 956 deletions(-) rename rust/src/{type_archive.rs => types/archive.rs} (99%) rename rust/src/{type_container.rs => types/container.rs} (99%) create mode 100644 rust/src/types/enumeration.rs rename rust/src/{type_library.rs => types/library.rs} (100%) rename rust/src/{type_parser.rs => types/parser.rs} (99%) rename rust/src/{type_printer.rs => types/printer.rs} (99%) create mode 100644 rust/src/types/structure.rs diff --git a/.gitignore b/.gitignore index 02e5388cc7..e838baf529 100644 --- a/.gitignore +++ b/.gitignore @@ -90,6 +90,7 @@ rust/examples/*/target/ rust/examples/dwarf/*/target/ # Allow the test binaries !/rust/fixtures/bin/** +!/rust/src/types # Debugger docs docs/img/debugger diff --git a/rust/examples/bndb_to_type_library.rs b/rust/examples/bndb_to_type_library.rs index 40a184cabb..84515cf7da 100644 --- a/rust/examples/bndb_to_type_library.rs +++ b/rust/examples/bndb_to_type_library.rs @@ -1,8 +1,7 @@ // Usage: cargo run --example bndb_to_type_library use binaryninja::binary_view::BinaryViewExt; -use binaryninja::type_library::TypeLibrary; -use binaryninja::types::QualifiedName; +use binaryninja::types::{QualifiedName, TypeLibrary}; fn main() { let bndb_path_str = std::env::args().nth(1).expect("No header provided"); diff --git a/rust/examples/create_type_library.rs b/rust/examples/create_type_library.rs index 35e9b11c0e..74d93765af 100644 --- a/rust/examples/create_type_library.rs +++ b/rust/examples/create_type_library.rs @@ -1,8 +1,7 @@ // Usage: cargo run --example create_type_library use binaryninja::platform::Platform; -use binaryninja::type_library::TypeLibrary; -use binaryninja::type_parser::{CoreTypeParser, TypeParser}; +use binaryninja::types::{CoreTypeParser, TypeLibrary, TypeParser}; fn main() { let header_path_str = std::env::args().nth(1).expect("No header provided"); diff --git a/rust/examples/dump_type_library.rs b/rust/examples/dump_type_library.rs index 34ca1baf4e..f7398d28eb 100644 --- a/rust/examples/dump_type_library.rs +++ b/rust/examples/dump_type_library.rs @@ -2,8 +2,8 @@ use binaryninja::binary_view::BinaryView; use binaryninja::file_metadata::FileMetadata; -use binaryninja::type_library::TypeLibrary; -use binaryninja::type_printer::{CoreTypePrinter, TokenEscapingType}; +use binaryninja::types::library::TypeLibrary; +use binaryninja::types::printer::{CoreTypePrinter, TokenEscapingType}; fn main() { let type_lib_str = std::env::args().nth(1).expect("No type library provided"); diff --git a/rust/examples/type_printer.rs b/rust/examples/type_printer.rs index 846e1a0908..b88a023304 100644 --- a/rust/examples/type_printer.rs +++ b/rust/examples/type_printer.rs @@ -1,4 +1,4 @@ -use binaryninja::type_printer::{CoreTypePrinter, TokenEscapingType}; +use binaryninja::types::printer::{CoreTypePrinter, TokenEscapingType}; use binaryninja::types::{MemberAccess, MemberScope, Structure, StructureMember, Type}; fn main() { diff --git a/rust/src/binary_view.rs b/rust/src/binary_view.rs index a48d0264b2..459253b9dc 100644 --- a/rust/src/binary_view.rs +++ b/rust/src/binary_view.rs @@ -30,10 +30,12 @@ pub use crate::workflow::AnalysisContext; use crate::architecture::{Architecture, CoreArchitecture}; use crate::base_detection::BaseAddressDetection; use crate::basic_block::BasicBlock; +use crate::binary_view::search::SearchQuery; use crate::component::Component; use crate::confidence::Conf; use crate::data_buffer::DataBuffer; use crate::debuginfo::DebugInfo; +use crate::disassembly::DisassemblySettings; use crate::external_library::{ExternalLibrary, ExternalLocation}; use crate::file_accessor::{Accessor, FileAccessor}; use crate::file_metadata::FileMetadata; @@ -53,12 +55,12 @@ use crate::settings::Settings; use crate::string::*; use crate::symbol::{Symbol, SymbolType}; use crate::tags::{Tag, TagType}; -use crate::type_container::TypeContainer; -use crate::type_library::TypeLibrary; use crate::types::{ NamedTypeReference, QualifiedName, QualifiedNameAndType, QualifiedNameTypeAndId, Type, + TypeArchive, TypeArchiveId, TypeContainer, TypeLibrary, }; use crate::variable::DataVariable; +use crate::workflow::Workflow; use crate::{Endianness, BN_FULL_CONFIDENCE}; use std::collections::HashMap; use std::ffi::{c_char, c_void, CString}; @@ -67,17 +69,12 @@ use std::ops::Range; use std::path::{Path, PathBuf}; use std::ptr::NonNull; use std::{result, slice}; -// TODO : general reorg of modules related to bv pub mod memory_map; pub mod reader; pub mod search; pub mod writer; -use crate::binary_view::search::SearchQuery; -use crate::disassembly::DisassemblySettings; -use crate::type_archive::{TypeArchive, TypeArchiveId}; -use crate::workflow::Workflow; pub use memory_map::MemoryMap; pub use reader::BinaryReader; pub use writer::BinaryWriter; diff --git a/rust/src/collaboration/sync.rs b/rust/src/collaboration/sync.rs index a8006b8b3c..72c57a46c6 100644 --- a/rust/src/collaboration/sync.rs +++ b/rust/src/collaboration/sync.rs @@ -13,7 +13,7 @@ use crate::progress::{NoProgressCallback, ProgressCallback}; use crate::project::file::ProjectFile; use crate::rc::Ref; use crate::string::{raw_to_string, BnString, IntoCStr}; -use crate::type_archive::{TypeArchive, TypeArchiveMergeConflict}; +use crate::types::archive::{TypeArchive, TypeArchiveMergeConflict}; /// Get the default directory path for a remote Project. This is based off the Setting for /// collaboration.directory, the project's id, and the project's remote's id. diff --git a/rust/src/component.rs b/rust/src/component.rs index 4c5d5992d2..f038d5c39c 100644 --- a/rust/src/component.rs +++ b/rust/src/component.rs @@ -2,7 +2,7 @@ use crate::binary_view::{BinaryView, BinaryViewExt}; use crate::function::Function; use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable}; use crate::string::{BnString, IntoCStr}; -use crate::types::ComponentReferencedType; +use crate::types::Type; use std::ffi::c_char; use std::fmt::Debug; use std::ptr::NonNull; @@ -304,3 +304,24 @@ unsafe impl CoreArrayProviderInner for Component { Guard::new(Self::from_raw(raw_ptr), context) } } + +// TODO: Remove this struct, or make it not a ZST with a terrible array provider. +/// ZST used only for `Array`. +pub struct ComponentReferencedType; + +impl CoreArrayProvider for ComponentReferencedType { + type Raw = *mut BNType; + type Context = (); + type Wrapped<'a> = &'a Type; +} + +unsafe impl CoreArrayProviderInner for ComponentReferencedType { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNComponentFreeReferencedTypes(raw, count) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + // SAFETY: &*mut BNType == &Type (*mut BNType == Type) + std::mem::transmute(raw) + } +} diff --git a/rust/src/data_notification.rs b/rust/src/data_notification.rs index 7749e79246..d01a5ed32a 100644 --- a/rust/src/data_notification.rs +++ b/rust/src/data_notification.rs @@ -15,8 +15,7 @@ use crate::section::Section; use crate::segment::Segment; use crate::symbol::Symbol; use crate::tags::{TagReference, TagType}; -use crate::type_archive::TypeArchive; -use crate::types::{QualifiedName, Type}; +use crate::types::{QualifiedName, Type, TypeArchive}; use crate::variable::DataVariable; macro_rules! trait_handler { diff --git a/rust/src/language_representation.rs b/rust/src/language_representation.rs index 34524b96b7..d39cb95f10 100644 --- a/rust/src/language_representation.rs +++ b/rust/src/language_representation.rs @@ -14,8 +14,7 @@ use crate::high_level_il::{HighLevelExpressionIndex, HighLevelILFunction}; use crate::line_formatter::CoreLineFormatter; use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref, RefCountable}; use crate::string::{BnString, IntoCStr}; -use crate::type_parser::CoreTypeParser; -use crate::type_printer::CoreTypePrinter; +use crate::types::{CoreTypeParser, CoreTypePrinter}; pub type InstructionTextTokenContext = BNInstructionTextTokenContext; pub type ScopeType = BNScopeType; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index f316603345..2d9cab27d8 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -81,11 +81,6 @@ pub mod string; pub mod symbol; pub mod tags; pub mod template_simplifier; -pub mod type_archive; -pub mod type_container; -pub mod type_library; -pub mod type_parser; -pub mod type_printer; pub mod types; pub mod update; pub mod variable; diff --git a/rust/src/platform.rs b/rust/src/platform.rs index 609f4edc2c..ed4f49f48d 100644 --- a/rust/src/platform.rs +++ b/rust/src/platform.rs @@ -14,15 +14,15 @@ //! Contains all information related to the execution environment of the binary, mainly the calling conventions used -use crate::type_container::TypeContainer; -use crate::type_parser::{TypeParserError, TypeParserErrorSeverity, TypeParserResult}; use crate::{ architecture::{Architecture, CoreArchitecture}, calling_convention::CoreCallingConvention, rc::*, string::*, - type_library::TypeLibrary, - types::QualifiedNameAndType, + types::{ + QualifiedNameAndType, TypeContainer, TypeLibrary, TypeParserError, TypeParserErrorSeverity, + TypeParserResult, + }, }; use binaryninjacore_sys::*; use std::fmt::Debug; diff --git a/rust/src/string.rs b/rust/src/string.rs index c7d541ce2a..8505cbf5f5 100644 --- a/rust/src/string.rs +++ b/rust/src/string.rs @@ -24,7 +24,6 @@ use std::ops::Deref; use std::path::{Path, PathBuf}; use crate::rc::*; -use crate::type_archive::TypeArchiveSnapshotId; use crate::types::QualifiedName; // TODO: Remove or refactor this. @@ -288,14 +287,6 @@ impl IntoCStr for &Path { } } -impl IntoCStr for TypeArchiveSnapshotId { - type Result = CString; - - fn to_cstr(self) -> Self::Result { - self.to_string().to_cstr() - } -} - pub trait IntoJson { type Output: IntoCStr; diff --git a/rust/src/types.rs b/rust/src/types.rs index 1387ffb32e..cfdc865bd5 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -11,16 +11,19 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -#![allow(unused)] -// TODO : More widely enforce the use of ref_from_raw vs just from_raw to simplify internal binding usage? Perhaps remove from_raw functions? -// TODO : Add documentation and fix examples -// TODO : Test the get_enumeration and get_structure methods +pub mod archive; +pub mod container; +pub mod enumeration; +pub mod library; +pub mod parser; +pub mod printer; +pub mod structure; use binaryninjacore_sys::*; use crate::{ - architecture::{Architecture, CoreArchitecture}, + architecture::Architecture, binary_view::{BinaryView, BinaryViewExt}, calling_convention::CoreCallingConvention, rc::*, @@ -29,19 +32,30 @@ use crate::{ use crate::confidence::{Conf, MAX_CONFIDENCE, MIN_CONFIDENCE}; use crate::string::{raw_to_string, strings_to_string_list}; -use crate::type_container::TypeContainer; use crate::variable::{Variable, VariableSourceType}; use std::borrow::Cow; use std::num::NonZeroUsize; use std::ops::{Index, IndexMut}; use std::{ collections::HashSet, - ffi::CStr, fmt::{Debug, Display, Formatter}, hash::{Hash, Hasher}, iter::IntoIterator, }; +pub use archive::{TypeArchive, TypeArchiveId, TypeArchiveSnapshotId}; +pub use container::TypeContainer; +pub use enumeration::{Enumeration, EnumerationBuilder, EnumerationMember}; +pub use library::TypeLibrary; +pub use parser::{ + CoreTypeParser, ParsedType, TypeParser, TypeParserError, TypeParserErrorSeverity, + TypeParserResult, +}; +pub use printer::{CoreTypePrinter, TypePrinter}; +pub use structure::{ + BaseStructure, InheritedStructureMember, Structure, StructureBuilder, StructureMember, +}; + pub type StructureType = BNStructureVariant; pub type ReferenceType = BNReferenceType; pub type TypeClass = BNTypeClass; @@ -970,7 +984,7 @@ impl Debug for Type { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { // You might be tempted to rip this atrocity out and make this more "sensible". READ BELOW! // Type is a one-size fits all structure, these are actually its fields! If we wanted to - // omit some fields for different type classes what you really want to do is implement your + // omit some fields for different type classes, what you really want to do is implement your // own formatter. This is supposed to represent the structure entirely, it's not supposed to be pretty! f.debug_struct("Type") .field("type_class", &self.type_class()) @@ -1048,27 +1062,6 @@ unsafe impl CoreArrayProviderInner for Type { } } -// TODO: Remove this struct, or make it not a ZST with a terrible array provider. -/// ZST used only for `Array`. -pub struct ComponentReferencedType; - -impl CoreArrayProvider for ComponentReferencedType { - type Raw = *mut BNType; - type Context = (); - type Wrapped<'a> = &'a Type; -} - -unsafe impl CoreArrayProviderInner for ComponentReferencedType { - unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { - BNComponentFreeReferencedTypes(raw, count) - } - - unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { - // SAFETY: &*mut BNType == &Type (*mut BNType == Type) - std::mem::transmute(raw) - } -} - #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct FunctionParameter { pub ty: Conf>, @@ -1105,6 +1098,7 @@ impl FunctionParameter { } } + #[allow(unused)] pub(crate) fn from_owned_raw(value: BNFunctionParameter) -> Self { let owned = Self::from_raw(&value); Self::free_raw(value); @@ -1136,875 +1130,6 @@ impl FunctionParameter { } } -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct EnumerationMember { - pub name: String, - /// The associated constant value for the member. - pub value: u64, - /// Whether this is the default member for the associated [`Enumeration`]. - pub default: bool, -} - -impl EnumerationMember { - pub(crate) fn from_raw(value: &BNEnumerationMember) -> Self { - Self { - name: raw_to_string(value.name).unwrap(), - value: value.value, - default: value.isDefault, - } - } - - pub(crate) fn from_owned_raw(value: BNEnumerationMember) -> Self { - let owned = Self::from_raw(&value); - Self::free_raw(value); - owned - } - - pub(crate) fn into_raw(value: Self) -> BNEnumerationMember { - let bn_name = BnString::new(value.name); - BNEnumerationMember { - name: BnString::into_raw(bn_name), - value: value.value, - isDefault: value.default, - } - } - - pub(crate) fn free_raw(value: BNEnumerationMember) { - unsafe { BnString::free_raw(value.name) }; - } - - pub fn new(name: String, value: u64, default: bool) -> Self { - Self { - name, - value, - default, - } - } -} - -#[derive(PartialEq, Eq, Hash)] -pub struct EnumerationBuilder { - pub(crate) handle: *mut BNEnumerationBuilder, -} - -impl EnumerationBuilder { - pub fn new() -> Self { - Self { - handle: unsafe { BNCreateEnumerationBuilder() }, - } - } - - pub(crate) unsafe fn from_raw(handle: *mut BNEnumerationBuilder) -> Self { - Self { handle } - } - - pub fn finalize(&self) -> Ref { - unsafe { Enumeration::ref_from_raw(BNFinalizeEnumerationBuilder(self.handle)) } - } - - pub fn append(&mut self, name: &str) -> &mut Self { - let name = name.to_cstr(); - unsafe { - BNAddEnumerationBuilderMember(self.handle, name.as_ref().as_ptr() as _); - } - self - } - - pub fn insert(&mut self, name: &str, value: u64) -> &mut Self { - let name = name.to_cstr(); - unsafe { - BNAddEnumerationBuilderMemberWithValue(self.handle, name.as_ref().as_ptr() as _, value); - } - self - } - - pub fn replace(&mut self, id: usize, name: &str, value: u64) -> &mut Self { - let name = name.to_cstr(); - unsafe { - BNReplaceEnumerationBuilderMember(self.handle, id, name.as_ref().as_ptr() as _, value); - } - self - } - - pub fn remove(&mut self, id: usize) -> &mut Self { - unsafe { - BNRemoveEnumerationBuilderMember(self.handle, id); - } - - self - } - - pub fn members(&self) -> Vec { - unsafe { - let mut count = 0; - let members_raw_ptr = BNGetEnumerationBuilderMembers(self.handle, &mut count); - let members_raw: &[BNEnumerationMember] = - std::slice::from_raw_parts(members_raw_ptr, count); - let members = members_raw - .iter() - .map(EnumerationMember::from_raw) - .collect(); - BNFreeEnumerationMemberList(members_raw_ptr, count); - members - } - } -} - -impl Default for EnumerationBuilder { - fn default() -> Self { - Self::new() - } -} - -impl From<&Enumeration> for EnumerationBuilder { - fn from(enumeration: &Enumeration) -> Self { - unsafe { - Self::from_raw(BNCreateEnumerationBuilderFromEnumeration( - enumeration.handle, - )) - } - } -} - -impl Drop for EnumerationBuilder { - fn drop(&mut self) { - unsafe { BNFreeEnumerationBuilder(self.handle) }; - } -} - -#[derive(PartialEq, Eq, Hash)] -pub struct Enumeration { - pub(crate) handle: *mut BNEnumeration, -} - -impl Enumeration { - pub(crate) unsafe fn ref_from_raw(handle: *mut BNEnumeration) -> Ref { - debug_assert!(!handle.is_null()); - Ref::new(Self { handle }) - } - - pub fn builder() -> EnumerationBuilder { - EnumerationBuilder::new() - } - - pub fn members(&self) -> Vec { - unsafe { - let mut count = 0; - let members_raw_ptr = BNGetEnumerationMembers(self.handle, &mut count); - debug_assert!(!members_raw_ptr.is_null()); - let members_raw: &[BNEnumerationMember] = - std::slice::from_raw_parts(members_raw_ptr, count); - let members = members_raw - .iter() - .map(EnumerationMember::from_raw) - .collect(); - BNFreeEnumerationMemberList(members_raw_ptr, count); - members - } - } -} - -impl Debug for Enumeration { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Enumeration") - .field("members", &self.members()) - .finish() - } -} - -unsafe impl RefCountable for Enumeration { - unsafe fn inc_ref(handle: &Self) -> Ref { - Self::ref_from_raw(BNNewEnumerationReference(handle.handle)) - } - - unsafe fn dec_ref(handle: &Self) { - BNFreeEnumeration(handle.handle); - } -} - -impl ToOwned for Enumeration { - type Owned = Ref; - - fn to_owned(&self) -> Self::Owned { - unsafe { RefCountable::inc_ref(self) } - } -} - -#[derive(PartialEq, Eq, Hash)] -pub struct StructureBuilder { - pub(crate) handle: *mut BNStructureBuilder, -} - -/// ```no_run -/// // Includes -/// # use binaryninja::binary_view::BinaryViewExt; -/// use binaryninja::types::{MemberAccess, MemberScope, Structure, StructureBuilder, Type}; -/// -/// // Types to use in the members -/// let field_1_ty = Type::named_int(5, false, "my_weird_int_type"); -/// let field_2_ty = Type::int(4, false); -/// let field_3_ty = Type::int(8, false); -/// -/// // Assign those fields -/// let mut my_custom_struct = StructureBuilder::new(); -/// my_custom_struct -/// .insert( -/// &field_1_ty, -/// "field_1", -/// 0, -/// false, -/// MemberAccess::PublicAccess, -/// MemberScope::NoScope, -/// ) -/// .insert( -/// &field_2_ty, -/// "field_2", -/// 5, -/// false, -/// MemberAccess::PublicAccess, -/// MemberScope::NoScope, -/// ) -/// .insert( -/// &field_3_ty, -/// "field_3", -/// 9, -/// false, -/// MemberAccess::PublicAccess, -/// MemberScope::NoScope, -/// ) -/// .append( -/// &field_1_ty, -/// "field_4", -/// MemberAccess::PublicAccess, -/// MemberScope::NoScope, -/// ); -/// -/// // Convert structure to type -/// let my_custom_structure_type = Type::structure(&my_custom_struct.finalize()); -/// -/// // Add the struct to the binary view to use in analysis -/// let bv = binaryninja::load("example").unwrap(); -/// bv.define_user_type("my_custom_struct", &my_custom_structure_type); -/// ``` -impl StructureBuilder { - pub fn new() -> Self { - Self { - handle: unsafe { BNCreateStructureBuilder() }, - } - } - - pub(crate) unsafe fn from_raw(handle: *mut BNStructureBuilder) -> Self { - debug_assert!(!handle.is_null()); - Self { handle } - } - - // TODO: Document the width adjustment with alignment. - pub fn finalize(&self) -> Ref { - let raw_struct_ptr = unsafe { BNFinalizeStructureBuilder(self.handle) }; - unsafe { Structure::ref_from_raw(raw_struct_ptr) } - } - - /// Sets the width of the [`StructureBuilder`] to the new width. - /// - /// This will remove all previously inserted members outside the new width. This is done by computing - /// the member access range (member offset + member width) and if it is larger than the new width - /// it will be removed. - pub fn width(&mut self, width: u64) -> &mut Self { - unsafe { - BNSetStructureBuilderWidth(self.handle, width); - } - self - } - - pub fn alignment(&mut self, alignment: usize) -> &mut Self { - unsafe { - BNSetStructureBuilderAlignment(self.handle, alignment); - } - self - } - - /// Sets whether the [`StructureBuilder`] is packed. - /// - /// If set the alignment of the structure will be `1`. You do not need to set the alignment to `1`. - pub fn packed(&mut self, packed: bool) -> &mut Self { - unsafe { - BNSetStructureBuilderPacked(self.handle, packed); - } - self - } - - pub fn structure_type(&mut self, t: StructureType) -> &mut Self { - unsafe { BNSetStructureBuilderType(self.handle, t) }; - self - } - - pub fn pointer_offset(&mut self, offset: i64) -> &mut Self { - unsafe { BNSetStructureBuilderPointerOffset(self.handle, offset) }; - self - } - - pub fn propagates_data_var_refs(&mut self, propagates: bool) -> &mut Self { - unsafe { BNSetStructureBuilderPropagatesDataVariableReferences(self.handle, propagates) }; - self - } - - pub fn base_structures(&mut self, bases: &[BaseStructure]) -> &mut Self { - let raw_base_structs: Vec = - bases.iter().map(BaseStructure::into_owned_raw).collect(); - unsafe { - BNSetBaseStructuresForStructureBuilder( - self.handle, - raw_base_structs.as_ptr() as *mut _, - raw_base_structs.len(), - ) - }; - self - } - - /// Append a member at the next available byte offset. - /// - /// Otherwise, consider using: - /// - /// - [`StructureBuilder::insert_member`] - /// - [`StructureBuilder::insert`] - /// - [`StructureBuilder::insert_bitwise`] - pub fn append<'a, T: Into>>( - &mut self, - ty: T, - name: &str, - access: MemberAccess, - scope: MemberScope, - ) -> &mut Self { - let name = name.to_cstr(); - let owned_raw_ty = Conf::<&Type>::into_raw(ty.into()); - unsafe { - BNAddStructureBuilderMember( - self.handle, - &owned_raw_ty, - name.as_ref().as_ptr() as _, - access, - scope, - ); - } - self - } - - /// Insert an already constructed [`StructureMember`]. - /// - /// Otherwise, consider using: - /// - /// - [`StructureBuilder::append`] - /// - [`StructureBuilder::insert`] - /// - [`StructureBuilder::insert_bitwise`] - pub fn insert_member( - &mut self, - member: StructureMember, - overwrite_existing: bool, - ) -> &mut Self { - self.insert_bitwise( - &member.ty, - &member.name, - member.bit_offset(), - member.bit_width, - overwrite_existing, - member.access, - member.scope, - ); - self - } - - /// Inserts a member at the `offset` (in bytes). - /// - /// If you need to insert a member at a specific bit within a given byte (like a bitfield), you - /// can use [`StructureBuilder::insert_bitwise`]. - pub fn insert<'a, T: Into>>( - &mut self, - ty: T, - name: &str, - offset: u64, - overwrite_existing: bool, - access: MemberAccess, - scope: MemberScope, - ) -> &mut Self { - self.insert_bitwise( - ty, - name, - offset * 8, - None, - overwrite_existing, - access, - scope, - ) - } - - /// Inserts a member at `bit_offset` with an optional `bit_width`. - /// - /// NOTE: The `bit_offset` is relative to the start of the structure, for example, passing `8` will place - /// the field at the start of the byte `0x1`. - pub fn insert_bitwise<'a, T: Into>>( - &mut self, - ty: T, - name: &str, - bit_offset: u64, - bit_width: Option, - overwrite_existing: bool, - access: MemberAccess, - scope: MemberScope, - ) -> &mut Self { - let name = name.to_cstr(); - let owned_raw_ty = Conf::<&Type>::into_raw(ty.into()); - let byte_offset = bit_offset / 8; - let bit_position = bit_offset % 8; - unsafe { - BNAddStructureBuilderMemberAtOffset( - self.handle, - &owned_raw_ty, - name.as_ref().as_ptr() as _, - byte_offset, - overwrite_existing, - access, - scope, - bit_position as u8, - bit_width.unwrap_or(0), - ); - } - self - } - - pub fn replace<'a, T: Into>>( - &mut self, - index: usize, - ty: T, - name: &str, - overwrite_existing: bool, - ) -> &mut Self { - let name = name.to_cstr(); - let owned_raw_ty = Conf::<&Type>::into_raw(ty.into()); - unsafe { - BNReplaceStructureBuilderMember( - self.handle, - index, - &owned_raw_ty, - name.as_ref().as_ptr() as _, - overwrite_existing, - ) - } - self - } - - /// Removes the member at a given index. - pub fn remove(&mut self, index: usize) -> &mut Self { - unsafe { BNRemoveStructureBuilderMember(self.handle, index) }; - self - } - - // TODO: We should add BNGetStructureBuilderAlignedWidth - /// Gets the current **unaligned** width of the structure. - /// - /// This cannot be used to accurately get the width of a non-packed structure. - pub fn current_width(&self) -> u64 { - unsafe { BNGetStructureBuilderWidth(self.handle) } - } -} - -impl From<&Structure> for StructureBuilder { - fn from(structure: &Structure) -> StructureBuilder { - unsafe { Self::from_raw(BNCreateStructureBuilderFromStructure(structure.handle)) } - } -} - -impl From> for StructureBuilder { - fn from(members: Vec) -> StructureBuilder { - let mut builder = StructureBuilder::new(); - for member in members { - builder.insert_member(member, false); - } - builder - } -} - -impl Drop for StructureBuilder { - fn drop(&mut self) { - unsafe { BNFreeStructureBuilder(self.handle) }; - } -} - -impl Default for StructureBuilder { - fn default() -> Self { - Self::new() - } -} - -#[derive(PartialEq, Eq, Hash)] -pub struct Structure { - pub(crate) handle: *mut BNStructure, -} - -impl Structure { - pub(crate) unsafe fn ref_from_raw(handle: *mut BNStructure) -> Ref { - debug_assert!(!handle.is_null()); - Ref::new(Self { handle }) - } - - pub fn builder() -> StructureBuilder { - StructureBuilder::new() - } - - pub fn width(&self) -> u64 { - unsafe { BNGetStructureWidth(self.handle) } - } - - pub fn structure_type(&self) -> StructureType { - unsafe { BNGetStructureType(self.handle) } - } - - /// Retrieve the members that are accessible at a given offset. - /// - /// The reason for this being plural is that members may overlap and the offset is in bytes - /// where a bitfield may contain multiple members at the given byte. - /// - /// Unions are also represented as structures and will cause this function to return - /// **all** members that can reach that offset. - /// - /// We must pass a [`TypeContainer`] here so that we can resolve base structure members, as they - /// are treated as members through this function. Typically, you get the [`TypeContainer`] - /// through the binary view with [`BinaryViewExt::type_container`]. - pub fn members_at_offset( - &self, - container: &TypeContainer, - offset: u64, - ) -> Vec { - self.members_including_inherited(container) - .into_iter() - .filter(|m| m.member.is_offset_valid(offset)) - .map(|m| m.member) - .collect() - } - - /// Return the list of non-inherited structure members. - /// - /// If you want to get all members, including ones inherited from base structures, - /// use [`Structure::members_including_inherited`] instead. - pub fn members(&self) -> Vec { - unsafe { - let mut count = 0; - let members_raw_ptr: *mut BNStructureMember = - BNGetStructureMembers(self.handle, &mut count); - debug_assert!(!members_raw_ptr.is_null()); - let members_raw = std::slice::from_raw_parts(members_raw_ptr, count); - let members = members_raw.iter().map(StructureMember::from_raw).collect(); - BNFreeStructureMemberList(members_raw_ptr, count); - members - } - } - - /// Returns the list of all structure members, including inherited ones. - /// - /// Because we must traverse through base structures, we have to provide the [`TypeContainer`]; - /// in most cases it is ok to provide the binary views container via [`BinaryViewExt::type_container`]. - pub fn members_including_inherited( - &self, - container: &TypeContainer, - ) -> Vec { - unsafe { - let mut count = 0; - let members_raw_ptr: *mut BNInheritedStructureMember = - BNGetStructureMembersIncludingInherited( - self.handle, - container.handle.as_ptr(), - &mut count, - ); - debug_assert!(!members_raw_ptr.is_null()); - let members_raw = std::slice::from_raw_parts(members_raw_ptr, count); - let members = members_raw - .iter() - .map(InheritedStructureMember::from_raw) - .collect(); - BNFreeInheritedStructureMemberList(members_raw_ptr, count); - members - } - } - - /// Retrieve the list of base structures for the structure. These base structures are what give - /// a structure inherited members. - pub fn base_structures(&self) -> Vec { - let mut count = 0; - let bases_raw_ptr = unsafe { BNGetBaseStructuresForStructure(self.handle, &mut count) }; - debug_assert!(!bases_raw_ptr.is_null()); - let bases_raw = unsafe { std::slice::from_raw_parts(bases_raw_ptr, count) }; - let bases = bases_raw.iter().map(BaseStructure::from_raw).collect(); - unsafe { BNFreeBaseStructureList(bases_raw_ptr, count) }; - bases - } - - /// Whether the structure is packed or not. - pub fn is_packed(&self) -> bool { - unsafe { BNIsStructurePacked(self.handle) } - } - - pub fn alignment(&self) -> usize { - unsafe { BNGetStructureAlignment(self.handle) } - } -} - -impl Debug for Structure { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Structure") - .field("width", &self.width()) - .field("alignment", &self.alignment()) - .field("packed", &self.is_packed()) - .field("structure_type", &self.structure_type()) - .field("base_structures", &self.base_structures()) - .field("members", &self.members()) - .finish() - } -} - -unsafe impl RefCountable for Structure { - unsafe fn inc_ref(handle: &Self) -> Ref { - Self::ref_from_raw(BNNewStructureReference(handle.handle)) - } - - unsafe fn dec_ref(handle: &Self) { - BNFreeStructure(handle.handle); - } -} - -impl ToOwned for Structure { - type Owned = Ref; - - fn to_owned(&self) -> Self::Owned { - unsafe { RefCountable::inc_ref(self) } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct StructureMember { - pub ty: Conf>, - // TODO: Shouldnt this be a QualifiedName? The ffi says no... - pub name: String, - /// The byte offset of the member. - pub offset: u64, - pub access: MemberAccess, - pub scope: MemberScope, - /// The bit position relative to the byte offset. - pub bit_position: Option, - pub bit_width: Option, -} - -impl StructureMember { - pub(crate) fn from_raw(value: &BNStructureMember) -> Self { - Self { - ty: Conf::new( - unsafe { Type::from_raw(value.type_) }.to_owned(), - value.typeConfidence, - ), - // TODO: I dislike using this function here. - name: raw_to_string(value.name as *mut _).unwrap(), - offset: value.offset, - access: value.access, - scope: value.scope, - bit_position: match value.bitPosition { - 0 => None, - _ => Some(value.bitPosition), - }, - bit_width: match value.bitWidth { - 0 => None, - _ => Some(value.bitWidth), - }, - } - } - - pub(crate) fn from_owned_raw(value: BNStructureMember) -> Self { - let owned = Self::from_raw(&value); - Self::free_raw(value); - owned - } - - pub(crate) fn into_raw(value: Self) -> BNStructureMember { - let bn_name = BnString::new(value.name); - BNStructureMember { - type_: unsafe { Ref::into_raw(value.ty.contents) }.handle, - name: BnString::into_raw(bn_name), - offset: value.offset, - typeConfidence: value.ty.confidence, - access: value.access, - scope: value.scope, - bitPosition: value.bit_position.unwrap_or(0), - bitWidth: value.bit_width.unwrap_or(0), - } - } - - pub(crate) fn free_raw(value: BNStructureMember) { - let _ = unsafe { Type::ref_from_raw(value.type_) }; - unsafe { BnString::free_raw(value.name) }; - } - - pub fn new( - ty: Conf>, - name: String, - offset: u64, - access: MemberAccess, - scope: MemberScope, - ) -> Self { - Self { - ty, - name, - offset, - access, - scope, - bit_position: None, - bit_width: None, - } - } - - pub fn new_bitfield( - ty: Conf>, - name: String, - bit_offset: u64, - bit_width: u8, - access: MemberAccess, - scope: MemberScope, - ) -> Self { - Self { - ty, - name, - offset: bit_offset / 8, - access, - scope, - bit_position: Some((bit_offset % 8) as u8), - bit_width: Some(bit_width), - } - } - - // TODO: Do we count bitwidth here? - /// Whether the offset within the accessible range of the member. - pub fn is_offset_valid(&self, offset: u64) -> bool { - self.offset <= offset && offset < self.offset + self.ty.contents.width() - } - - /// Member offset in bits. - pub fn bit_offset(&self) -> u64 { - (self.offset * 8) + self.bit_position.unwrap_or(0) as u64 - } -} - -impl CoreArrayProvider for StructureMember { - type Raw = BNStructureMember; - type Context = (); - type Wrapped<'a> = Self; -} - -unsafe impl CoreArrayProviderInner for StructureMember { - unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { - BNFreeStructureMemberList(raw, count) - } - - unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { - Self::from_raw(raw) - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct InheritedStructureMember { - pub base: Ref, - pub base_offset: u64, - pub member: StructureMember, - pub member_index: usize, -} - -impl InheritedStructureMember { - pub(crate) fn from_raw(value: &BNInheritedStructureMember) -> Self { - Self { - base: unsafe { NamedTypeReference::from_raw(value.base) }.to_owned(), - base_offset: value.baseOffset, - member: StructureMember::from_raw(&value.member), - member_index: value.memberIndex, - } - } - - pub(crate) fn from_owned_raw(value: BNInheritedStructureMember) -> Self { - let owned = Self::from_raw(&value); - Self::free_raw(value); - owned - } - - pub(crate) fn into_raw(value: Self) -> BNInheritedStructureMember { - BNInheritedStructureMember { - base: unsafe { Ref::into_raw(value.base) }.handle, - baseOffset: value.base_offset, - member: StructureMember::into_raw(value.member), - memberIndex: value.member_index, - } - } - - pub(crate) fn free_raw(value: BNInheritedStructureMember) { - let _ = unsafe { NamedTypeReference::ref_from_raw(value.base) }; - StructureMember::free_raw(value.member); - } - - pub fn new( - base: Ref, - base_offset: u64, - member: StructureMember, - member_index: usize, - ) -> Self { - Self { - base, - base_offset, - member, - member_index, - } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct BaseStructure { - pub ty: Ref, - pub offset: u64, - pub width: u64, -} - -impl BaseStructure { - pub(crate) fn from_raw(value: &BNBaseStructure) -> Self { - Self { - ty: unsafe { NamedTypeReference::from_raw(value.type_) }.to_owned(), - offset: value.offset, - width: value.width, - } - } - - pub(crate) fn from_owned_raw(value: BNBaseStructure) -> Self { - let owned = Self::from_raw(&value); - Self::free_raw(value); - owned - } - - pub(crate) fn into_raw(value: Self) -> BNBaseStructure { - BNBaseStructure { - type_: unsafe { Ref::into_raw(value.ty) }.handle, - offset: value.offset, - width: value.width, - } - } - - pub(crate) fn into_owned_raw(value: &Self) -> BNBaseStructure { - BNBaseStructure { - type_: value.ty.handle, - offset: value.offset, - width: value.width, - } - } - - pub(crate) fn free_raw(value: BNBaseStructure) { - let _ = unsafe { NamedTypeReference::ref_from_raw(value.type_) }; - } - - pub fn new(ty: Ref, offset: u64, width: u64) -> Self { - Self { ty, offset, width } - } -} - #[derive(PartialEq, Eq, Hash)] pub struct NamedTypeReference { pub(crate) handle: *mut BNNamedTypeReference, @@ -2435,6 +1560,7 @@ impl QualifiedNameTypeAndId { } } + #[allow(unused)] pub(crate) fn from_owned_raw(value: BNQualifiedNameTypeAndId) -> Self { let owned = Self::from_raw(&value); Self::free_raw(value); @@ -2495,6 +1621,7 @@ impl NameAndType { } } + #[allow(unused)] pub(crate) fn from_owned_raw(value: BNNameAndType) -> Self { let owned = Self::from_raw(&value); Self::free_raw(value); diff --git a/rust/src/type_archive.rs b/rust/src/types/archive.rs similarity index 99% rename from rust/src/type_archive.rs rename to rust/src/types/archive.rs index 4a04720500..15f4cbfeb4 100644 --- a/rust/src/type_archive.rs +++ b/rust/src/types/archive.rs @@ -1,6 +1,6 @@ use crate::progress::{NoProgressCallback, ProgressCallback}; use binaryninjacore_sys::*; -use std::ffi::{c_char, c_void, CStr}; +use std::ffi::{c_char, c_void, CStr, CString}; use std::fmt::{Debug, Display, Formatter}; use std::hash::Hash; use std::path::{Path, PathBuf}; @@ -11,8 +11,9 @@ use crate::metadata::Metadata; use crate::platform::Platform; use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Guard, Ref, RefCountable}; use crate::string::{raw_to_string, BnString, IntoCStr}; -use crate::type_container::TypeContainer; -use crate::types::{QualifiedName, QualifiedNameAndType, QualifiedNameTypeAndId, Type}; +use crate::types::{ + QualifiedName, QualifiedNameAndType, QualifiedNameTypeAndId, Type, TypeContainer, +}; #[repr(transparent)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -57,6 +58,14 @@ unsafe impl CoreArrayProviderInner for TypeArchiveSnapshotId { } } +impl IntoCStr for TypeArchiveSnapshotId { + type Result = CString; + + fn to_cstr(self) -> Self::Result { + self.to_string().to_cstr() + } +} + /// Type Archives are a collection of types which can be shared between different analysis /// sessions and are backed by a database file on disk. Their types can be modified, and /// a history of previous versions of types is stored in snapshots in the archive. diff --git a/rust/src/type_container.rs b/rust/src/types/container.rs similarity index 99% rename from rust/src/type_container.rs rename to rust/src/types/container.rs index e8c915dfa4..5096ac78c0 100644 --- a/rust/src/type_container.rs +++ b/rust/src/types/container.rs @@ -12,8 +12,7 @@ use crate::platform::Platform; use crate::progress::{NoProgressCallback, ProgressCallback}; use crate::rc::{Array, Ref}; use crate::string::{raw_to_string, BnString, IntoCStr}; -use crate::type_parser::{TypeParserError, TypeParserResult}; -use crate::types::{QualifiedName, QualifiedNameAndType, Type}; +use crate::types::{QualifiedName, QualifiedNameAndType, Type, TypeParserError, TypeParserResult}; use binaryninjacore_sys::*; use std::collections::HashMap; use std::ffi::{c_char, c_void}; diff --git a/rust/src/types/enumeration.rs b/rust/src/types/enumeration.rs new file mode 100644 index 0000000000..70c266e1f7 --- /dev/null +++ b/rust/src/types/enumeration.rs @@ -0,0 +1,201 @@ +use crate::rc::{Ref, RefCountable}; +use crate::string::{raw_to_string, BnString, IntoCStr}; +use binaryninjacore_sys::*; +use std::fmt::{Debug, Formatter}; + +#[derive(PartialEq, Eq, Hash)] +pub struct EnumerationBuilder { + pub(crate) handle: *mut BNEnumerationBuilder, +} + +impl EnumerationBuilder { + pub fn new() -> Self { + Self { + handle: unsafe { BNCreateEnumerationBuilder() }, + } + } + + pub(crate) unsafe fn from_raw(handle: *mut BNEnumerationBuilder) -> Self { + Self { handle } + } + + pub fn finalize(&self) -> Ref { + unsafe { Enumeration::ref_from_raw(BNFinalizeEnumerationBuilder(self.handle)) } + } + + pub fn append(&mut self, name: &str) -> &mut Self { + let name = name.to_cstr(); + unsafe { + BNAddEnumerationBuilderMember(self.handle, name.as_ref().as_ptr() as _); + } + self + } + + pub fn insert(&mut self, name: &str, value: u64) -> &mut Self { + let name = name.to_cstr(); + unsafe { + BNAddEnumerationBuilderMemberWithValue(self.handle, name.as_ref().as_ptr() as _, value); + } + self + } + + pub fn replace(&mut self, id: usize, name: &str, value: u64) -> &mut Self { + let name = name.to_cstr(); + unsafe { + BNReplaceEnumerationBuilderMember(self.handle, id, name.as_ref().as_ptr() as _, value); + } + self + } + + pub fn remove(&mut self, id: usize) -> &mut Self { + unsafe { + BNRemoveEnumerationBuilderMember(self.handle, id); + } + + self + } + + pub fn members(&self) -> Vec { + unsafe { + let mut count = 0; + let members_raw_ptr = BNGetEnumerationBuilderMembers(self.handle, &mut count); + let members_raw: &[BNEnumerationMember] = + std::slice::from_raw_parts(members_raw_ptr, count); + let members = members_raw + .iter() + .map(EnumerationMember::from_raw) + .collect(); + BNFreeEnumerationMemberList(members_raw_ptr, count); + members + } + } +} + +impl Default for EnumerationBuilder { + fn default() -> Self { + Self::new() + } +} + +impl From<&Enumeration> for EnumerationBuilder { + fn from(enumeration: &Enumeration) -> Self { + unsafe { + Self::from_raw(BNCreateEnumerationBuilderFromEnumeration( + enumeration.handle, + )) + } + } +} + +impl Drop for EnumerationBuilder { + fn drop(&mut self) { + unsafe { BNFreeEnumerationBuilder(self.handle) }; + } +} + +#[derive(PartialEq, Eq, Hash)] +pub struct Enumeration { + pub(crate) handle: *mut BNEnumeration, +} + +impl Enumeration { + pub(crate) unsafe fn ref_from_raw(handle: *mut BNEnumeration) -> Ref { + debug_assert!(!handle.is_null()); + Ref::new(Self { handle }) + } + + pub fn builder() -> EnumerationBuilder { + EnumerationBuilder::new() + } + + pub fn members(&self) -> Vec { + unsafe { + let mut count = 0; + let members_raw_ptr = BNGetEnumerationMembers(self.handle, &mut count); + debug_assert!(!members_raw_ptr.is_null()); + let members_raw: &[BNEnumerationMember] = + std::slice::from_raw_parts(members_raw_ptr, count); + let members = members_raw + .iter() + .map(EnumerationMember::from_raw) + .collect(); + BNFreeEnumerationMemberList(members_raw_ptr, count); + members + } + } +} + +impl Debug for Enumeration { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Enumeration") + .field("members", &self.members()) + .finish() + } +} + +unsafe impl RefCountable for Enumeration { + unsafe fn inc_ref(handle: &Self) -> Ref { + Self::ref_from_raw(BNNewEnumerationReference(handle.handle)) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeEnumeration(handle.handle); + } +} + +impl ToOwned for Enumeration { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct EnumerationMember { + pub name: String, + /// The associated constant value for the member. + pub value: u64, + /// Whether this is the default member for the associated [`Enumeration`]. + pub default: bool, +} + +impl EnumerationMember { + pub(crate) fn from_raw(value: &BNEnumerationMember) -> Self { + Self { + name: raw_to_string(value.name).unwrap(), + value: value.value, + default: value.isDefault, + } + } + + #[allow(unused)] + pub(crate) fn from_owned_raw(value: BNEnumerationMember) -> Self { + let owned = Self::from_raw(&value); + Self::free_raw(value); + owned + } + + #[allow(unused)] + pub(crate) fn into_raw(value: Self) -> BNEnumerationMember { + let bn_name = BnString::new(value.name); + BNEnumerationMember { + name: BnString::into_raw(bn_name), + value: value.value, + isDefault: value.default, + } + } + + #[allow(unused)] + pub(crate) fn free_raw(value: BNEnumerationMember) { + unsafe { BnString::free_raw(value.name) }; + } + + pub fn new(name: String, value: u64, default: bool) -> Self { + Self { + name, + value, + default, + } + } +} diff --git a/rust/src/type_library.rs b/rust/src/types/library.rs similarity index 100% rename from rust/src/type_library.rs rename to rust/src/types/library.rs diff --git a/rust/src/type_parser.rs b/rust/src/types/parser.rs similarity index 99% rename from rust/src/type_parser.rs rename to rust/src/types/parser.rs index 8ad0725fb5..1ee071882c 100644 --- a/rust/src/type_parser.rs +++ b/rust/src/types/parser.rs @@ -7,8 +7,7 @@ use std::ptr::NonNull; use crate::platform::Platform; use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref}; use crate::string::{raw_to_string, BnString, IntoCStr}; -use crate::type_container::TypeContainer; -use crate::types::{QualifiedName, QualifiedNameAndType, Type}; +use crate::types::{QualifiedName, QualifiedNameAndType, Type, TypeContainer}; pub type TypeParserErrorSeverity = BNTypeParserErrorSeverity; pub type TypeParserOption = BNTypeParserOption; diff --git a/rust/src/type_printer.rs b/rust/src/types/printer.rs similarity index 99% rename from rust/src/type_printer.rs rename to rust/src/types/printer.rs index a7495f1c16..d168434eff 100644 --- a/rust/src/type_printer.rs +++ b/rust/src/types/printer.rs @@ -5,8 +5,7 @@ use crate::disassembly::InstructionTextToken; use crate::platform::Platform; use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref}; use crate::string::{raw_to_string, BnString, IntoCStr}; -use crate::type_container::TypeContainer; -use crate::types::{NamedTypeReference, QualifiedName, QualifiedNameAndType, Type}; +use crate::types::{NamedTypeReference, QualifiedName, QualifiedNameAndType, Type, TypeContainer}; use binaryninjacore_sys::*; use std::ffi::{c_char, c_int, c_void}; use std::ptr::NonNull; diff --git a/rust/src/types/structure.rs b/rust/src/types/structure.rs new file mode 100644 index 0000000000..7de466a4a9 --- /dev/null +++ b/rust/src/types/structure.rs @@ -0,0 +1,696 @@ +use crate::confidence::Conf; +use crate::rc::{CoreArrayProvider, CoreArrayProviderInner, Ref, RefCountable}; +use crate::string::{raw_to_string, BnString, IntoCStr}; +use crate::types::{ + MemberAccess, MemberScope, NamedTypeReference, StructureType, Type, TypeContainer, +}; +use binaryninjacore_sys::*; +use std::fmt::{Debug, Formatter}; + +// Needed for doc comments +#[allow(unused)] +use crate::binary_view::BinaryViewExt; + +#[derive(PartialEq, Eq, Hash)] +pub struct StructureBuilder { + pub(crate) handle: *mut BNStructureBuilder, +} + +/// ```no_run +/// // Includes +/// # use binaryninja::binary_view::BinaryViewExt; +/// use binaryninja::types::{MemberAccess, MemberScope, Structure, StructureBuilder, Type}; +/// +/// // Types to use in the members +/// let field_1_ty = Type::named_int(5, false, "my_weird_int_type"); +/// let field_2_ty = Type::int(4, false); +/// let field_3_ty = Type::int(8, false); +/// +/// // Assign those fields +/// let mut my_custom_struct = StructureBuilder::new(); +/// my_custom_struct +/// .insert( +/// &field_1_ty, +/// "field_1", +/// 0, +/// false, +/// MemberAccess::PublicAccess, +/// MemberScope::NoScope, +/// ) +/// .insert( +/// &field_2_ty, +/// "field_2", +/// 5, +/// false, +/// MemberAccess::PublicAccess, +/// MemberScope::NoScope, +/// ) +/// .insert( +/// &field_3_ty, +/// "field_3", +/// 9, +/// false, +/// MemberAccess::PublicAccess, +/// MemberScope::NoScope, +/// ) +/// .append( +/// &field_1_ty, +/// "field_4", +/// MemberAccess::PublicAccess, +/// MemberScope::NoScope, +/// ); +/// +/// // Convert structure to type +/// let my_custom_structure_type = Type::structure(&my_custom_struct.finalize()); +/// +/// // Add the struct to the binary view to use in analysis +/// let bv = binaryninja::load("example").unwrap(); +/// bv.define_user_type("my_custom_struct", &my_custom_structure_type); +/// ``` +impl StructureBuilder { + pub fn new() -> Self { + Self { + handle: unsafe { BNCreateStructureBuilder() }, + } + } + + pub(crate) unsafe fn from_raw(handle: *mut BNStructureBuilder) -> Self { + debug_assert!(!handle.is_null()); + Self { handle } + } + + // TODO: Document the width adjustment with alignment. + pub fn finalize(&self) -> Ref { + let raw_struct_ptr = unsafe { BNFinalizeStructureBuilder(self.handle) }; + unsafe { Structure::ref_from_raw(raw_struct_ptr) } + } + + /// Sets the width of the [`StructureBuilder`] to the new width. + /// + /// This will remove all previously inserted members outside the new width. This is done by computing + /// the member access range (member offset + member width) and if it is larger than the new width + /// it will be removed. + pub fn width(&mut self, width: u64) -> &mut Self { + unsafe { + BNSetStructureBuilderWidth(self.handle, width); + } + self + } + + pub fn alignment(&mut self, alignment: usize) -> &mut Self { + unsafe { + BNSetStructureBuilderAlignment(self.handle, alignment); + } + self + } + + /// Sets whether the [`StructureBuilder`] is packed. + /// + /// If set the alignment of the structure will be `1`. You do not need to set the alignment to `1`. + pub fn packed(&mut self, packed: bool) -> &mut Self { + unsafe { + BNSetStructureBuilderPacked(self.handle, packed); + } + self + } + + pub fn structure_type(&mut self, t: StructureType) -> &mut Self { + unsafe { BNSetStructureBuilderType(self.handle, t) }; + self + } + + pub fn pointer_offset(&mut self, offset: i64) -> &mut Self { + unsafe { BNSetStructureBuilderPointerOffset(self.handle, offset) }; + self + } + + pub fn propagates_data_var_refs(&mut self, propagates: bool) -> &mut Self { + unsafe { BNSetStructureBuilderPropagatesDataVariableReferences(self.handle, propagates) }; + self + } + + pub fn base_structures(&mut self, bases: &[BaseStructure]) -> &mut Self { + let raw_base_structs: Vec = + bases.iter().map(BaseStructure::into_owned_raw).collect(); + unsafe { + BNSetBaseStructuresForStructureBuilder( + self.handle, + raw_base_structs.as_ptr() as *mut _, + raw_base_structs.len(), + ) + }; + self + } + + /// Append a member at the next available byte offset. + /// + /// Otherwise, consider using: + /// + /// - [`StructureBuilder::insert_member`] + /// - [`StructureBuilder::insert`] + /// - [`StructureBuilder::insert_bitwise`] + pub fn append<'a, T: Into>>( + &mut self, + ty: T, + name: &str, + access: MemberAccess, + scope: MemberScope, + ) -> &mut Self { + let name = name.to_cstr(); + let owned_raw_ty = Conf::<&Type>::into_raw(ty.into()); + unsafe { + BNAddStructureBuilderMember( + self.handle, + &owned_raw_ty, + name.as_ref().as_ptr() as _, + access, + scope, + ); + } + self + } + + /// Insert an already constructed [`StructureMember`]. + /// + /// Otherwise, consider using: + /// + /// - [`StructureBuilder::append`] + /// - [`StructureBuilder::insert`] + /// - [`StructureBuilder::insert_bitwise`] + pub fn insert_member( + &mut self, + member: StructureMember, + overwrite_existing: bool, + ) -> &mut Self { + self.insert_bitwise( + &member.ty, + &member.name, + member.bit_offset(), + member.bit_width, + overwrite_existing, + member.access, + member.scope, + ); + self + } + + /// Inserts a member at the `offset` (in bytes). + /// + /// If you need to insert a member at a specific bit within a given byte (like a bitfield), you + /// can use [`StructureBuilder::insert_bitwise`]. + pub fn insert<'a, T: Into>>( + &mut self, + ty: T, + name: &str, + offset: u64, + overwrite_existing: bool, + access: MemberAccess, + scope: MemberScope, + ) -> &mut Self { + self.insert_bitwise( + ty, + name, + offset * 8, + None, + overwrite_existing, + access, + scope, + ) + } + + /// Inserts a member at `bit_offset` with an optional `bit_width`. + /// + /// NOTE: The `bit_offset` is relative to the start of the structure, for example, passing `8` will place + /// the field at the start of the byte `0x1`. + pub fn insert_bitwise<'a, T: Into>>( + &mut self, + ty: T, + name: &str, + bit_offset: u64, + bit_width: Option, + overwrite_existing: bool, + access: MemberAccess, + scope: MemberScope, + ) -> &mut Self { + let name = name.to_cstr(); + let owned_raw_ty = Conf::<&Type>::into_raw(ty.into()); + let byte_offset = bit_offset / 8; + let bit_position = bit_offset % 8; + unsafe { + BNAddStructureBuilderMemberAtOffset( + self.handle, + &owned_raw_ty, + name.as_ref().as_ptr() as _, + byte_offset, + overwrite_existing, + access, + scope, + bit_position as u8, + bit_width.unwrap_or(0), + ); + } + self + } + + pub fn replace<'a, T: Into>>( + &mut self, + index: usize, + ty: T, + name: &str, + overwrite_existing: bool, + ) -> &mut Self { + let name = name.to_cstr(); + let owned_raw_ty = Conf::<&Type>::into_raw(ty.into()); + unsafe { + BNReplaceStructureBuilderMember( + self.handle, + index, + &owned_raw_ty, + name.as_ref().as_ptr() as _, + overwrite_existing, + ) + } + self + } + + /// Removes the member at a given index. + pub fn remove(&mut self, index: usize) -> &mut Self { + unsafe { BNRemoveStructureBuilderMember(self.handle, index) }; + self + } + + // TODO: We should add BNGetStructureBuilderAlignedWidth + /// Gets the current **unaligned** width of the structure. + /// + /// This cannot be used to accurately get the width of a non-packed structure. + pub fn current_width(&self) -> u64 { + unsafe { BNGetStructureBuilderWidth(self.handle) } + } +} + +impl From<&Structure> for StructureBuilder { + fn from(structure: &Structure) -> StructureBuilder { + unsafe { Self::from_raw(BNCreateStructureBuilderFromStructure(structure.handle)) } + } +} + +impl From> for StructureBuilder { + fn from(members: Vec) -> StructureBuilder { + let mut builder = StructureBuilder::new(); + for member in members { + builder.insert_member(member, false); + } + builder + } +} + +impl Drop for StructureBuilder { + fn drop(&mut self) { + unsafe { BNFreeStructureBuilder(self.handle) }; + } +} + +impl Default for StructureBuilder { + fn default() -> Self { + Self::new() + } +} + +#[derive(PartialEq, Eq, Hash)] +pub struct Structure { + pub(crate) handle: *mut BNStructure, +} + +impl Structure { + pub(crate) unsafe fn ref_from_raw(handle: *mut BNStructure) -> Ref { + debug_assert!(!handle.is_null()); + Ref::new(Self { handle }) + } + + pub fn builder() -> StructureBuilder { + StructureBuilder::new() + } + + pub fn width(&self) -> u64 { + unsafe { BNGetStructureWidth(self.handle) } + } + + pub fn structure_type(&self) -> StructureType { + unsafe { BNGetStructureType(self.handle) } + } + + /// Retrieve the members that are accessible at a given offset. + /// + /// The reason for this being plural is that members may overlap and the offset is in bytes + /// where a bitfield may contain multiple members at the given byte. + /// + /// Unions are also represented as structures and will cause this function to return + /// **all** members that can reach that offset. + /// + /// We must pass a [`TypeContainer`] here so that we can resolve base structure members, as they + /// are treated as members through this function. Typically, you get the [`TypeContainer`] + /// through the binary view with [`BinaryViewExt::type_container`]. + pub fn members_at_offset( + &self, + container: &TypeContainer, + offset: u64, + ) -> Vec { + self.members_including_inherited(container) + .into_iter() + .filter(|m| m.member.is_offset_valid(offset)) + .map(|m| m.member) + .collect() + } + + /// Return the list of non-inherited structure members. + /// + /// If you want to get all members, including ones inherited from base structures, + /// use [`Structure::members_including_inherited`] instead. + pub fn members(&self) -> Vec { + unsafe { + let mut count = 0; + let members_raw_ptr: *mut BNStructureMember = + BNGetStructureMembers(self.handle, &mut count); + debug_assert!(!members_raw_ptr.is_null()); + let members_raw = std::slice::from_raw_parts(members_raw_ptr, count); + let members = members_raw.iter().map(StructureMember::from_raw).collect(); + BNFreeStructureMemberList(members_raw_ptr, count); + members + } + } + + /// Returns the list of all structure members, including inherited ones. + /// + /// Because we must traverse through base structures, we have to provide the [`TypeContainer`]; + /// in most cases it is ok to provide the binary views container via [`BinaryViewExt::type_container`]. + pub fn members_including_inherited( + &self, + container: &TypeContainer, + ) -> Vec { + unsafe { + let mut count = 0; + let members_raw_ptr: *mut BNInheritedStructureMember = + BNGetStructureMembersIncludingInherited( + self.handle, + container.handle.as_ptr(), + &mut count, + ); + debug_assert!(!members_raw_ptr.is_null()); + let members_raw = std::slice::from_raw_parts(members_raw_ptr, count); + let members = members_raw + .iter() + .map(InheritedStructureMember::from_raw) + .collect(); + BNFreeInheritedStructureMemberList(members_raw_ptr, count); + members + } + } + + /// Retrieve the list of base structures for the structure. These base structures are what give + /// a structure inherited members. + pub fn base_structures(&self) -> Vec { + let mut count = 0; + let bases_raw_ptr = unsafe { BNGetBaseStructuresForStructure(self.handle, &mut count) }; + debug_assert!(!bases_raw_ptr.is_null()); + let bases_raw = unsafe { std::slice::from_raw_parts(bases_raw_ptr, count) }; + let bases = bases_raw.iter().map(BaseStructure::from_raw).collect(); + unsafe { BNFreeBaseStructureList(bases_raw_ptr, count) }; + bases + } + + /// Whether the structure is packed or not. + pub fn is_packed(&self) -> bool { + unsafe { BNIsStructurePacked(self.handle) } + } + + pub fn alignment(&self) -> usize { + unsafe { BNGetStructureAlignment(self.handle) } + } +} + +impl Debug for Structure { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Structure") + .field("width", &self.width()) + .field("alignment", &self.alignment()) + .field("packed", &self.is_packed()) + .field("structure_type", &self.structure_type()) + .field("base_structures", &self.base_structures()) + .field("members", &self.members()) + .finish() + } +} + +unsafe impl RefCountable for Structure { + unsafe fn inc_ref(handle: &Self) -> Ref { + Self::ref_from_raw(BNNewStructureReference(handle.handle)) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeStructure(handle.handle); + } +} + +impl ToOwned for Structure { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct StructureMember { + pub ty: Conf>, + // TODO: Shouldnt this be a QualifiedName? The ffi says no... + pub name: String, + /// The byte offset of the member. + pub offset: u64, + pub access: MemberAccess, + pub scope: MemberScope, + /// The bit position relative to the byte offset. + pub bit_position: Option, + pub bit_width: Option, +} + +impl StructureMember { + pub(crate) fn from_raw(value: &BNStructureMember) -> Self { + Self { + ty: Conf::new( + unsafe { Type::from_raw(value.type_) }.to_owned(), + value.typeConfidence, + ), + // TODO: I dislike using this function here. + name: raw_to_string(value.name as *mut _).unwrap(), + offset: value.offset, + access: value.access, + scope: value.scope, + bit_position: match value.bitPosition { + 0 => None, + _ => Some(value.bitPosition), + }, + bit_width: match value.bitWidth { + 0 => None, + _ => Some(value.bitWidth), + }, + } + } + + #[allow(unused)] + pub(crate) fn from_owned_raw(value: BNStructureMember) -> Self { + let owned = Self::from_raw(&value); + Self::free_raw(value); + owned + } + + #[allow(unused)] + pub(crate) fn into_raw(value: Self) -> BNStructureMember { + let bn_name = BnString::new(value.name); + BNStructureMember { + type_: unsafe { Ref::into_raw(value.ty.contents) }.handle, + name: BnString::into_raw(bn_name), + offset: value.offset, + typeConfidence: value.ty.confidence, + access: value.access, + scope: value.scope, + bitPosition: value.bit_position.unwrap_or(0), + bitWidth: value.bit_width.unwrap_or(0), + } + } + + #[allow(unused)] + pub(crate) fn free_raw(value: BNStructureMember) { + let _ = unsafe { Type::ref_from_raw(value.type_) }; + unsafe { BnString::free_raw(value.name) }; + } + + pub fn new( + ty: Conf>, + name: String, + offset: u64, + access: MemberAccess, + scope: MemberScope, + ) -> Self { + Self { + ty, + name, + offset, + access, + scope, + bit_position: None, + bit_width: None, + } + } + + pub fn new_bitfield( + ty: Conf>, + name: String, + bit_offset: u64, + bit_width: u8, + access: MemberAccess, + scope: MemberScope, + ) -> Self { + Self { + ty, + name, + offset: bit_offset / 8, + access, + scope, + bit_position: Some((bit_offset % 8) as u8), + bit_width: Some(bit_width), + } + } + + // TODO: Do we count bitwidth here? + /// Whether the offset within the accessible range of the member. + pub fn is_offset_valid(&self, offset: u64) -> bool { + self.offset <= offset && offset < self.offset + self.ty.contents.width() + } + + /// Member offset in bits. + pub fn bit_offset(&self) -> u64 { + (self.offset * 8) + self.bit_position.unwrap_or(0) as u64 + } +} + +impl CoreArrayProvider for StructureMember { + type Raw = BNStructureMember; + type Context = (); + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for StructureMember { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeStructureMemberList(raw, count) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::from_raw(raw) + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct InheritedStructureMember { + pub base: Ref, + pub base_offset: u64, + pub member: StructureMember, + pub member_index: usize, +} + +impl InheritedStructureMember { + pub(crate) fn from_raw(value: &BNInheritedStructureMember) -> Self { + Self { + base: unsafe { NamedTypeReference::from_raw(value.base) }.to_owned(), + base_offset: value.baseOffset, + member: StructureMember::from_raw(&value.member), + member_index: value.memberIndex, + } + } + + #[allow(unused)] + pub(crate) fn from_owned_raw(value: BNInheritedStructureMember) -> Self { + let owned = Self::from_raw(&value); + Self::free_raw(value); + owned + } + + #[allow(unused)] + pub(crate) fn into_raw(value: Self) -> BNInheritedStructureMember { + BNInheritedStructureMember { + base: unsafe { Ref::into_raw(value.base) }.handle, + baseOffset: value.base_offset, + member: StructureMember::into_raw(value.member), + memberIndex: value.member_index, + } + } + + #[allow(unused)] + pub(crate) fn free_raw(value: BNInheritedStructureMember) { + let _ = unsafe { NamedTypeReference::ref_from_raw(value.base) }; + StructureMember::free_raw(value.member); + } + + pub fn new( + base: Ref, + base_offset: u64, + member: StructureMember, + member_index: usize, + ) -> Self { + Self { + base, + base_offset, + member, + member_index, + } + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct BaseStructure { + pub ty: Ref, + pub offset: u64, + pub width: u64, +} + +impl BaseStructure { + pub(crate) fn from_raw(value: &BNBaseStructure) -> Self { + Self { + ty: unsafe { NamedTypeReference::from_raw(value.type_) }.to_owned(), + offset: value.offset, + width: value.width, + } + } + + #[allow(unused)] + pub(crate) fn from_owned_raw(value: BNBaseStructure) -> Self { + let owned = Self::from_raw(&value); + Self::free_raw(value); + owned + } + + #[allow(unused)] + pub(crate) fn into_raw(value: Self) -> BNBaseStructure { + BNBaseStructure { + type_: unsafe { Ref::into_raw(value.ty) }.handle, + offset: value.offset, + width: value.width, + } + } + + pub(crate) fn into_owned_raw(value: &Self) -> BNBaseStructure { + BNBaseStructure { + type_: value.ty.handle, + offset: value.offset, + width: value.width, + } + } + + #[allow(unused)] + pub(crate) fn free_raw(value: BNBaseStructure) { + let _ = unsafe { NamedTypeReference::ref_from_raw(value.type_) }; + } + + pub fn new(ty: Ref, offset: u64, width: u64) -> Self { + Self { ty, offset, width } + } +} diff --git a/rust/tests/type_archive.rs b/rust/tests/type_archive.rs index 12fcec9dac..3e13507ee6 100644 --- a/rust/tests/type_archive.rs +++ b/rust/tests/type_archive.rs @@ -1,7 +1,6 @@ use binaryninja::headless::Session; use binaryninja::platform::Platform; -use binaryninja::type_archive::TypeArchive; -use binaryninja::types::{Type, TypeClass}; +use binaryninja::types::{Type, TypeArchive, TypeClass}; #[test] fn test_create_archive() { diff --git a/rust/tests/type_library.rs b/rust/tests/type_library.rs index 83937386e0..b8a12870aa 100644 --- a/rust/tests/type_library.rs +++ b/rust/tests/type_library.rs @@ -1,8 +1,7 @@ use binaryninja::binary_view::BinaryViewExt; use binaryninja::headless::Session; use binaryninja::platform::Platform; -use binaryninja::type_library::TypeLibrary; -use binaryninja::types::{Type, TypeClass}; +use binaryninja::types::{Type, TypeClass, TypeLibrary}; use std::path::PathBuf; #[test] diff --git a/rust/tests/type_parser.rs b/rust/tests/type_parser.rs index d08e7791d4..64771404d2 100644 --- a/rust/tests/type_parser.rs +++ b/rust/tests/type_parser.rs @@ -1,7 +1,6 @@ use binaryninja::headless::Session; use binaryninja::platform::Platform; -use binaryninja::type_parser::{CoreTypeParser, TypeParser, TypeParserError}; -use binaryninja::types::Type; +use binaryninja::types::{CoreTypeParser, Type, TypeParser, TypeParserError}; use binaryninjacore_sys::BNTypeParserErrorSeverity::ErrorSeverity; const TEST_TYPES: &str = r#" diff --git a/rust/tests/type_printer.rs b/rust/tests/type_printer.rs index 4ff67232da..6703537613 100644 --- a/rust/tests/type_printer.rs +++ b/rust/tests/type_printer.rs @@ -3,12 +3,10 @@ use binaryninja::disassembly::InstructionTextToken; use binaryninja::headless::Session; use binaryninja::platform::Platform; use binaryninja::rc::Ref; -use binaryninja::type_container::TypeContainer; -use binaryninja::type_printer::{ - register_type_printer, CoreTypePrinter, TokenEscapingType, TypeDefinitionLine, TypePrinter, -}; +use binaryninja::types::printer::{register_type_printer, TokenEscapingType, TypeDefinitionLine}; use binaryninja::types::{ - MemberAccess, MemberScope, QualifiedName, Structure, StructureMember, Type, + CoreTypePrinter, MemberAccess, MemberScope, QualifiedName, Structure, StructureMember, Type, + TypeContainer, TypePrinter, }; use std::path::PathBuf; From d1da7e1e58eba8c71ce64fec67384c0379f74ce8 Mon Sep 17 00:00:00 2001 From: Mason Reed Date: Thu, 11 Dec 2025 15:47:56 -0500 Subject: [PATCH 2/4] [Rust] Misc documentation and internal cleanup of types module --- rust/src/types.rs | 177 +++++++++++++++++++++------------------------- 1 file changed, 82 insertions(+), 95 deletions(-) diff --git a/rust/src/types.rs b/rust/src/types.rs index cfdc865bd5..cf7b6f1aab 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -11,6 +11,18 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +//! The model for representing types in Binary Ninja. +//! +//! [`Type`]'s are fundamental to analysis. With types, you can influence how decompilation resolves accesses, +//! renders data, and tell the analysis of properties such as volatility and constness. +//! +//! Types are typically stored within a [`BinaryView`], [`TypeArchive`] or a [`TypeLibrary`]. +//! +//! Types can be created using the [`TypeBuilder`] or one of the convenience functions. Another way +//! to create a type is with a [`TypeParser`] if you have C type definitions. +//! +//! Some interfaces may expect to be passed a [`TypeContainer`] which itself does not store any type +//! information, rather a generic interface to query for types by name or by id. pub mod archive; pub mod container; @@ -80,13 +92,11 @@ impl TypeBuilder { Self { handle } } - // Chainable terminal + /// Turn the [`TypeBuilder`] into a [`Type`]. pub fn finalize(&self) -> Ref { unsafe { Type::ref_from_raw(BNFinalizeTypeBuilder(self.handle)) } } - // Settable properties - pub fn set_can_return>>(&self, value: T) -> &Self { let mut bool_with_confidence = value.into().into(); unsafe { BNSetFunctionTypeBuilderCanReturn(self.handle, &mut bool_with_confidence) }; @@ -284,18 +294,22 @@ impl TypeBuilder { // TODO : This and properties // pub fn tokens(&self) -> ? {} + /// Create a void [`TypeBuilder`]. Analogous to [`Type::void`]. pub fn void() -> Self { unsafe { Self::from_raw(BNCreateVoidTypeBuilder()) } } + /// Create a bool [`TypeBuilder`]. Analogous to [`Type::bool`]. pub fn bool() -> Self { unsafe { Self::from_raw(BNCreateBoolTypeBuilder()) } } + /// Create a signed one byte integer [`TypeBuilder`]. Analogous to [`Type::char`]. pub fn char() -> Self { Self::int(1, true) } + /// Create an integer [`TypeBuilder`] with the given width and signedness. Analogous to [`Type::int`]. pub fn int(width: usize, is_signed: bool) -> Self { let mut is_signed = Conf::new(is_signed, MAX_CONFIDENCE).into(); @@ -303,15 +317,16 @@ impl TypeBuilder { Self::from_raw(BNCreateIntegerTypeBuilder( width, &mut is_signed, - BnString::new("").as_ptr() as *mut _, + c"".as_ptr() as _, )) } } + /// Create an integer [`TypeBuilder`] with the given width and signedness and an alternative name. + /// Analogous to [`Type::named_int`]. pub fn named_int(width: usize, is_signed: bool, alt_name: &str) -> Self { let mut is_signed = Conf::new(is_signed, MAX_CONFIDENCE).into(); - // let alt_name = BnString::new(alt_name); - let alt_name = alt_name.to_cstr(); // This segfaulted once, so the above version is there if we need to change to it, but in theory this is copied into a `const string&` on the C++ side; I'm just not 100% confident that a constant reference copies data + let alt_name = alt_name.to_cstr(); unsafe { Self::from_raw(BNCreateIntegerTypeBuilder( @@ -322,20 +337,25 @@ impl TypeBuilder { } } + /// Create a float [`TypeBuilder`] with the given width. Analogous to [`Type::float`]. pub fn float(width: usize) -> Self { unsafe { Self::from_raw(BNCreateFloatTypeBuilder(width, c"".as_ptr())) } } + /// Create a float [`TypeBuilder`] with the given width and alternative name. Analogous to [`Type::named_float`]. pub fn named_float(width: usize, alt_name: &str) -> Self { let alt_name = alt_name.to_cstr(); unsafe { Self::from_raw(BNCreateFloatTypeBuilder(width, alt_name.as_ptr())) } } + /// Create an array [`TypeBuilder`] with the given element type and count. Analogous to [`Type::array`]. pub fn array<'a, T: Into>>(ty: T, count: u64) -> Self { let owned_raw_ty = Conf::<&Type>::into_raw(ty.into()); unsafe { Self::from_raw(BNCreateArrayTypeBuilder(&owned_raw_ty, count)) } } + /// Create an enumeration [`TypeBuilder`] with the given width and signedness. Analogous to [`Type::enumeration`]. + /// /// ## NOTE /// /// The C/C++ APIs require an associated architecture, but in the core we only query the default_int_size if the given width is 0. @@ -357,10 +377,12 @@ impl TypeBuilder { } } + /// Create a structure [`TypeBuilder`]. Analogous to [`Type::structure`]. pub fn structure(structure_type: &Structure) -> Self { unsafe { Self::from_raw(BNCreateStructureTypeBuilder(structure_type.handle)) } } + /// Create a named type reference [`TypeBuilder`]. Analogous to [`Type::named_type`]. pub fn named_type(type_reference: NamedTypeReference) -> Self { let mut is_const = Conf::new(false, MIN_CONFIDENCE).into(); let mut is_volatile = Conf::new(false, MIN_CONFIDENCE).into(); @@ -375,6 +397,7 @@ impl TypeBuilder { } } + /// Create a named type reference [`TypeBuilder`] from a type and name. Analogous to [`Type::named_type_from_type`]. pub fn named_type_from_type>(name: T, t: &Type) -> Self { let mut raw_name = QualifiedName::into_raw(name.into()); let id = c""; @@ -392,60 +415,30 @@ impl TypeBuilder { // TODO : BNCreateFunctionTypeBuilder + /// Create a pointer [`TypeBuilder`] with the given target type. Analogous to [`Type::pointer`]. pub fn pointer<'a, A: Architecture, T: Into>>(arch: &A, ty: T) -> Self { - let mut is_const = Conf::new(false, MIN_CONFIDENCE).into(); - let mut is_volatile = Conf::new(false, MIN_CONFIDENCE).into(); - let owned_raw_ty = Conf::<&Type>::into_raw(ty.into()); - unsafe { - Self::from_raw(BNCreatePointerTypeBuilder( - arch.as_ref().handle, - &owned_raw_ty, - &mut is_const, - &mut is_volatile, - ReferenceType::PointerReferenceType, - )) - } + Self::pointer_with_options(arch, ty, false, false, None) } + /// Create a const pointer [`TypeBuilder`] with the given target type. Analogous to [`Type::const_pointer`]. pub fn const_pointer<'a, A: Architecture, T: Into>>(arch: &A, ty: T) -> Self { - let mut is_const = Conf::new(true, MAX_CONFIDENCE).into(); - let mut is_volatile = Conf::new(false, MIN_CONFIDENCE).into(); - let owned_raw_ty = Conf::<&Type>::into_raw(ty.into()); - unsafe { - Self::from_raw(BNCreatePointerTypeBuilder( - arch.as_ref().handle, - &owned_raw_ty, - &mut is_const, - &mut is_volatile, - ReferenceType::PointerReferenceType, - )) - } + Self::pointer_with_options(arch, ty, true, false, None) } - pub fn pointer_of_width<'a, T: Into>>( + pub fn pointer_with_options<'a, A: Architecture, T: Into>>( + arch: &A, ty: T, - size: usize, is_const: bool, is_volatile: bool, ref_type: Option, ) -> Self { - let mut is_const = Conf::new(is_const, MAX_CONFIDENCE).into(); - let mut is_volatile = Conf::new(is_volatile, MAX_CONFIDENCE).into(); - let owned_raw_ty = Conf::<&Type>::into_raw(ty.into()); - unsafe { - Self::from_raw(BNCreatePointerTypeBuilderOfWidth( - size, - &owned_raw_ty, - &mut is_const, - &mut is_volatile, - ref_type.unwrap_or(ReferenceType::PointerReferenceType), - )) - } + let arch_ptr_size = arch.address_size(); + Self::pointer_of_width(ty, arch_ptr_size, is_const, is_volatile, ref_type) } - pub fn pointer_with_options<'a, A: Architecture, T: Into>>( - arch: &A, + pub fn pointer_of_width<'a, T: Into>>( ty: T, + size: usize, is_const: bool, is_volatile: bool, ref_type: Option, @@ -454,8 +447,8 @@ impl TypeBuilder { let mut is_volatile = Conf::new(is_volatile, MAX_CONFIDENCE).into(); let owned_raw_ty = Conf::<&Type>::into_raw(ty.into()); unsafe { - Self::from_raw(BNCreatePointerTypeBuilder( - arch.as_ref().handle, + Self::from_raw(BNCreatePointerTypeBuilderOfWidth( + size, &owned_raw_ty, &mut is_const, &mut is_volatile, @@ -479,11 +472,32 @@ impl Drop for TypeBuilder { } } -#[repr(transparent)] -pub struct Type { - pub handle: *mut BNType, -} - +/// The core model for types in Binary Ninja. +/// +/// A [`Type`] is how we model the storage of a [`Variable`] or [`crate::variable::DataVariable`] as +/// well as propagate information such as the constness of a variable. Types are also used to declare +/// function signatures, such as the [`FunctionParameter`]'s and return type. +/// +/// Types are immutable. To change a type, you must create a new one either using [`TypeBuilder`] or +/// one of the helper functions: +/// +/// - [`Type::void`] +/// - [`Type::bool`] +/// - [`Type::char`] +/// - [`Type::wide_char`] +/// - [`Type::int`], [`Type::named_int`] +/// - [`Type::float`], [`Type::named_float`] +/// - [`Type::array`] +/// - [`Type::enumeration`] +/// - [`Type::structure`] +/// - [`Type::named_type`], [`Type::named_type_from_type`] +/// - [`Type::function`], [`Type::function_with_opts`] +/// - [`Type::pointer`], [`Type::const_pointer`], [`Type::pointer_of_width`], [`Type::pointer_with_options`] +/// +/// # Example +/// +/// As an example, defining a _named_ type within a [`BinaryView`]: +/// /// ```no_run /// # use crate::binaryninja::binary_view::BinaryViewExt; /// # use binaryninja::types::Type; @@ -493,6 +507,11 @@ pub struct Type { /// bv.define_user_type("int_1", &my_custom_type_1); /// bv.define_user_type("int_2", &my_custom_type_2); /// ``` +#[repr(transparent)] +pub struct Type { + pub handle: *mut BNType, +} + impl Type { pub unsafe fn from_raw(handle: *mut BNType) -> Self { debug_assert!(!handle.is_null()); @@ -885,62 +904,30 @@ impl Type { } pub fn pointer<'a, A: Architecture, T: Into>>(arch: &A, ty: T) -> Ref { - let mut is_const = Conf::new(false, MIN_CONFIDENCE).into(); - let mut is_volatile = Conf::new(false, MIN_CONFIDENCE).into(); - let owned_raw_ty = Conf::<&Type>::into_raw(ty.into()); - unsafe { - Self::ref_from_raw(BNCreatePointerType( - arch.as_ref().handle, - &owned_raw_ty, - &mut is_const, - &mut is_volatile, - ReferenceType::PointerReferenceType, - )) - } + Self::pointer_with_options(arch, ty, false, false, None) } pub fn const_pointer<'a, A: Architecture, T: Into>>( arch: &A, ty: T, ) -> Ref { - let mut is_const = Conf::new(true, MAX_CONFIDENCE).into(); - let mut is_volatile = Conf::new(false, MIN_CONFIDENCE).into(); - let owned_raw_ty = Conf::<&Type>::into_raw(ty.into()); - unsafe { - Self::ref_from_raw(BNCreatePointerType( - arch.as_ref().handle, - &owned_raw_ty, - &mut is_const, - &mut is_volatile, - ReferenceType::PointerReferenceType, - )) - } + Self::pointer_with_options(arch, ty, true, false, None) } - pub fn pointer_of_width<'a, T: Into>>( + pub fn pointer_with_options<'a, A: Architecture, T: Into>>( + arch: &A, ty: T, - size: usize, is_const: bool, is_volatile: bool, ref_type: Option, ) -> Ref { - let mut is_const = Conf::new(is_const, MAX_CONFIDENCE).into(); - let mut is_volatile = Conf::new(is_volatile, MAX_CONFIDENCE).into(); - let owned_raw_ty = Conf::<&Type>::into_raw(ty.into()); - unsafe { - Self::ref_from_raw(BNCreatePointerTypeOfWidth( - size, - &owned_raw_ty, - &mut is_const, - &mut is_volatile, - ref_type.unwrap_or(ReferenceType::PointerReferenceType), - )) - } + let arch_pointer_size = arch.address_size(); + Self::pointer_of_width(ty, arch_pointer_size, is_const, is_volatile, ref_type) } - pub fn pointer_with_options<'a, A: Architecture, T: Into>>( - arch: &A, + pub fn pointer_of_width<'a, T: Into>>( ty: T, + size: usize, is_const: bool, is_volatile: bool, ref_type: Option, @@ -949,8 +936,8 @@ impl Type { let mut is_volatile = Conf::new(is_volatile, MAX_CONFIDENCE).into(); let owned_raw_ty = Conf::<&Type>::into_raw(ty.into()); unsafe { - Self::ref_from_raw(BNCreatePointerType( - arch.as_ref().handle, + Self::ref_from_raw(BNCreatePointerTypeOfWidth( + size, &owned_raw_ty, &mut is_const, &mut is_volatile, From aec0d26dec15d83892b46ba4d9ab4ae0619395b3 Mon Sep 17 00:00:00 2001 From: Mason Reed Date: Sat, 13 Dec 2025 15:44:33 -0500 Subject: [PATCH 3/4] [Rust] Move `QualifiedName` to own module This is used in more places than types, so its best we keep it separate. --- rust/src/lib.rs | 1 + rust/src/qualified_name.rs | 221 ++++++++++++++++++++++++++++++++++++ rust/src/types.rs | 223 +------------------------------------ 3 files changed, 227 insertions(+), 218 deletions(-) create mode 100644 rust/src/qualified_name.rs diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 2d9cab27d8..ba654f099d 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -68,6 +68,7 @@ pub mod metadata; pub mod platform; pub mod progress; pub mod project; +pub mod qualified_name; pub mod rc; pub mod references; pub mod relocation; diff --git a/rust/src/qualified_name.rs b/rust/src/qualified_name.rs new file mode 100644 index 0000000000..c1d943f866 --- /dev/null +++ b/rust/src/qualified_name.rs @@ -0,0 +1,221 @@ +use crate::rc::{CoreArrayProvider, CoreArrayProviderInner}; +use crate::string::{raw_to_string, strings_to_string_list, BnString}; +use binaryninjacore_sys::*; +use std::borrow::Cow; +use std::fmt::{Display, Formatter}; +use std::ops::{Index, IndexMut}; + +// TODO: Document usage, specifically how to make a qualified name and why it exists. +#[derive(Default, Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] +pub struct QualifiedName { + // TODO: Make this Option where default is "::". + pub separator: String, + pub items: Vec, +} + +impl QualifiedName { + pub(crate) fn from_raw(value: &BNQualifiedName) -> Self { + // TODO: This could be improved... + let raw_names = unsafe { std::slice::from_raw_parts(value.name, value.nameCount) }; + let items = raw_names + .iter() + .filter_map(|&raw_name| raw_to_string(raw_name as *const _)) + .collect(); + let separator = raw_to_string(value.join).unwrap(); + Self { items, separator } + } + + pub(crate) fn from_owned_raw(value: BNQualifiedName) -> Self { + let result = Self::from_raw(&value); + Self::free_raw(value); + result + } + + pub fn into_raw(value: Self) -> BNQualifiedName { + let bn_join = BnString::new(&value.separator); + BNQualifiedName { + // NOTE: Leaking string list must be freed by core or us! + name: strings_to_string_list(&value.items), + // NOTE: Leaking string must be freed by core or us! + join: BnString::into_raw(bn_join), + nameCount: value.items.len(), + } + } + + pub(crate) fn free_raw(value: BNQualifiedName) { + unsafe { BnString::free_raw(value.join) }; + unsafe { BNFreeStringList(value.name, value.nameCount) }; + } + + pub fn new(items: Vec) -> Self { + Self::new_with_separator(items, "::".to_string()) + } + + pub fn new_with_separator(items: Vec, separator: String) -> Self { + Self { items, separator } + } + + pub fn with_item(&self, item: impl Into) -> Self { + let mut items = self.items.clone(); + items.push(item.into()); + Self::new_with_separator(items, self.separator.clone()) + } + + pub fn push(&mut self, item: String) { + self.items.push(item); + } + + pub fn pop(&mut self) -> Option { + self.items.pop() + } + + pub fn insert(&mut self, index: usize, item: String) { + if index <= self.items.len() { + self.items.insert(index, item); + } + } + + pub fn split_last(&self) -> Option<(String, QualifiedName)> { + self.items.split_last().map(|(a, b)| { + ( + a.to_owned(), + QualifiedName::new_with_separator(b.to_vec(), self.separator.clone()), + ) + }) + } + + /// Replaces all occurrences of a substring with another string in all items of the `QualifiedName` + /// and returns an owned version of the modified `QualifiedName`. + /// + /// # Example + /// + /// ``` + /// use binaryninja::types::QualifiedName; + /// + /// let qualified_name = + /// QualifiedName::new(vec!["my::namespace".to_string(), "mytype".to_string()]); + /// let replaced = qualified_name.replace("my", "your"); + /// assert_eq!( + /// replaced.items, + /// vec!["your::namespace".to_string(), "yourtype".to_string()] + /// ); + /// ``` + pub fn replace(&self, from: &str, to: &str) -> Self { + Self { + items: self + .items + .iter() + .map(|item| item.replace(from, to)) + .collect(), + separator: self.separator.clone(), + } + } + + /// Returns the last item, or `None` if it is empty. + pub fn last(&self) -> Option<&String> { + self.items.last() + } + + /// Returns a mutable reference to the last item, or `None` if it is empty. + pub fn last_mut(&mut self) -> Option<&mut String> { + self.items.last_mut() + } + + pub fn len(&self) -> usize { + self.items.len() + } + + /// A [`QualifiedName`] is empty if it has no items. + /// + /// If you want to know if the unqualified name is empty (i.e. no characters) + /// you must first convert the qualified name to unqualified via the `to_string` method. + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } +} + +impl From for QualifiedName { + fn from(value: String) -> Self { + Self { + items: vec![value], + // TODO: See comment in struct def. + separator: String::from("::"), + } + } +} + +impl From<&str> for QualifiedName { + fn from(value: &str) -> Self { + Self::from(value.to_string()) + } +} + +impl From<&String> for QualifiedName { + fn from(value: &String) -> Self { + Self::from(value.to_owned()) + } +} + +impl From> for QualifiedName { + fn from(value: Cow<'_, str>) -> Self { + Self::from(value.to_string()) + } +} + +impl From> for QualifiedName { + fn from(value: Vec) -> Self { + Self::new(value) + } +} + +impl From> for QualifiedName { + fn from(value: Vec<&str>) -> Self { + value + .iter() + .map(ToString::to_string) + .collect::>() + .into() + } +} + +impl From for String { + fn from(value: QualifiedName) -> Self { + value.to_string() + } +} + +impl Index for QualifiedName { + type Output = String; + + fn index(&self, index: usize) -> &Self::Output { + &self.items[index] + } +} + +impl IndexMut for QualifiedName { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.items[index] + } +} + +impl Display for QualifiedName { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.items.join(&self.separator)) + } +} + +impl CoreArrayProvider for QualifiedName { + type Raw = BNQualifiedName; + type Context = (); + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for QualifiedName { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeTypeNameList(raw, count); + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + QualifiedName::from_raw(raw) + } +} diff --git a/rust/src/types.rs b/rust/src/types.rs index cf7b6f1aab..417dc554aa 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -43,11 +43,9 @@ use crate::{ }; use crate::confidence::{Conf, MAX_CONFIDENCE, MIN_CONFIDENCE}; -use crate::string::{raw_to_string, strings_to_string_list}; +use crate::string::raw_to_string; use crate::variable::{Variable, VariableSourceType}; -use std::borrow::Cow; use std::num::NonZeroUsize; -use std::ops::{Index, IndexMut}; use std::{ collections::HashSet, fmt::{Debug, Display, Formatter}, @@ -68,6 +66,10 @@ pub use structure::{ BaseStructure, InheritedStructureMember, Structure, StructureBuilder, StructureMember, }; +#[deprecated(note = "Use crate::qualified_name::QualifiedName instead")] +// Re-export QualifiedName so that we do not break public consumers. +pub use crate::qualified_name::QualifiedName; + pub type StructureType = BNStructureVariant; pub type ReferenceType = BNReferenceType; pub type TypeClass = BNTypeClass; @@ -1238,221 +1240,6 @@ impl Debug for NamedTypeReference { } } -// TODO: Document usage, specifically how to make a qualified name and why it exists. -#[derive(Default, Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] -pub struct QualifiedName { - // TODO: Make this Option where default is "::". - pub separator: String, - pub items: Vec, -} - -impl QualifiedName { - pub(crate) fn from_raw(value: &BNQualifiedName) -> Self { - // TODO: This could be improved... - let raw_names = unsafe { std::slice::from_raw_parts(value.name, value.nameCount) }; - let items = raw_names - .iter() - .filter_map(|&raw_name| raw_to_string(raw_name as *const _)) - .collect(); - let separator = raw_to_string(value.join).unwrap(); - Self { items, separator } - } - - pub(crate) fn from_owned_raw(value: BNQualifiedName) -> Self { - let result = Self::from_raw(&value); - Self::free_raw(value); - result - } - - pub fn into_raw(value: Self) -> BNQualifiedName { - let bn_join = BnString::new(&value.separator); - BNQualifiedName { - // NOTE: Leaking string list must be freed by core or us! - name: strings_to_string_list(&value.items), - // NOTE: Leaking string must be freed by core or us! - join: BnString::into_raw(bn_join), - nameCount: value.items.len(), - } - } - - pub(crate) fn free_raw(value: BNQualifiedName) { - unsafe { BnString::free_raw(value.join) }; - unsafe { BNFreeStringList(value.name, value.nameCount) }; - } - - pub fn new(items: Vec) -> Self { - Self::new_with_separator(items, "::".to_string()) - } - - pub fn new_with_separator(items: Vec, separator: String) -> Self { - Self { items, separator } - } - - pub fn with_item(&self, item: impl Into) -> Self { - let mut items = self.items.clone(); - items.push(item.into()); - Self::new_with_separator(items, self.separator.clone()) - } - - pub fn push(&mut self, item: String) { - self.items.push(item); - } - - pub fn pop(&mut self) -> Option { - self.items.pop() - } - - pub fn insert(&mut self, index: usize, item: String) { - if index <= self.items.len() { - self.items.insert(index, item); - } - } - - pub fn split_last(&self) -> Option<(String, QualifiedName)> { - self.items.split_last().map(|(a, b)| { - ( - a.to_owned(), - QualifiedName::new_with_separator(b.to_vec(), self.separator.clone()), - ) - }) - } - - /// Replaces all occurrences of a substring with another string in all items of the `QualifiedName` - /// and returns an owned version of the modified `QualifiedName`. - /// - /// # Example - /// - /// ``` - /// use binaryninja::types::QualifiedName; - /// - /// let qualified_name = - /// QualifiedName::new(vec!["my::namespace".to_string(), "mytype".to_string()]); - /// let replaced = qualified_name.replace("my", "your"); - /// assert_eq!( - /// replaced.items, - /// vec!["your::namespace".to_string(), "yourtype".to_string()] - /// ); - /// ``` - pub fn replace(&self, from: &str, to: &str) -> Self { - Self { - items: self - .items - .iter() - .map(|item| item.replace(from, to)) - .collect(), - separator: self.separator.clone(), - } - } - - /// Returns the last item, or `None` if it is empty. - pub fn last(&self) -> Option<&String> { - self.items.last() - } - - /// Returns a mutable reference to the last item, or `None` if it is empty. - pub fn last_mut(&mut self) -> Option<&mut String> { - self.items.last_mut() - } - - pub fn len(&self) -> usize { - self.items.len() - } - - /// A [`QualifiedName`] is empty if it has no items. - /// - /// If you want to know if the unqualified name is empty (i.e. no characters) - /// you must first convert the qualified name to unqualified via the `to_string` method. - pub fn is_empty(&self) -> bool { - self.items.is_empty() - } -} - -impl From for QualifiedName { - fn from(value: String) -> Self { - Self { - items: vec![value], - // TODO: See comment in struct def. - separator: String::from("::"), - } - } -} - -impl From<&str> for QualifiedName { - fn from(value: &str) -> Self { - Self::from(value.to_string()) - } -} - -impl From<&String> for QualifiedName { - fn from(value: &String) -> Self { - Self::from(value.to_owned()) - } -} - -impl From> for QualifiedName { - fn from(value: Cow<'_, str>) -> Self { - Self::from(value.to_string()) - } -} - -impl From> for QualifiedName { - fn from(value: Vec) -> Self { - Self::new(value) - } -} - -impl From> for QualifiedName { - fn from(value: Vec<&str>) -> Self { - value - .iter() - .map(ToString::to_string) - .collect::>() - .into() - } -} - -impl From for String { - fn from(value: QualifiedName) -> Self { - value.to_string() - } -} - -impl Index for QualifiedName { - type Output = String; - - fn index(&self, index: usize) -> &Self::Output { - &self.items[index] - } -} - -impl IndexMut for QualifiedName { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - &mut self.items[index] - } -} - -impl Display for QualifiedName { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.items.join(&self.separator)) - } -} - -impl CoreArrayProvider for QualifiedName { - type Raw = BNQualifiedName; - type Context = (); - type Wrapped<'a> = Self; -} - -unsafe impl CoreArrayProviderInner for QualifiedName { - unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { - BNFreeTypeNameList(raw, count); - } - - unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { - QualifiedName::from_raw(raw) - } -} - #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct QualifiedNameAndType { pub name: QualifiedName, From 1e6a0a0523ea2d0c4c31cfc192445c740f5e7189 Mon Sep 17 00:00:00 2001 From: Mason Reed Date: Sat, 13 Dec 2025 16:32:17 -0500 Subject: [PATCH 4/4] [Rust] Misc `QualifiedName` cleanup - Added some unit tests - Improved documentation - Made `QualifiedName::new` and `QualifiedName::new_with_separator` generic over `Into` --- rust/src/qualified_name.rs | 61 ++++++++++-- rust/tests/qualified_name.rs | 187 +++++++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+), 9 deletions(-) create mode 100644 rust/tests/qualified_name.rs diff --git a/rust/src/qualified_name.rs b/rust/src/qualified_name.rs index c1d943f866..fc362cf64c 100644 --- a/rust/src/qualified_name.rs +++ b/rust/src/qualified_name.rs @@ -1,3 +1,5 @@ +//! The [`QualifiedName`] is the canonical way to represent structured names in Binary Ninja. + use crate::rc::{CoreArrayProvider, CoreArrayProviderInner}; use crate::string::{raw_to_string, strings_to_string_list, BnString}; use binaryninjacore_sys::*; @@ -5,7 +7,34 @@ use std::borrow::Cow; use std::fmt::{Display, Formatter}; use std::ops::{Index, IndexMut}; -// TODO: Document usage, specifically how to make a qualified name and why it exists. +/// A [`QualifiedName`] represents a name composed of multiple components, typically used for symbols +/// and type names within namespaces, classes, or modules. +/// +/// # Creating a Qualified Name +/// +/// ``` +/// use binaryninja::qualified_name::QualifiedName; +/// +/// // Uses the default separator "::" +/// let qn_vec = QualifiedName::new(vec!["my", "namespace", "func"]); +/// assert_eq!(qn_vec.to_string(), "my::namespace::func"); +/// +/// // Using `QualifiedName::from` will not split on the default separator "::". +/// let qn_from = QualifiedName::from("std::string"); +/// assert_eq!(qn_from.len(), 1); +/// assert_eq!(qn_from.to_string(), "std::string"); +/// ``` +/// +/// # Using a Custom Separator +/// +/// While `::` is the default, you can specify a custom separator: +/// +/// ``` +/// use binaryninja::qualified_name::QualifiedName; +/// +/// let qn = QualifiedName::new_with_separator(["a", "b", "c"], "."); +/// assert_eq!(qn.to_string(), "a.b.c"); +/// ``` #[derive(Default, Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] pub struct QualifiedName { // TODO: Make this Option where default is "::". @@ -14,7 +43,7 @@ pub struct QualifiedName { } impl QualifiedName { - pub(crate) fn from_raw(value: &BNQualifiedName) -> Self { + pub fn from_raw(value: &BNQualifiedName) -> Self { // TODO: This could be improved... let raw_names = unsafe { std::slice::from_raw_parts(value.name, value.nameCount) }; let items = raw_names @@ -25,7 +54,7 @@ impl QualifiedName { Self { items, separator } } - pub(crate) fn from_owned_raw(value: BNQualifiedName) -> Self { + pub fn from_owned_raw(value: BNQualifiedName) -> Self { let result = Self::from_raw(&value); Self::free_raw(value); result @@ -42,17 +71,31 @@ impl QualifiedName { } } - pub(crate) fn free_raw(value: BNQualifiedName) { + pub fn free_raw(value: BNQualifiedName) { unsafe { BnString::free_raw(value.join) }; unsafe { BNFreeStringList(value.name, value.nameCount) }; } - pub fn new(items: Vec) -> Self { - Self::new_with_separator(items, "::".to_string()) + /// Creates a new [`QualifiedName`] with the default separator `::`. + pub fn new(items: I) -> Self + where + I: IntoIterator, + S: Into, + { + Self::new_with_separator(items, "::") } - pub fn new_with_separator(items: Vec, separator: String) -> Self { - Self { items, separator } + /// Creates a new `QualifiedName` with a custom separator. + pub fn new_with_separator(items: I, separator: impl Into) -> Self + where + I: IntoIterator, + S: Into, + { + let items = items.into_iter().map(Into::into).collect::>(); + Self { + items, + separator: separator.into(), + } } pub fn with_item(&self, item: impl Into) -> Self { @@ -90,7 +133,7 @@ impl QualifiedName { /// # Example /// /// ``` - /// use binaryninja::types::QualifiedName; + /// use binaryninja::qualified_name::QualifiedName; /// /// let qualified_name = /// QualifiedName::new(vec!["my::namespace".to_string(), "mytype".to_string()]); diff --git a/rust/tests/qualified_name.rs b/rust/tests/qualified_name.rs new file mode 100644 index 0000000000..c0d16a75bb --- /dev/null +++ b/rust/tests/qualified_name.rs @@ -0,0 +1,187 @@ +use binaryninja::qualified_name::QualifiedName; + +#[test] +fn test_new_from_vec_string() { + let items = vec!["std".to_string(), "vector".to_string(), "int".to_string()]; + let qn = QualifiedName::new(items); + assert_eq!(qn.len(), 3); + assert_eq!(qn.to_string(), "std::vector::int"); + assert_eq!(qn.separator, "::"); +} + +#[test] +fn test_new_from_array() { + let items = ["root", "data"]; + let qn = QualifiedName::new(items); + assert_eq!(qn.len(), 2); + assert_eq!(qn.to_string(), "root::data"); +} + +#[test] +fn test_new_empty() { + let items: Vec = Vec::new(); + let qn = QualifiedName::new(items); + assert!(qn.is_empty()); + assert_eq!(qn.to_string(), ""); +} + +#[test] +fn test_new_with_custom_separator() { + let items = vec!["a".to_string(), "b".to_string(), "c".to_string()]; + let separator = ".".to_string(); + let qn = QualifiedName::new_with_separator(items, separator); + assert_eq!(qn.to_string(), "a.b.c"); + assert_eq!(qn.separator, "."); +} + +#[test] +fn test_from_string() { + let qn: QualifiedName = "SingleName".to_string().into(); + assert_eq!(qn.len(), 1); + assert_eq!(qn[0], "SingleName"); + assert_eq!(qn.to_string(), "SingleName"); +} + +#[test] +fn test_from_str_literal() { + let qn: QualifiedName = "AnotherName".into(); + assert_eq!(qn.len(), 1); + assert_eq!(qn[0], "AnotherName"); +} + +#[test] +fn test_into_string() { + let qn = QualifiedName::new(vec!["A", "B", "C"]); + let s: String = qn.into(); + assert_eq!(s, "A::B::C"); +} + +#[test] +fn test_push_pop_and_last() { + let mut qn = QualifiedName::new(vec!["ns1"]); + + qn.push("TypeA".to_string()); + assert_eq!(qn.len(), 2); + assert_eq!(qn.to_string(), "ns1::TypeA"); + + assert_eq!(qn.last().unwrap(), "TypeA"); + *qn.last_mut().unwrap() = "TypeB".to_string(); + assert_eq!(qn.to_string(), "ns1::TypeB"); + + assert_eq!(qn.pop().unwrap(), "TypeB"); + assert_eq!(qn.len(), 1); + assert_eq!(qn.to_string(), "ns1"); + + assert_eq!(qn.pop().unwrap(), "ns1"); + assert!(qn.is_empty()); + assert_eq!(qn.pop(), None); +} + +#[test] +fn test_with_item() { + let qn_a = QualifiedName::from("ns1"); + let qn_b = qn_a.with_item("ns2"); + let qn_c = qn_b.with_item("symbol"); + + assert_eq!(qn_a.to_string(), "ns1"); + assert_eq!(qn_b.to_string(), "ns1::ns2"); + assert_eq!(qn_c.to_string(), "ns1::ns2::symbol"); + + // Ensure the original is unchanged + assert_eq!(qn_a.len(), 1); +} + +#[test] +fn test_split_last() { + let qn = QualifiedName::new(vec!["A", "B", "C"]); + + let (last, prefix) = qn.split_last().unwrap(); + assert_eq!(last, "C"); + assert_eq!(prefix.to_string(), "A::B"); + + let (last2, prefix2) = prefix.split_last().unwrap(); + assert_eq!(last2, "B"); + assert_eq!(prefix2.to_string(), "A"); + + let (last3, prefix3) = prefix2.split_last().unwrap(); + assert_eq!(last3, "A"); + assert!(prefix3.is_empty()); + + // Split on an empty name + assert_eq!(prefix3.split_last(), None); +} + +#[test] +fn test_replace() { + let qualified_name = + QualifiedName::new(vec!["my_prefix::ns".to_string(), "my_Type".to_string()]); + + let replaced = qualified_name.replace("my", "your"); + assert_eq!( + replaced.items, + vec!["your_prefix::ns".to_string(), "your_Type".to_string()] + ); + assert_eq!(replaced.to_string(), "your_prefix::ns::your_Type"); + + let qualified_name_dot = QualifiedName::new_with_separator(vec!["foo", "bar"], ".".to_string()); + let replaced_dot = qualified_name_dot.replace("foo", "baz"); + assert_eq!(replaced_dot.to_string(), "baz.bar"); +} + +#[test] +fn test_index_and_index_mut() { + let mut qn = QualifiedName::new(vec!["a", "b", "c"]); + assert_eq!(qn[0], "a"); + assert_eq!(qn[2], "c"); + qn[1] = "NEW_ITEM".to_string(); + assert_eq!(qn.to_string(), "a::NEW_ITEM::c"); +} + +#[test] +#[should_panic] +fn test_index_out_of_bounds() { + let qn = QualifiedName::new(vec!["a"]); + // This should panic + let _ = qn[1]; +} + +#[test] +fn test_insert() { + let mut qn = QualifiedName::new(vec!["start", "end"]); + + qn.insert(1, "middle".to_string()); + assert_eq!(qn.to_string(), "start::middle::end"); + + qn.insert(0, "HEAD".to_string()); + assert_eq!(qn.to_string(), "HEAD::start::middle::end"); + + qn.insert(4, "TAIL".to_string()); + assert_eq!(qn.to_string(), "HEAD::start::middle::end::TAIL"); + + let initial_len = qn.len(); + qn.insert(initial_len + 5, "NOPE".to_string()); + assert_eq!(qn.len(), initial_len); +} + +#[test] +fn test_into_and_from_raw() { + let original_qn = QualifiedName::new(["std", "vector"]); + assert_eq!(original_qn.to_string(), "std::vector"); + + let raw_qn = QualifiedName::into_raw(original_qn); + assert_eq!(raw_qn.nameCount, 2); + assert!(!raw_qn.join.is_null()); + + let restored_qn = QualifiedName::from_owned_raw(raw_qn); + assert_eq!(restored_qn.len(), 2); + assert_eq!(restored_qn.to_string(), "std::vector"); +} + +#[test] +fn test_raw_freeing() { + let qn = QualifiedName::new(["std", "vector"]); + let raw_qn = QualifiedName::into_raw(qn); + assert!(!raw_qn.join.is_null()); + assert!(!raw_qn.name.is_null()); + QualifiedName::free_raw(raw_qn); +}