From ca0e32efb1e5b9eba157e9fd2eef178d9367c578 Mon Sep 17 00:00:00 2001 From: Mason Reed Date: Thu, 11 Dec 2025 14:45:23 -0500 Subject: [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`. --- 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/type_archive.rs | 1165 ---------------------------------- rust/src/type_container.rs | 410 ------------ rust/src/type_library.rs | 365 ----------- rust/src/type_parser.rs | 688 -------------------- rust/src/type_printer.rs | 977 ----------------------------- rust/src/types.rs | 923 +-------------------------- rust/src/types/archive.rs | 1174 +++++++++++++++++++++++++++++++++++ rust/src/types/container.rs | 409 ++++++++++++ rust/src/types/enumeration.rs | 201 ++++++ rust/src/types/library.rs | 365 +++++++++++ rust/src/types/parser.rs | 687 ++++++++++++++++++++ rust/src/types/printer.rs | 976 +++++++++++++++++++++++++++++ rust/src/types/structure.rs | 696 +++++++++++++++++++++ 21 files changed, 4566 insertions(+), 4534 deletions(-) delete mode 100644 rust/src/type_archive.rs delete mode 100644 rust/src/type_container.rs delete mode 100644 rust/src/type_library.rs delete mode 100644 rust/src/type_parser.rs delete mode 100644 rust/src/type_printer.rs create mode 100644 rust/src/types/archive.rs create mode 100644 rust/src/types/container.rs create mode 100644 rust/src/types/enumeration.rs create mode 100644 rust/src/types/library.rs create mode 100644 rust/src/types/parser.rs create mode 100644 rust/src/types/printer.rs create mode 100644 rust/src/types/structure.rs (limited to 'rust/src') diff --git a/rust/src/binary_view.rs b/rust/src/binary_view.rs index a48d0264..459253b9 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 a8006b8b..72c57a46 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 4c5d5992..f038d5c3 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 7749e792..d01a5ed3 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 34524b96..d39cb95f 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 f3166033..2d9cab27 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 609f4edc..ed4f49f4 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 c7d541ce..8505cbf5 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/type_archive.rs b/rust/src/type_archive.rs deleted file mode 100644 index 4a047205..00000000 --- a/rust/src/type_archive.rs +++ /dev/null @@ -1,1165 +0,0 @@ -use crate::progress::{NoProgressCallback, ProgressCallback}; -use binaryninjacore_sys::*; -use std::ffi::{c_char, c_void, CStr}; -use std::fmt::{Debug, Display, Formatter}; -use std::hash::Hash; -use std::path::{Path, PathBuf}; -use std::ptr::NonNull; - -use crate::data_buffer::DataBuffer; -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}; - -#[repr(transparent)] -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct TypeArchiveId(pub String); - -impl Display for TypeArchiveId { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{}", self.0)) - } -} - -#[repr(transparent)] -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct TypeArchiveSnapshotId(pub String); - -impl TypeArchiveSnapshotId { - pub fn unset() -> Self { - Self("".to_string()) - } -} - -impl Display for TypeArchiveSnapshotId { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("{}", self.0)) - } -} - -impl CoreArrayProvider for TypeArchiveSnapshotId { - type Raw = *mut c_char; - type Context = (); - type Wrapped<'a> = TypeArchiveSnapshotId; -} - -unsafe impl CoreArrayProviderInner for TypeArchiveSnapshotId { - unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { - BNFreeStringList(raw, count) - } - - unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { - let str = CStr::from_ptr(*raw).to_str().unwrap().to_string(); - TypeArchiveSnapshotId(str) - } -} - -/// 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. -pub struct TypeArchive { - pub(crate) handle: NonNull, -} - -impl TypeArchive { - pub(crate) unsafe fn from_raw(handle: NonNull) -> Self { - Self { handle } - } - - pub(crate) unsafe fn ref_from_raw(handle: NonNull) -> Ref { - Ref::new(Self { handle }) - } - - /// Open the Type Archive at the given path, if it exists. - pub fn open(path: impl AsRef) -> Option> { - let raw_path = path.as_ref().to_cstr(); - let handle = unsafe { BNOpenTypeArchive(raw_path.as_ptr()) }; - NonNull::new(handle).map(|handle| unsafe { TypeArchive::ref_from_raw(handle) }) - } - - /// Create a Type Archive at the given path, returning `None` if it could not be created. - /// - /// If the file has already been created and is not a valid type archive this will return `None`. - pub fn create(path: impl AsRef, platform: &Platform) -> Option> { - let raw_path = path.as_ref().to_cstr(); - let handle = unsafe { BNCreateTypeArchive(raw_path.as_ptr(), platform.handle) }; - NonNull::new(handle).map(|handle| unsafe { TypeArchive::ref_from_raw(handle) }) - } - - /// Create a Type Archive at the given path and id, returning `None` if it could not be created. - /// - /// If the file has already been created and is not a valid type archive this will return `None`. - pub fn create_with_id( - path: impl AsRef, - id: &TypeArchiveId, - platform: &Platform, - ) -> Option> { - let raw_path = path.as_ref().to_cstr(); - let id = id.0.as_str().to_cstr(); - let handle = - unsafe { BNCreateTypeArchiveWithId(raw_path.as_ptr(), platform.handle, id.as_ptr()) }; - NonNull::new(handle).map(|handle| unsafe { TypeArchive::ref_from_raw(handle) }) - } - - /// Get a reference to the Type Archive with the known id, if one exists. - pub fn lookup_by_id(id: &TypeArchiveId) -> Option> { - let id = id.0.as_str().to_cstr(); - let handle = unsafe { BNLookupTypeArchiveById(id.as_ptr()) }; - NonNull::new(handle).map(|handle| unsafe { TypeArchive::ref_from_raw(handle) }) - } - - /// Get the path to the Type Archive's file - pub fn path(&self) -> Option { - let result = unsafe { BNGetTypeArchivePath(self.handle.as_ptr()) }; - assert!(!result.is_null()); - let path_str = unsafe { BnString::into_string(result) }; - Some(PathBuf::from(path_str)) - } - - /// Get the guid for a Type Archive - pub fn id(&self) -> TypeArchiveId { - let result = unsafe { BNGetTypeArchiveId(self.handle.as_ptr()) }; - assert!(!result.is_null()); - let result_str = unsafe { BnString::from_raw(result) }; - TypeArchiveId(result_str.to_string_lossy().to_string()) - } - - /// Get the associated Platform for a Type Archive - pub fn platform(&self) -> Ref { - let result = unsafe { BNGetTypeArchivePlatform(self.handle.as_ptr()) }; - assert!(!result.is_null()); - unsafe { Platform::ref_from_raw(result) } - } - - /// Get the id of the current snapshot in the type archive - pub fn current_snapshot_id(&self) -> TypeArchiveSnapshotId { - let result = unsafe { BNGetTypeArchiveCurrentSnapshotId(self.handle.as_ptr()) }; - assert!(!result.is_null()); - let id = unsafe { BnString::into_string(result) }; - TypeArchiveSnapshotId(id) - } - - /// Revert the type archive's current snapshot to the given snapshot - pub fn set_current_snapshot_id(&self, id: &TypeArchiveSnapshotId) { - let snapshot = id.clone().to_cstr(); - unsafe { BNSetTypeArchiveCurrentSnapshot(self.handle.as_ptr(), snapshot.as_ptr()) } - } - - /// Get a list of every snapshot's id - pub fn all_snapshot_ids(&self) -> Array { - let mut count = 0; - let result = unsafe { BNGetTypeArchiveAllSnapshotIds(self.handle.as_ptr(), &mut count) }; - assert!(!result.is_null()); - unsafe { Array::new(result, count, ()) } - } - - /// Get the ids of the parents to the given snapshot - pub fn get_snapshot_parent_ids( - &self, - snapshot: &TypeArchiveSnapshotId, - ) -> Option> { - let mut count = 0; - let snapshot = snapshot.clone().to_cstr(); - let result = unsafe { - BNGetTypeArchiveSnapshotParentIds(self.handle.as_ptr(), snapshot.as_ptr(), &mut count) - }; - (!result.is_null()).then(|| unsafe { Array::new(result, count, ()) }) - } - - /// Get the ids of the children to the given snapshot - pub fn get_snapshot_child_ids( - &self, - snapshot: &TypeArchiveSnapshotId, - ) -> Option> { - let mut count = 0; - let snapshot = snapshot.clone().to_cstr(); - let result = unsafe { - BNGetTypeArchiveSnapshotChildIds(self.handle.as_ptr(), snapshot.as_ptr(), &mut count) - }; - (!result.is_null()).then(|| unsafe { Array::new(result, count, ()) }) - } - - /// Add a named type to the type archive. Type must have all dependant named types added - /// prior to being added, or this function will fail. - /// If the type already exists, it will be overwritten. - /// - /// * `named_type` - Named type to add - pub fn add_type(&self, named_type: QualifiedNameAndType) -> bool { - self.add_types(vec![named_type]) - } - - /// Add named types to the type archive. Types must have all dependant named - /// types prior to being added, or included in the list, or this function will fail. - /// Types already existing with any added names will be overwritten. - /// - /// * `named_types` - Names and definitions of new types - pub fn add_types(&self, named_types: Vec) -> bool { - let new_types_raw: Vec<_> = named_types - .into_iter() - .map(QualifiedNameAndType::into_raw) - .collect(); - let result = unsafe { - BNAddTypeArchiveTypes( - self.handle.as_ptr(), - new_types_raw.as_ptr(), - new_types_raw.len(), - ) - }; - for new_type in new_types_raw { - QualifiedNameAndType::free_raw(new_type); - } - result - } - - /// Change the name of an existing type in the type archive. Returns false if failed. - /// - /// * `old_name` - Old type name in archive - /// * `new_name` - New type name - pub fn rename_type(&self, old_name: QualifiedName, new_name: QualifiedName) -> bool { - if let Some(id) = self.get_type_id(old_name) { - self.rename_type_by_id(&id, new_name) - } else { - false - } - } - - /// Change the name of an existing type in the type archive. Returns false if failed. - /// - /// * `id` - Old id of type in archive - /// * `new_name` - New type name - pub fn rename_type_by_id(&self, id: &str, new_name: QualifiedName) -> bool { - let id = id.to_cstr(); - let raw_name = QualifiedName::into_raw(new_name); - let result = - unsafe { BNRenameTypeArchiveType(self.handle.as_ptr(), id.as_ptr(), &raw_name) }; - QualifiedName::free_raw(raw_name); - result - } - - /// Delete an existing type in the type archive. - pub fn delete_type(&self, name: QualifiedName) -> bool { - if let Some(type_id) = self.get_type_id(name) { - self.delete_type_by_id(&type_id) - } else { - false - } - } - - /// Delete an existing type in the type archive. - pub fn delete_type_by_id(&self, id: &str) -> bool { - let id = id.to_cstr(); - unsafe { BNDeleteTypeArchiveType(self.handle.as_ptr(), id.as_ptr()) } - } - - /// Retrieve a stored type in the archive - /// - /// * `name` - Type name - pub fn get_type_by_name(&self, name: QualifiedName) -> Option> { - self.get_type_by_name_from_snapshot(name, &TypeArchiveSnapshotId::unset()) - } - - /// Retrieve a stored type in the archive - /// - /// * `name` - Type name - /// * `snapshot` - Snapshot id to search for types - pub fn get_type_by_name_from_snapshot( - &self, - name: QualifiedName, - snapshot: &TypeArchiveSnapshotId, - ) -> Option> { - let raw_name = QualifiedName::into_raw(name); - let snapshot = snapshot.clone().to_cstr(); - let result = unsafe { - BNGetTypeArchiveTypeByName(self.handle.as_ptr(), &raw_name, snapshot.as_ptr()) - }; - QualifiedName::free_raw(raw_name); - (!result.is_null()).then(|| unsafe { Type::ref_from_raw(result) }) - } - - /// Retrieve a stored type in the archive by id - /// - /// * `id` - Type id - pub fn get_type_by_id(&self, id: &str) -> Option> { - self.get_type_by_id_from_snapshot(id, &TypeArchiveSnapshotId::unset()) - } - - /// Retrieve a stored type in the archive by id - /// - /// * `id` - Type id - /// * `snapshot` - Snapshot id to search for types - pub fn get_type_by_id_from_snapshot( - &self, - id: &str, - snapshot: &TypeArchiveSnapshotId, - ) -> Option> { - let id = id.to_cstr(); - let snapshot = snapshot.clone().to_cstr(); - let result = unsafe { - BNGetTypeArchiveTypeById(self.handle.as_ptr(), id.as_ptr(), snapshot.as_ptr()) - }; - (!result.is_null()).then(|| unsafe { Type::ref_from_raw(result) }) - } - - /// Retrieve a type's name by its id - /// - /// * `id` - Type id - pub fn get_type_name_by_id(&self, id: &str) -> QualifiedName { - self.get_type_name_by_id_from_snapshot(id, &TypeArchiveSnapshotId::unset()) - } - - /// Retrieve a type's name by its id - /// - /// * `id` - Type id - /// * `snapshot` - Snapshot id to search for types - pub fn get_type_name_by_id_from_snapshot( - &self, - id: &str, - snapshot: &TypeArchiveSnapshotId, - ) -> QualifiedName { - let id = id.to_cstr(); - let snapshot = snapshot.clone().to_cstr(); - let result = unsafe { - BNGetTypeArchiveTypeName(self.handle.as_ptr(), id.as_ptr(), snapshot.as_ptr()) - }; - QualifiedName::from_owned_raw(result) - } - - /// Retrieve a type's id by its name - /// - /// * `name` - Type name - pub fn get_type_id(&self, name: QualifiedName) -> Option { - self.get_type_id_from_snapshot(name, &TypeArchiveSnapshotId::unset()) - } - - /// Retrieve a type's id by its name - /// - /// * `name` - Type name - /// * `snapshot` - Snapshot id to search for types - pub fn get_type_id_from_snapshot( - &self, - name: QualifiedName, - snapshot: &TypeArchiveSnapshotId, - ) -> Option { - let raw_name = QualifiedName::into_raw(name); - let snapshot = snapshot.clone().to_cstr(); - let result = - unsafe { BNGetTypeArchiveTypeId(self.handle.as_ptr(), &raw_name, snapshot.as_ptr()) }; - QualifiedName::free_raw(raw_name); - (!result.is_null()).then(|| unsafe { BnString::into_string(result) }) - } - - /// Retrieve all stored types in the archive at a snapshot - pub fn get_types_and_ids(&self) -> Array { - self.get_types_and_ids_from_snapshot(&TypeArchiveSnapshotId::unset()) - } - - /// Retrieve all stored types in the archive at a snapshot - /// - /// * `snapshot` - Snapshot id to search for types - pub fn get_types_and_ids_from_snapshot( - &self, - snapshot: &TypeArchiveSnapshotId, - ) -> Array { - let mut count = 0; - let snapshot = snapshot.clone().to_cstr(); - let result = - unsafe { BNGetTypeArchiveTypes(self.handle.as_ptr(), snapshot.as_ptr(), &mut count) }; - assert!(!result.is_null()); - unsafe { Array::new(result, count, ()) } - } - - /// Get a list of all types' ids in the archive at a snapshot - pub fn get_type_ids(&self) -> Array { - self.get_type_ids_from_snapshot(&TypeArchiveSnapshotId::unset()) - } - - /// Get a list of all types' ids in the archive at a snapshot - /// - /// * `snapshot` - Snapshot id to search for types - pub fn get_type_ids_from_snapshot(&self, snapshot: &TypeArchiveSnapshotId) -> Array { - let mut count = 0; - let snapshot = snapshot.clone().to_cstr(); - let result = - unsafe { BNGetTypeArchiveTypeIds(self.handle.as_ptr(), snapshot.as_ptr(), &mut count) }; - assert!(!result.is_null()); - unsafe { Array::new(result, count, ()) } - } - - /// Get a list of all types' names in the archive at a snapshot - pub fn get_type_names(&self) -> Array { - self.get_type_names_from_snapshot(&TypeArchiveSnapshotId::unset()) - } - - /// Get a list of all types' names in the archive at a snapshot - /// - /// * `snapshot` - Snapshot id to search for types - pub fn get_type_names_from_snapshot( - &self, - snapshot: &TypeArchiveSnapshotId, - ) -> Array { - let mut count = 0; - let snapshot = snapshot.clone().to_cstr(); - let result = unsafe { - BNGetTypeArchiveTypeNames(self.handle.as_ptr(), snapshot.as_ptr(), &mut count) - }; - assert!(!result.is_null()); - unsafe { Array::new(result, count, ()) } - } - - /// Get a list of all types' names and ids in the archive at the latest snapshot - pub fn get_type_names_and_ids(&self) -> (Array, Array) { - self.get_type_names_and_ids_from_snapshot(&TypeArchiveSnapshotId::unset()) - } - - /// Get a list of all types' names and ids in the archive at a specific snapshot - /// - /// * `snapshot` - Snapshot id to search for types - pub fn get_type_names_and_ids_from_snapshot( - &self, - snapshot: &TypeArchiveSnapshotId, - ) -> (Array, Array) { - let mut count = 0; - let snapshot = snapshot.clone().to_cstr(); - let mut names = std::ptr::null_mut(); - let mut ids = std::ptr::null_mut(); - let result = unsafe { - BNGetTypeArchiveTypeNamesAndIds( - self.handle.as_ptr(), - snapshot.as_ptr(), - &mut names, - &mut ids, - &mut count, - ) - }; - assert!(result); - (unsafe { Array::new(names, count, ()) }, unsafe { - Array::new(ids, count, ()) - }) - } - - /// Get all types a given type references directly - /// - /// * `id` - Source type id - pub fn get_outgoing_direct_references(&self, id: &str) -> Array { - self.get_outgoing_direct_references_from_snapshot(id, &TypeArchiveSnapshotId::unset()) - } - - /// Get all types a given type references directly - /// - /// * `id` - Source type id - /// * `snapshot` - Snapshot id to search for types - pub fn get_outgoing_direct_references_from_snapshot( - &self, - id: &str, - snapshot: &TypeArchiveSnapshotId, - ) -> Array { - let id = id.to_cstr(); - let snapshot = snapshot.clone().to_cstr(); - let mut count = 0; - let result = unsafe { - BNGetTypeArchiveOutgoingDirectTypeReferences( - self.handle.as_ptr(), - id.as_ptr(), - snapshot.as_ptr(), - &mut count, - ) - }; - assert!(!result.is_null()); - unsafe { Array::new(result, count, ()) } - } - - /// Get all types a given type references, and any types that the referenced types reference - /// - /// * `id` - Source type id - pub fn get_outgoing_recursive_references(&self, id: &str) -> Array { - self.get_outgoing_recursive_references_from_snapshot(id, &TypeArchiveSnapshotId::unset()) - } - - /// Get all types a given type references, and any types that the referenced types reference - /// - /// * `id` - Source type id - /// * `snapshot` - Snapshot id to search for types - pub fn get_outgoing_recursive_references_from_snapshot( - &self, - id: &str, - snapshot: &TypeArchiveSnapshotId, - ) -> Array { - let id = id.to_cstr(); - let snapshot = snapshot.clone().to_cstr(); - let mut count = 0; - let result = unsafe { - BNGetTypeArchiveOutgoingRecursiveTypeReferences( - self.handle.as_ptr(), - id.as_ptr(), - snapshot.as_ptr(), - &mut count, - ) - }; - assert!(!result.is_null()); - unsafe { Array::new(result, count, ()) } - } - - /// Get all types that reference a given type - /// - /// * `id` - Target type id - pub fn get_incoming_direct_references(&self, id: &str) -> Array { - self.get_incoming_direct_references_with_snapshot(id, &TypeArchiveSnapshotId::unset()) - } - - /// Get all types that reference a given type - /// - /// * `id` - Target type id - /// * `snapshot` - Snapshot id to search for types - pub fn get_incoming_direct_references_with_snapshot( - &self, - id: &str, - snapshot: &TypeArchiveSnapshotId, - ) -> Array { - let id = id.to_cstr(); - let snapshot = snapshot.clone().to_cstr(); - let mut count = 0; - let result = unsafe { - BNGetTypeArchiveIncomingDirectTypeReferences( - self.handle.as_ptr(), - id.as_ptr(), - snapshot.as_ptr(), - &mut count, - ) - }; - assert!(!result.is_null()); - unsafe { Array::new(result, count, ()) } - } - - /// Get all types that reference a given type, and all types that reference them, recursively - /// - /// * `id` - Target type id - pub fn get_incoming_recursive_references(&self, id: &str) -> Array { - self.get_incoming_recursive_references_with_snapshot(id, &TypeArchiveSnapshotId::unset()) - } - - /// Get all types that reference a given type, and all types that reference them, recursively - /// - /// * `id` - Target type id - /// * `snapshot` - Snapshot id to search for types, or empty string to search the latest snapshot - pub fn get_incoming_recursive_references_with_snapshot( - &self, - id: &str, - snapshot: &TypeArchiveSnapshotId, - ) -> Array { - let id = id.to_cstr(); - let snapshot = snapshot.clone().to_cstr(); - let mut count = 0; - let result = unsafe { - BNGetTypeArchiveIncomingRecursiveTypeReferences( - self.handle.as_ptr(), - id.as_ptr(), - snapshot.as_ptr(), - &mut count, - ) - }; - assert!(!result.is_null()); - unsafe { Array::new(result, count, ()) } - } - - /// Look up a metadata entry in the archive - pub fn query_metadata(&self, key: &str) -> Option> { - let key = key.to_cstr(); - let result = unsafe { BNTypeArchiveQueryMetadata(self.handle.as_ptr(), key.as_ptr()) }; - (!result.is_null()).then(|| unsafe { Metadata::ref_from_raw(result) }) - } - - /// Store a key/value pair in the archive's metadata storage - /// - /// * `key` - key value to associate the Metadata object with - /// * `md` - object to store. - pub fn store_metadata(&self, key: &str, md: &Metadata) { - let key = key.to_cstr(); - let result = - unsafe { BNTypeArchiveStoreMetadata(self.handle.as_ptr(), key.as_ptr(), md.handle) }; - assert!(result); - } - - /// Delete a given metadata entry in the archive from the `key` - pub fn remove_metadata(&self, key: &str) -> bool { - let key = key.to_cstr(); - unsafe { BNTypeArchiveRemoveMetadata(self.handle.as_ptr(), key.as_ptr()) } - } - - /// Turn a given `snapshot` id into a data stream - pub fn serialize_snapshot(&self, snapshot: &TypeArchiveSnapshotId) -> DataBuffer { - let snapshot = snapshot.clone().to_cstr(); - let result = - unsafe { BNTypeArchiveSerializeSnapshot(self.handle.as_ptr(), snapshot.as_ptr()) }; - assert!(!result.is_null()); - DataBuffer::from_raw(result) - } - - /// Take a serialized snapshot `data` stream and create a new snapshot from it - pub fn deserialize_snapshot(&self, data: &DataBuffer) -> TypeArchiveSnapshotId { - let result = - unsafe { BNTypeArchiveDeserializeSnapshot(self.handle.as_ptr(), data.as_raw()) }; - assert!(!result.is_null()); - let id = unsafe { BnString::into_string(result) }; - TypeArchiveSnapshotId(id) - } - - /// Register a notification listener - pub fn register_notification_callback( - &self, - callback: T, - ) -> TypeArchiveCallbackHandle { - // SAFETY free on [TypeArchiveCallbackHandle::Drop] - let callback = Box::leak(Box::new(callback)); - let mut notification = BNTypeArchiveNotification { - context: callback as *mut T as *mut c_void, - typeAdded: Some(cb_type_added::), - typeUpdated: Some(cb_type_updated::), - typeRenamed: Some(cb_type_renamed::), - typeDeleted: Some(cb_type_deleted::), - }; - unsafe { BNRegisterTypeArchiveNotification(self.handle.as_ptr(), &mut notification) } - TypeArchiveCallbackHandle { - callback, - type_archive: self.to_owned(), - } - } - - // NOTE NotificationClosure is left private, there is no need for the user - // to know or use it. - #[allow(private_interfaces)] - pub fn register_notification_closure( - &self, - type_added: A, - type_updated: U, - type_renamed: R, - type_deleted: D, - ) -> TypeArchiveCallbackHandle> - where - A: FnMut(&TypeArchive, &str, &Type), - U: FnMut(&TypeArchive, &str, &Type, &Type), - R: FnMut(&TypeArchive, &str, &QualifiedName, &QualifiedName), - D: FnMut(&TypeArchive, &str, &Type), - { - self.register_notification_callback(NotificationClosure { - fun_type_added: type_added, - fun_type_updated: type_updated, - fun_type_renamed: type_renamed, - fun_type_deleted: type_deleted, - }) - } - - /// Close a type archive, disconnecting it from any active views and closing - /// any open file handles - pub fn close(&self) { - unsafe { BNCloseTypeArchive(self.handle.as_ptr()) } - } - - /// Determine if `file` is a Type Archive - pub fn is_type_archive(file: &Path) -> bool { - let file = file.to_cstr(); - unsafe { BNIsTypeArchive(file.as_ptr()) } - } - - ///// Get the TypeContainer interface for this Type Archive, presenting types - ///// at the current snapshot in the archive. - pub fn type_container(&self) -> TypeContainer { - let result = unsafe { BNGetTypeArchiveTypeContainer(self.handle.as_ptr()) }; - unsafe { TypeContainer::from_raw(NonNull::new(result).unwrap()) } - } - - /// Do some function in a transaction making a new snapshot whose id is passed to func. If func throws, - /// the transaction will be rolled back and the snapshot will not be created. - /// - /// * `func` - Function to call - /// * `parents` - Parent snapshot ids - /// - /// Returns Created snapshot id - pub fn new_snapshot_transaction( - &self, - mut function: F, - parents: &[TypeArchiveSnapshotId], - ) -> TypeArchiveSnapshotId - where - F: FnMut(&TypeArchiveSnapshotId) -> bool, - { - unsafe extern "C" fn cb_callback bool>( - ctxt: *mut c_void, - id: *const c_char, - ) -> bool { - let fun: &mut F = &mut *(ctxt as *mut F); - let id_str = raw_to_string(id).unwrap(); - fun(&TypeArchiveSnapshotId(id_str)) - } - - let parents_cstr: Vec<_> = parents.iter().map(|p| p.clone().to_cstr()).collect(); - let parents_raw: Vec<_> = parents_cstr.iter().map(|p| p.as_ptr()).collect(); - let result = unsafe { - BNTypeArchiveNewSnapshotTransaction( - self.handle.as_ptr(), - Some(cb_callback::), - &mut function as *mut F as *mut c_void, - parents_raw.as_ptr(), - parents.len(), - ) - }; - assert!(!result.is_null()); - let id_str = unsafe { BnString::into_string(result) }; - TypeArchiveSnapshotId(id_str) - } - - /// Merge two snapshots in the archive to produce a new snapshot - /// - /// * `base_snapshot` - Common ancestor of snapshots - /// * `first_snapshot` - First snapshot to merge - /// * `second_snapshot` - Second snapshot to merge - /// * `merge_conflicts` - List of all conflicting types, id <-> target snapshot - /// * `progress` - Function to call for progress updates - /// - /// Returns Snapshot id, if merge was successful, otherwise the List of - /// conflicting type ids - pub fn merge_snapshots( - &self, - base_snapshot: &TypeArchiveSnapshotId, - first_snapshot: &TypeArchiveSnapshotId, - second_snapshot: &TypeArchiveSnapshotId, - merge_conflicts: M, - ) -> Result> - where - M: IntoIterator, - { - self.merge_snapshots_with_progress( - base_snapshot, - first_snapshot, - second_snapshot, - merge_conflicts, - NoProgressCallback, - ) - } - - /// Merge two snapshots in the archive to produce a new snapshot - /// - /// * `base_snapshot` - Common ancestor of snapshots - /// * `first_snapshot` - First snapshot to merge - /// * `second_snapshot` - Second snapshot to merge - /// * `merge_conflicts` - List of all conflicting types, id <-> target snapshot - /// * `progress` - Function to call for progress updates - /// - /// Returns Snapshot id, if merge was successful, otherwise the List of - /// conflicting type ids - pub fn merge_snapshots_with_progress( - &self, - base_snapshot: &TypeArchiveSnapshotId, - first_snapshot: &TypeArchiveSnapshotId, - second_snapshot: &TypeArchiveSnapshotId, - merge_conflicts: M, - mut progress: PC, - ) -> Result> - where - M: IntoIterator, - PC: ProgressCallback, - { - let base_snapshot = base_snapshot.0.as_str().to_cstr(); - let first_snapshot = first_snapshot.0.as_str().to_cstr(); - let second_snapshot = second_snapshot.0.as_str().to_cstr(); - let (merge_keys, merge_values): (Vec, Vec) = merge_conflicts - .into_iter() - .map(|(k, v)| (BnString::new(k), BnString::new(v))) - .unzip(); - // SAFETY BnString and `*const c_char` are transparent - let merge_keys_raw = merge_keys.as_ptr() as *const *const c_char; - let merge_values_raw = merge_values.as_ptr() as *const *const c_char; - - let mut conflicts_errors = std::ptr::null_mut(); - let mut conflicts_errors_count = 0; - - let mut result = std::ptr::null_mut(); - - let success = unsafe { - BNTypeArchiveMergeSnapshots( - self.handle.as_ptr(), - base_snapshot.as_ptr(), - first_snapshot.as_ptr(), - second_snapshot.as_ptr(), - merge_keys_raw, - merge_values_raw, - merge_keys.len(), - &mut conflicts_errors, - &mut conflicts_errors_count, - &mut result, - Some(PC::cb_progress_callback), - &mut progress as *mut PC as *mut c_void, - ) - }; - - if success { - assert!(!result.is_null()); - Ok(unsafe { BnString::from_raw(result) }) - } else { - assert!(!conflicts_errors.is_null()); - Err(unsafe { Array::new(conflicts_errors, conflicts_errors_count, ()) }) - } - } -} - -impl ToOwned for TypeArchive { - type Owned = Ref; - - fn to_owned(&self) -> Self::Owned { - unsafe { RefCountable::inc_ref(self) } - } -} - -unsafe impl RefCountable for TypeArchive { - unsafe fn inc_ref(handle: &Self) -> Ref { - Ref::new(Self { - handle: NonNull::new(BNNewTypeArchiveReference(handle.handle.as_ptr())).unwrap(), - }) - } - - unsafe fn dec_ref(handle: &Self) { - BNFreeTypeArchiveReference(handle.handle.as_ptr()); - } -} - -impl PartialEq for TypeArchive { - fn eq(&self, other: &Self) -> bool { - self.id() == other.id() - } -} -impl Eq for TypeArchive {} - -impl Hash for TypeArchive { - fn hash(&self, state: &mut H) { - self.id().hash(state); - } -} - -impl Debug for TypeArchive { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("TypeArchive") - .field("id", &self.id()) - .field("path", &self.path()) - .field("current_snapshot_id", &self.current_snapshot_id()) - .field("platform", &self.platform()) - .finish() - } -} - -impl CoreArrayProvider for TypeArchive { - type Raw = *mut BNTypeArchive; - type Context = (); - type Wrapped<'a> = Guard<'a, TypeArchive>; -} - -unsafe impl CoreArrayProviderInner for TypeArchive { - unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { - BNFreeTypeArchiveList(raw, count) - } - - unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { - let raw_ptr = NonNull::new(*raw).unwrap(); - Guard::new(Self::from_raw(raw_ptr), context) - } -} - -pub struct TypeArchiveCallbackHandle { - callback: *mut T, - type_archive: Ref, -} - -impl Drop for TypeArchiveCallbackHandle { - fn drop(&mut self) { - let mut notification = BNTypeArchiveNotification { - context: self.callback as *mut c_void, - typeAdded: Some(cb_type_added::), - typeUpdated: Some(cb_type_updated::), - typeRenamed: Some(cb_type_renamed::), - typeDeleted: Some(cb_type_deleted::), - }; - // unregister the notification callback - unsafe { - BNUnregisterTypeArchiveNotification( - self.type_archive.handle.as_ptr(), - &mut notification, - ) - } - // free the context created at [TypeArchive::register_notification_callback] - drop(unsafe { Box::from_raw(self.callback) }); - } -} - -pub trait TypeArchiveNotificationCallback { - /// Called when a type is added to the archive - /// - /// * `archive` - Source Type archive - /// * `id` - Id of type added - /// * `definition` - Definition of type - fn type_added(&mut self, _archive: &TypeArchive, _id: &str, _definition: &Type) {} - - /// Called when a type in the archive is updated to a new definition - /// - /// * `archive` - Source Type archive - /// * `id` - Id of type - /// * `old_definition` - Previous definition - /// * `new_definition` - Current definition - fn type_updated( - &mut self, - _archive: &TypeArchive, - _id: &str, - _old_definition: &Type, - _new_definition: &Type, - ) { - } - - /// Called when a type in the archive is renamed - /// - /// * `archive` - Source Type archive - /// * `id` - Type id - /// * `old_name` - Previous name - /// * `new_name` - Current name - fn type_renamed( - &mut self, - _archive: &TypeArchive, - _id: &str, - _old_name: &QualifiedName, - _new_name: &QualifiedName, - ) { - } - - /// Called when a type in the archive is deleted from the archive - /// - /// * `archive` - Source Type archive - /// * `id` - Id of type deleted - /// * `definition` - Definition of type deleted - fn type_deleted(&mut self, _archive: &TypeArchive, _id: &str, _definition: &Type) {} -} - -struct NotificationClosure -where - A: FnMut(&TypeArchive, &str, &Type), - U: FnMut(&TypeArchive, &str, &Type, &Type), - R: FnMut(&TypeArchive, &str, &QualifiedName, &QualifiedName), - D: FnMut(&TypeArchive, &str, &Type), -{ - fun_type_added: A, - fun_type_updated: U, - fun_type_renamed: R, - fun_type_deleted: D, -} - -impl TypeArchiveNotificationCallback for NotificationClosure -where - A: FnMut(&TypeArchive, &str, &Type), - U: FnMut(&TypeArchive, &str, &Type, &Type), - R: FnMut(&TypeArchive, &str, &QualifiedName, &QualifiedName), - D: FnMut(&TypeArchive, &str, &Type), -{ - fn type_added(&mut self, archive: &TypeArchive, id: &str, definition: &Type) { - (self.fun_type_added)(archive, id, definition) - } - - fn type_updated( - &mut self, - archive: &TypeArchive, - id: &str, - old_definition: &Type, - new_definition: &Type, - ) { - (self.fun_type_updated)(archive, id, old_definition, new_definition) - } - - fn type_renamed( - &mut self, - archive: &TypeArchive, - id: &str, - old_name: &QualifiedName, - new_name: &QualifiedName, - ) { - (self.fun_type_renamed)(archive, id, old_name, new_name) - } - - fn type_deleted(&mut self, archive: &TypeArchive, id: &str, definition: &Type) { - (self.fun_type_deleted)(archive, id, definition) - } -} - -unsafe extern "C" fn cb_type_added( - ctxt: *mut ::std::os::raw::c_void, - archive: *mut BNTypeArchive, - id: *const ::std::os::raw::c_char, - definition: *mut BNType, -) { - let ctxt: &mut T = &mut *(ctxt as *mut T); - // `archive` is owned by the caller. - let archive = unsafe { TypeArchive::from_raw(NonNull::new(archive).unwrap()) }; - ctxt.type_added( - &archive, - unsafe { CStr::from_ptr(id).to_string_lossy().as_ref() }, - &Type { handle: definition }, - ) -} -unsafe extern "C" fn cb_type_updated( - ctxt: *mut ::std::os::raw::c_void, - archive: *mut BNTypeArchive, - id: *const ::std::os::raw::c_char, - old_definition: *mut BNType, - new_definition: *mut BNType, -) { - let ctxt: &mut T = &mut *(ctxt as *mut T); - // `archive` is owned by the caller. - let archive = unsafe { TypeArchive::from_raw(NonNull::new(archive).unwrap()) }; - ctxt.type_updated( - &archive, - unsafe { CStr::from_ptr(id).to_string_lossy().as_ref() }, - &Type { - handle: old_definition, - }, - &Type { - handle: new_definition, - }, - ) -} -unsafe extern "C" fn cb_type_renamed( - ctxt: *mut ::std::os::raw::c_void, - archive: *mut BNTypeArchive, - id: *const ::std::os::raw::c_char, - old_name: *const BNQualifiedName, - new_name: *const BNQualifiedName, -) { - let ctxt: &mut T = &mut *(ctxt as *mut T); - // `old_name` is freed by the caller - let old_name = QualifiedName::from_raw(&*old_name); - // `new_name` is freed by the caller - let new_name = QualifiedName::from_raw(&*new_name); - // `archive` is owned by the caller. - let archive = unsafe { TypeArchive::from_raw(NonNull::new(archive).unwrap()) }; - ctxt.type_renamed( - &archive, - unsafe { CStr::from_ptr(id).to_string_lossy().as_ref() }, - &old_name, - &new_name, - ) -} -unsafe extern "C" fn cb_type_deleted( - ctxt: *mut ::std::os::raw::c_void, - archive: *mut BNTypeArchive, - id: *const ::std::os::raw::c_char, - definition: *mut BNType, -) { - let ctxt: &mut T = &mut *(ctxt as *mut T); - // `archive` is owned by the caller. - let archive = unsafe { TypeArchive::from_raw(NonNull::new(archive).unwrap()) }; - ctxt.type_deleted( - &archive, - unsafe { CStr::from_ptr(id).to_string_lossy().as_ref() }, - &Type { handle: definition }, - ) -} - -#[repr(transparent)] -pub struct TypeArchiveMergeConflict { - handle: NonNull, -} - -impl TypeArchiveMergeConflict { - pub(crate) unsafe fn from_raw(handle: NonNull) -> Self { - Self { handle } - } - - #[allow(unused)] - pub(crate) unsafe fn ref_from_raw(handle: NonNull) -> Ref { - Ref::new(Self { handle }) - } - - pub fn get_type_archive(&self) -> Option> { - let value = unsafe { BNTypeArchiveMergeConflictGetTypeArchive(self.handle.as_ptr()) }; - NonNull::new(value).map(|handle| unsafe { TypeArchive::ref_from_raw(handle) }) - } - - pub fn type_id(&self) -> String { - let value = unsafe { BNTypeArchiveMergeConflictGetTypeId(self.handle.as_ptr()) }; - assert!(!value.is_null()); - unsafe { BnString::into_string(value) } - } - - pub fn base_snapshot_id(&self) -> TypeArchiveSnapshotId { - let value = unsafe { BNTypeArchiveMergeConflictGetBaseSnapshotId(self.handle.as_ptr()) }; - assert!(!value.is_null()); - let id = unsafe { BnString::into_string(value) }; - TypeArchiveSnapshotId(id) - } - - pub fn first_snapshot_id(&self) -> TypeArchiveSnapshotId { - let value = unsafe { BNTypeArchiveMergeConflictGetFirstSnapshotId(self.handle.as_ptr()) }; - assert!(!value.is_null()); - let id = unsafe { BnString::into_string(value) }; - TypeArchiveSnapshotId(id) - } - - pub fn second_snapshot_id(&self) -> TypeArchiveSnapshotId { - let value = unsafe { BNTypeArchiveMergeConflictGetSecondSnapshotId(self.handle.as_ptr()) }; - assert!(!value.is_null()); - let id = unsafe { BnString::into_string(value) }; - TypeArchiveSnapshotId(id) - } - - /// Call this when you've resolved the conflict to save the result. - pub fn success(&self, result: &str) -> bool { - let result = result.to_cstr(); - unsafe { BNTypeArchiveMergeConflictSuccess(self.handle.as_ptr(), result.as_ptr()) } - } -} - -impl Debug for TypeArchiveMergeConflict { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("TypeArchiveMergeConflict") - .field("type_id", &self.type_id()) - .field("base_snapshot_id", &self.base_snapshot_id()) - .field("first_snapshot_id", &self.first_snapshot_id()) - .field("second_snapshot_id", &self.second_snapshot_id()) - .finish() - } -} - -impl ToOwned for TypeArchiveMergeConflict { - type Owned = Ref; - - fn to_owned(&self) -> Self::Owned { - unsafe { RefCountable::inc_ref(self) } - } -} - -unsafe impl RefCountable for TypeArchiveMergeConflict { - unsafe fn inc_ref(handle: &Self) -> Ref { - Ref::new(Self { - handle: NonNull::new(BNNewTypeArchiveMergeConflictReference( - handle.handle.as_ptr(), - )) - .unwrap(), - }) - } - - unsafe fn dec_ref(handle: &Self) { - BNFreeTypeArchiveMergeConflict(handle.handle.as_ptr()); - } -} - -impl CoreArrayProvider for TypeArchiveMergeConflict { - type Raw = *mut BNTypeArchiveMergeConflict; - type Context = (); - type Wrapped<'a> = Guard<'a, Self>; -} - -unsafe impl CoreArrayProviderInner for TypeArchiveMergeConflict { - unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { - BNFreeTypeArchiveMergeConflictList(raw, count) - } - - unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { - let raw_ptr = NonNull::new(*raw).unwrap(); - Guard::new(Self::from_raw(raw_ptr), context) - } -} diff --git a/rust/src/type_container.rs b/rust/src/type_container.rs deleted file mode 100644 index e8c915df..00000000 --- a/rust/src/type_container.rs +++ /dev/null @@ -1,410 +0,0 @@ -// TODO: Add these! -// The `TypeContainer` class should not generally be instantiated directly. Instances -// can be retrieved from the following properties and methods in the API: -// * [BinaryView::type_container] -// * [BinaryView::auto_type_container] -// * [BinaryView::user_type_container] -// * [Platform::type_container] -// * [TypeLibrary::type_container] -// * [DebugInfo::get_type_container] - -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 binaryninjacore_sys::*; -use std::collections::HashMap; -use std::ffi::{c_char, c_void}; -use std::fmt::{Debug, Formatter}; -use std::ptr::NonNull; - -pub type TypeContainerType = BNTypeContainerType; - -/// A `TypeContainer` is a generic interface to access various Binary Ninja models -/// that contain types. Types are stored with both a unique id and a unique name. -#[repr(transparent)] -pub struct TypeContainer { - pub handle: NonNull, -} - -impl TypeContainer { - pub(crate) unsafe fn from_raw(handle: NonNull) -> Self { - // NOTE: There does not seem to be any shared ref counting for type containers, it seems if the - // NOTE: binary view is freed the type container will be freed and cause this to become invalid - // NOTE: but this is how the C++ and Python bindings operate so i guess its fine? - // TODO: I really dont get how some of the usage of the TypeContainer doesnt free the underlying container. - // TODO: So for now we always duplicate the type container - let cloned_ptr = NonNull::new(BNDuplicateTypeContainer(handle.as_ptr())); - Self { - handle: cloned_ptr.unwrap(), - } - } - - /// Get an empty type container that contains no types (immutable) - pub fn empty() -> TypeContainer { - let result = unsafe { BNGetEmptyTypeContainer() }; - unsafe { Self::from_raw(NonNull::new(result).unwrap()) } - } - - /// Get an id string for the Type Container. This will be unique within a given - /// analysis session, but may not be globally unique. - pub fn id(&self) -> String { - let result = unsafe { BNTypeContainerGetId(self.handle.as_ptr()) }; - assert!(!result.is_null()); - unsafe { BnString::into_string(result) } - } - - /// Get a user-friendly name for the Type Container. - pub fn name(&self) -> String { - let result = unsafe { BNTypeContainerGetName(self.handle.as_ptr()) }; - assert!(!result.is_null()); - unsafe { BnString::into_string(result) } - } - - /// Get the type of underlying model the Type Container is accessing. - pub fn container_type(&self) -> TypeContainerType { - unsafe { BNTypeContainerGetType(self.handle.as_ptr()) } - } - - /// If the Type Container supports mutable operations (add, rename, delete) - pub fn is_mutable(&self) -> bool { - unsafe { BNTypeContainerIsMutable(self.handle.as_ptr()) } - } - - /// Get the Platform object associated with this Type Container. All Type Containers - /// have exactly one associated Platform (as opposed to, e.g. Type Libraries). - pub fn platform(&self) -> Ref { - let result = unsafe { BNTypeContainerGetPlatform(self.handle.as_ptr()) }; - assert!(!result.is_null()); - unsafe { Platform::ref_from_raw(result) } - } - - /// Add or update types to a Type Container. If the Type Container already contains - /// a type with the same name as a type being added, the existing type will be - /// replaced with the definition given to this function, and references will be - /// updated in the source model. - pub fn add_types(&self, types: I) -> bool - where - I: IntoIterator, - T: Into, - { - self.add_types_with_progress(types, NoProgressCallback) - } - - pub fn add_types_with_progress(&self, types: I, mut progress: P) -> bool - where - I: IntoIterator, - T: Into, - P: ProgressCallback, - { - // TODO: I dislike how this iter unzip looks like... but its how to avoid allocating again... - let (raw_names, mut raw_types): (Vec, Vec<_>) = types - .into_iter() - .map(|t| { - let t = t.into(); - // Leaked to be freed after the call to core. - ( - QualifiedName::into_raw(t.name), - unsafe { Ref::into_raw(t.ty) }.handle, - ) - }) - .unzip(); - - let mut result_names = std::ptr::null_mut(); - let mut result_ids = std::ptr::null_mut(); - let mut result_count = 0; - - let success = unsafe { - BNTypeContainerAddTypes( - self.handle.as_ptr(), - raw_names.as_ptr(), - raw_types.as_mut_ptr(), - raw_types.len(), - Some(P::cb_progress_callback), - &mut progress as *mut P as *mut c_void, - &mut result_names, - &mut result_ids, - &mut result_count, - ) - }; - - for name in raw_names { - QualifiedName::free_raw(name); - } - for ty in raw_types { - let _ = unsafe { Type::ref_from_raw(ty) }; - } - success - } - - /// Rename a type in the Type Container. All references to this type will be updated - /// (by id) to use the new name. - /// - /// Returns true if the type was renamed. - pub fn rename_type>(&self, name: T, type_id: &str) -> bool { - let type_id = type_id.to_cstr(); - let raw_name = QualifiedName::into_raw(name.into()); - let success = - unsafe { BNTypeContainerRenameType(self.handle.as_ptr(), type_id.as_ptr(), &raw_name) }; - QualifiedName::free_raw(raw_name); - success - } - - /// Delete a type in the Type Container. Behavior of references to this type is - /// not specified and you may end up with broken references if any still exist. - /// - /// Returns true if the type was deleted. - pub fn delete_type(&self, type_id: &str) -> bool { - let type_id = type_id.to_cstr(); - unsafe { BNTypeContainerDeleteType(self.handle.as_ptr(), type_id.as_ptr()) } - } - - /// Get the unique id of the type in the Type Container with the given name. - /// - /// If no type with that name exists, returns None. - pub fn type_id>(&self, name: T) -> Option { - let mut result = std::ptr::null_mut(); - let raw_name = QualifiedName::into_raw(name.into()); - let success = - unsafe { BNTypeContainerGetTypeId(self.handle.as_ptr(), &raw_name, &mut result) }; - QualifiedName::free_raw(raw_name); - success.then(|| unsafe { BnString::into_string(result) }) - } - - /// Get the unique name of the type in the Type Container with the given id. - /// - /// If no type with that id exists, returns None. - pub fn type_name(&self, type_id: &str) -> Option { - let type_id = type_id.to_cstr(); - let mut result = BNQualifiedName::default(); - let success = unsafe { - BNTypeContainerGetTypeName(self.handle.as_ptr(), type_id.as_ptr(), &mut result) - }; - success.then(|| QualifiedName::from_owned_raw(result)) - } - - /// Get the definition of the type in the Type Container with the given id. - /// - /// If no type with that id exists, returns None. - pub fn type_by_id(&self, type_id: &str) -> Option> { - let type_id = type_id.to_cstr(); - let mut result = std::ptr::null_mut(); - let success = unsafe { - BNTypeContainerGetTypeById(self.handle.as_ptr(), type_id.as_ptr(), &mut result) - }; - success.then(|| unsafe { Type::ref_from_raw(result) }) - } - - /// Get the definition of the type in the Type Container with the given name. - /// - /// If no type with that name exists, returns None. - pub fn type_by_name>(&self, name: T) -> Option> { - let mut result = std::ptr::null_mut(); - let raw_name = QualifiedName::into_raw(name.into()); - let success = - unsafe { BNTypeContainerGetTypeByName(self.handle.as_ptr(), &raw_name, &mut result) }; - QualifiedName::free_raw(raw_name); - success.then(|| unsafe { Type::ref_from_raw(result) }) - } - - /// Get a mapping of all types in a Type Container. - pub fn types(&self) -> Option)>> { - let mut type_ids = std::ptr::null_mut(); - let mut type_names = std::ptr::null_mut(); - let mut type_types = std::ptr::null_mut(); - let mut type_count = 0; - let success = unsafe { - BNTypeContainerGetTypes( - self.handle.as_ptr(), - &mut type_ids, - &mut type_names, - &mut type_types, - &mut type_count, - ) - }; - success.then(|| unsafe { - let raw_ids = std::slice::from_raw_parts(type_ids, type_count); - let raw_names = std::slice::from_raw_parts(type_names, type_count); - let raw_types = std::slice::from_raw_parts(type_types, type_count); - let mut map = HashMap::new(); - for (idx, raw_id) in raw_ids.iter().enumerate() { - let id = raw_to_string(*raw_id).expect("Valid string"); - // Take the qualified name as a ref as the name should not be freed. - let name = QualifiedName::from_raw(&raw_names[idx]); - // Take the type as an owned ref, as the returned type was not already incremented. - let ty = Type::from_raw(raw_types[idx]).to_owned(); - map.insert(id, (name, ty)); - } - BNFreeStringList(type_ids, type_count); - BNFreeTypeNameList(type_names, type_count); - BNFreeTypeList(type_types, type_count); - map - }) - } - - /// Get all type ids in a Type Container. - pub fn type_ids(&self) -> Option> { - let mut type_ids = std::ptr::null_mut(); - let mut type_count = 0; - let success = unsafe { - BNTypeContainerGetTypeIds(self.handle.as_ptr(), &mut type_ids, &mut type_count) - }; - success.then(|| unsafe { Array::new(type_ids, type_count, ()) }) - } - - /// Get all type names in a Type Container. - pub fn type_names(&self) -> Option> { - let mut type_ids = std::ptr::null_mut(); - let mut type_count = 0; - let success = unsafe { - BNTypeContainerGetTypeNames(self.handle.as_ptr(), &mut type_ids, &mut type_count) - }; - success.then(|| unsafe { Array::new(type_ids, type_count, ()) }) - } - - /// Get a mapping of all type ids and type names in a Type Container. - pub fn type_names_and_ids(&self) -> Option<(Array, Array)> { - let mut type_ids = std::ptr::null_mut(); - let mut type_names = std::ptr::null_mut(); - let mut type_count = 0; - let success = unsafe { - BNTypeContainerGetTypeNamesAndIds( - self.handle.as_ptr(), - &mut type_ids, - &mut type_names, - &mut type_count, - ) - }; - success.then(|| unsafe { - let ids = Array::new(type_ids, type_count, ()); - let names = Array::new(type_names, type_count, ()); - (ids, names) - }) - } - - /// Parse a single type and name from a string containing their definition, with - /// knowledge of the types in the Type Container. - /// - /// * `source` - Source code to parse - /// * `import_dependencies` - If Type Library / Type Archive types should be imported during parsing - pub fn parse_type_string( - &self, - source: &str, - import_dependencies: bool, - ) -> Result> { - let source = source.to_cstr(); - let mut result = BNQualifiedNameAndType::default(); - let mut errors = std::ptr::null_mut(); - let mut error_count = 0; - let success = unsafe { - BNTypeContainerParseTypeString( - self.handle.as_ptr(), - source.as_ptr(), - import_dependencies, - &mut result, - &mut errors, - &mut error_count, - ) - }; - if success { - Ok(QualifiedNameAndType::from_owned_raw(result)) - } else { - assert!(!errors.is_null()); - Err(unsafe { Array::new(errors, error_count, ()) }) - } - } - - /// Parse an entire block of source into types, variables, and functions, with - /// knowledge of the types in the Type Container. - /// - /// * `source` - Source code to parse - /// * `file_name` - Name of the file containing the source (optional: exists on disk) - /// * `options` - String arguments to pass as options, e.g. command line arguments - /// * `include_dirs` - List of directories to include in the header search path - /// * `auto_type_source` - Source of types if used for automatically generated types - /// * `import_dependencies` - If Type Library / Type Archive types should be imported during parsing - pub fn parse_types_from_source( - &self, - source: &str, - filename: &str, - options: O, - include_directories: I, - auto_type_source: &str, - import_dependencies: bool, - ) -> Result> - where - O: IntoIterator, - I: IntoIterator, - { - let source = source.to_cstr(); - let filename = filename.to_cstr(); - let options: Vec<_> = options.into_iter().map(|o| o.to_cstr()).collect(); - let options_raw: Vec<*const c_char> = options.iter().map(|o| o.as_ptr()).collect(); - let include_directories: Vec<_> = include_directories - .into_iter() - .map(|d| d.to_cstr()) - .collect(); - let include_directories_raw: Vec<*const c_char> = - include_directories.iter().map(|d| d.as_ptr()).collect(); - let auto_type_source = auto_type_source.to_cstr(); - let mut raw_result = BNTypeParserResult::default(); - let mut errors = std::ptr::null_mut(); - let mut error_count = 0; - let success = unsafe { - BNTypeContainerParseTypesFromSource( - self.handle.as_ptr(), - source.as_ptr(), - filename.as_ptr(), - options_raw.as_ptr(), - options_raw.len(), - include_directories_raw.as_ptr(), - include_directories_raw.len(), - auto_type_source.as_ptr(), - import_dependencies, - &mut raw_result, - &mut errors, - &mut error_count, - ) - }; - if success { - let result = TypeParserResult::from_raw(&raw_result); - // NOTE: This is safe because the core allocated the TypeParserResult - TypeParserResult::free_raw(raw_result); - Ok(result) - } else { - assert!(!errors.is_null()); - Err(unsafe { Array::new(errors, error_count, ()) }) - } - } -} - -impl Debug for TypeContainer { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("TypeContainer") - .field("id", &self.id()) - .field("name", &self.name()) - .field("container_type", &self.container_type()) - .field("is_mutable", &self.is_mutable()) - .field("type_names", &self.type_names().unwrap().to_vec()) - .finish() - } -} - -impl Drop for TypeContainer { - fn drop(&mut self) { - unsafe { BNFreeTypeContainer(self.handle.as_ptr()) } - } -} - -impl Clone for TypeContainer { - fn clone(&self) -> Self { - unsafe { - let cloned_ptr = NonNull::new(BNDuplicateTypeContainer(self.handle.as_ptr())); - Self { - handle: cloned_ptr.unwrap(), - } - } - } -} diff --git a/rust/src/type_library.rs b/rust/src/type_library.rs deleted file mode 100644 index 89f5e48f..00000000 --- a/rust/src/type_library.rs +++ /dev/null @@ -1,365 +0,0 @@ -use binaryninjacore_sys::*; -use std::fmt::{Debug, Formatter}; - -use crate::rc::{Guard, RefCountable}; -use crate::{ - architecture::CoreArchitecture, - metadata::Metadata, - platform::Platform, - rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref}, - string::{BnString, IntoCStr}, - types::{QualifiedName, QualifiedNameAndType, Type}, -}; -use std::path::Path; -use std::ptr::NonNull; - -#[repr(transparent)] -pub struct TypeLibrary { - handle: NonNull, -} - -impl TypeLibrary { - pub(crate) unsafe fn from_raw(handle: NonNull) -> Self { - Self { handle } - } - - pub(crate) unsafe fn ref_from_raw(handle: NonNull) -> Ref { - Ref::new(Self { handle }) - } - - #[allow(clippy::mut_from_ref)] - pub(crate) unsafe fn as_raw(&self) -> &mut BNTypeLibrary { - &mut *self.handle.as_ptr() - } - - pub fn new_duplicated(&self) -> Ref { - unsafe { Self::ref_from_raw(NonNull::new(BNDuplicateTypeLibrary(self.as_raw())).unwrap()) } - } - - /// Creates an empty type library object with a random GUID and the provided name. - pub fn new(arch: CoreArchitecture, name: &str) -> Ref { - let name = name.to_cstr(); - let new_lib = unsafe { BNNewTypeLibrary(arch.handle, name.as_ptr()) }; - unsafe { TypeLibrary::ref_from_raw(NonNull::new(new_lib).unwrap()) } - } - - pub fn all(arch: CoreArchitecture) -> Array { - let mut count = 0; - let result = unsafe { BNGetArchitectureTypeLibraries(arch.handle, &mut count) }; - assert!(!result.is_null()); - unsafe { Array::new(result, count, ()) } - } - - /// Decompresses a type library file to a file on disk. - pub fn decompress_to_file(path: &Path, output_path: &Path) -> bool { - let path = path.to_cstr(); - let output = output_path.to_cstr(); - unsafe { BNTypeLibraryDecompressToFile(path.as_ptr(), output.as_ptr()) } - } - - /// Loads a finalized type library instance from file - pub fn load_from_file(path: &Path) -> Option> { - let path = path.to_cstr(); - let handle = unsafe { BNLoadTypeLibraryFromFile(path.as_ptr()) }; - NonNull::new(handle).map(|h| unsafe { TypeLibrary::ref_from_raw(h) }) - } - - /// Saves a finalized type library instance to file - pub fn write_to_file(&self, path: &Path) -> bool { - let path = path.to_cstr(); - unsafe { BNWriteTypeLibraryToFile(self.as_raw(), path.as_ptr()) } - } - - /// Looks up the first type library found with a matching name. Keep in mind that names are not - /// necessarily unique. - /// - /// NOTE: If the type library architecture's associated platform has not been initialized, this will - /// return `None`. To make sure that the platform has been initialized, one should instead get the type - /// libraries through [`Platform::get_type_libraries_by_name`]. - pub fn from_name(arch: CoreArchitecture, name: &str) -> Option> { - let name = name.to_cstr(); - let handle = unsafe { BNLookupTypeLibraryByName(arch.handle, name.as_ptr()) }; - NonNull::new(handle).map(|h| unsafe { TypeLibrary::ref_from_raw(h) }) - } - - /// Attempts to grab a type library associated with the provided Architecture and GUID pair. - /// - /// NOTE: If the associated platform for the architecture has not been initialized, - /// this will return `None`. Avoid calling this outside of a view context. - pub fn from_guid(arch: CoreArchitecture, guid: &str) -> Option> { - let guid = guid.to_cstr(); - let handle = unsafe { BNLookupTypeLibraryByGuid(arch.handle, guid.as_ptr()) }; - NonNull::new(handle).map(|h| unsafe { TypeLibrary::ref_from_raw(h) }) - } - - /// The Architecture this type library is associated with - pub fn arch(&self) -> CoreArchitecture { - let arch = unsafe { BNGetTypeLibraryArchitecture(self.as_raw()) }; - assert!(!arch.is_null()); - unsafe { CoreArchitecture::from_raw(arch) } - } - - /// The primary name associated with this type library - pub fn name(&self) -> String { - let result = unsafe { BNGetTypeLibraryName(self.as_raw()) }; - assert!(!result.is_null()); - unsafe { BnString::into_string(result) } - } - - /// Sets the name of a type library instance that has not been finalized - pub fn set_name(&self, value: &str) { - let value = value.to_cstr(); - unsafe { BNSetTypeLibraryName(self.as_raw(), value.as_ptr()) } - } - - /// The `dependency_name` of a library is the name used to record dependencies across - /// type libraries. This allows, for example, a library with the name "musl_libc" to have - /// dependencies on it recorded as "libc_generic", allowing a type library to be used across - /// multiple platforms where each has a specific libc that also provides the name "libc_generic" - /// as an `alternate_name`. - pub fn dependency_name(&self) -> String { - let result = unsafe { BNGetTypeLibraryDependencyName(self.as_raw()) }; - assert!(!result.is_null()); - unsafe { BnString::into_string(result) } - } - - /// Sets the dependency name of a type library instance that has not been finalized - pub fn set_dependency_name(&self, value: &str) { - let value = value.to_cstr(); - unsafe { BNSetTypeLibraryDependencyName(self.as_raw(), value.as_ptr()) } - } - - /// Returns the GUID associated with the type library - pub fn guid(&self) -> String { - let result = unsafe { BNGetTypeLibraryGuid(self.as_raw()) }; - assert!(!result.is_null()); - unsafe { BnString::into_string(result) } - } - - /// Sets the GUID of a type library instance that has not been finalized - pub fn set_guid(&self, value: &str) { - let value = value.to_cstr(); - unsafe { BNSetTypeLibraryGuid(self.as_raw(), value.as_ptr()) } - } - - /// A list of extra names that will be considered a match by [Platform::get_type_libraries_by_name] - pub fn alternate_names(&self) -> Array { - let mut count = 0; - let result = unsafe { BNGetTypeLibraryAlternateNames(self.as_raw(), &mut count) }; - assert!(!result.is_null()); - unsafe { Array::new(result, count, ()) } - } - - /// Adds an extra name to this type library used during library lookups and dependency resolution - pub fn add_alternate_name(&self, value: &str) { - let value = value.to_cstr(); - unsafe { BNAddTypeLibraryAlternateName(self.as_raw(), value.as_ptr()) } - } - - /// Returns a list of all platform names that this type library will register with during platform - /// type registration. - /// - /// This returns strings, not Platform objects, as type libraries can be distributed with support for - /// Platforms that may not be present. - pub fn platform_names(&self) -> Array { - let mut count = 0; - let result = unsafe { BNGetTypeLibraryPlatforms(self.as_raw(), &mut count) }; - assert!(!result.is_null()); - unsafe { Array::new(result, count, ()) } - } - - /// Associate a platform with a type library instance that has not been finalized. - /// - /// This will cause the library to be searchable by [Platform::get_type_libraries_by_name] - /// when loaded. - /// - /// This does not have side affects until finalization of the type library. - pub fn add_platform(&self, plat: &Platform) { - unsafe { BNAddTypeLibraryPlatform(self.as_raw(), plat.handle) } - } - - /// Clears the list of platforms associated with a type library instance that has not been finalized - pub fn clear_platforms(&self) { - unsafe { BNClearTypeLibraryPlatforms(self.as_raw()) } - } - - /// Flags a newly created type library instance as finalized and makes it available for Platform and Architecture - /// type library searches - pub fn finalize(&self) -> bool { - unsafe { BNFinalizeTypeLibrary(self.as_raw()) } - } - - /// Retrieves a metadata associated with the given key stored in the type library - pub fn query_metadata(&self, key: &str) -> Option> { - let key = key.to_cstr(); - let result = unsafe { BNTypeLibraryQueryMetadata(self.as_raw(), key.as_ptr()) }; - (!result.is_null()).then(|| unsafe { Metadata::ref_from_raw(result) }) - } - - /// Stores an object for the given key in the current type library. Objects stored using - /// `store_metadata` can be retrieved from any reference to the library. Objects stored are not arbitrary python - /// objects! The values stored must be able to be held in a Metadata object. See [Metadata] - /// for more information. Python objects could obviously be serialized using pickle but this intentionally - /// a task left to the user since there is the potential security issues. - /// - /// This is primarily intended as a way to store Platform specific information relevant to BinaryView implementations; - /// for example the PE BinaryViewType uses type library metadata to retrieve ordinal information, when available. - /// - /// * `key` - key value to associate the Metadata object with - /// * `md` - object to store. - pub fn store_metadata(&self, key: &str, md: &Metadata) { - let key = key.to_cstr(); - unsafe { BNTypeLibraryStoreMetadata(self.as_raw(), key.as_ptr(), md.handle) } - } - - /// Removes the metadata associated with key from the current type library. - pub fn remove_metadata(&self, key: &str) { - let key = key.to_cstr(); - unsafe { BNTypeLibraryRemoveMetadata(self.as_raw(), key.as_ptr()) } - } - - /// Retrieves the metadata associated with the current type library. - pub fn metadata(&self) -> Ref { - let md_handle = unsafe { BNTypeLibraryGetMetadata(self.as_raw()) }; - assert!(!md_handle.is_null()); - unsafe { Metadata::ref_from_raw(md_handle) } - } - - // TODO: implement TypeContainer - // /// Type Container for all TYPES within the Type Library. Objects are not included. - // /// The Type Container's Platform will be the first platform associated with the Type Library. - // pub fn type_container(&self) -> TypeContainer { - // let result = unsafe{ BNGetTypeLibraryTypeContainer(self.as_raw())}; - // unsafe{TypeContainer::from_raw(NonNull::new(result).unwrap())} - // } - - /// Directly inserts a named object into the type library's object store. - /// This is not done recursively, so care should be taken that types referring to other types - /// through NamedTypeReferences are already appropriately prepared. - /// - /// To add types and objects from an existing BinaryView, it is recommended to use - /// `export_object_to_library `, which will automatically pull in - /// all referenced types and record additional dependencies as needed. - pub fn add_named_object(&self, name: QualifiedName, type_: &Type) { - let mut raw_name = QualifiedName::into_raw(name); - unsafe { BNAddTypeLibraryNamedObject(self.as_raw(), &mut raw_name, type_.handle) } - QualifiedName::free_raw(raw_name); - } - - /// Directly inserts a named object into the type library's object store. - /// This is not done recursively, so care should be taken that types referring to other types - /// through NamedTypeReferences are already appropriately prepared. - /// - /// To add types and objects from an existing BinaryView, it is recommended to use - /// `export_type_to_library `, which will automatically pull in - /// all referenced types and record additional dependencies as needed. - pub fn add_named_type(&self, name: QualifiedName, type_: &Type) { - let mut raw_name = QualifiedName::into_raw(name); - unsafe { BNAddTypeLibraryNamedType(self.as_raw(), &mut raw_name, type_.handle) } - QualifiedName::free_raw(raw_name); - } - - /// Manually flag NamedTypeReferences to the given QualifiedName as originating from another source - /// TypeLibrary with the given dependency name. - /// - ///
- /// - /// Use this api with extreme caution. - /// - ///
- pub fn add_type_source(&self, name: QualifiedName, source: &str) { - let source = source.to_cstr(); - let mut raw_name = QualifiedName::into_raw(name); - unsafe { BNAddTypeLibraryNamedTypeSource(self.as_raw(), &mut raw_name, source.as_ptr()) } - QualifiedName::free_raw(raw_name); - } - - /// Direct extracts a reference to a contained object -- when - /// attempting to extract types from a library into a BinaryView, consider using - /// `import_library_object ` instead. - pub fn get_named_object(&self, name: QualifiedName) -> Option> { - let mut raw_name = QualifiedName::into_raw(name); - let t = unsafe { BNGetTypeLibraryNamedObject(self.as_raw(), &mut raw_name) }; - QualifiedName::free_raw(raw_name); - (!t.is_null()).then(|| unsafe { Type::ref_from_raw(t) }) - } - - /// Direct extracts a reference to a contained type -- when - /// attempting to extract types from a library into a BinaryView, consider using - /// `import_library_type ` instead. - pub fn get_named_type(&self, name: QualifiedName) -> Option> { - let mut raw_name = QualifiedName::into_raw(name); - let t = unsafe { BNGetTypeLibraryNamedType(self.as_raw(), &mut raw_name) }; - QualifiedName::free_raw(raw_name); - (!t.is_null()).then(|| unsafe { Type::ref_from_raw(t) }) - } - - /// A dict containing all named objects (functions, exported variables) provided by a type library - pub fn named_objects(&self) -> Array { - let mut count = 0; - let result = unsafe { BNGetTypeLibraryNamedObjects(self.as_raw(), &mut count) }; - assert!(!result.is_null()); - unsafe { Array::new(result, count, ()) } - } - - /// A dict containing all named types provided by a type library - pub fn named_types(&self) -> Array { - let mut count = 0; - let result = unsafe { BNGetTypeLibraryNamedTypes(self.as_raw(), &mut count) }; - assert!(!result.is_null()); - unsafe { Array::new(result, count, ()) } - } -} - -impl Debug for TypeLibrary { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("TypeLibrary") - .field("name", &self.name()) - .field("dependency_name", &self.dependency_name()) - .field("arch", &self.arch()) - .field("guid", &self.guid()) - .field("alternate_names", &self.alternate_names().to_vec()) - .field("platform_names", &self.platform_names().to_vec()) - .field("metadata", &self.metadata()) - // These two are too verbose. - // .field("named_objects", &self.named_objects().to_vec()) - // .field("named_types", &self.named_types().to_vec()) - .finish() - } -} - -unsafe impl RefCountable for TypeLibrary { - unsafe fn inc_ref(handle: &Self) -> Ref { - Ref::new(Self { - handle: NonNull::new(BNNewTypeLibraryReference(handle.handle.as_ptr())).unwrap(), - }) - } - - unsafe fn dec_ref(handle: &Self) { - BNFreeTypeLibrary(handle.handle.as_ptr()); - } -} - -impl ToOwned for TypeLibrary { - type Owned = Ref; - - fn to_owned(&self) -> Self::Owned { - unsafe { RefCountable::inc_ref(self) } - } -} - -impl CoreArrayProvider for TypeLibrary { - type Raw = *mut BNTypeLibrary; - type Context = (); - type Wrapped<'a> = Guard<'a, Self>; -} - -unsafe impl CoreArrayProviderInner for TypeLibrary { - unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { - BNFreeTypeLibraryList(raw, count) - } - - unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { - Guard::new(Self::from_raw(NonNull::new(*raw).unwrap()), context) - } -} diff --git a/rust/src/type_parser.rs b/rust/src/type_parser.rs deleted file mode 100644 index 8ad0725f..00000000 --- a/rust/src/type_parser.rs +++ /dev/null @@ -1,688 +0,0 @@ -#![allow(unused)] -use binaryninjacore_sys::*; -use std::ffi::{c_char, c_void}; -use std::fmt::Debug; -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}; - -pub type TypeParserErrorSeverity = BNTypeParserErrorSeverity; -pub type TypeParserOption = BNTypeParserOption; - -/// Register a custom parser with the API -pub fn register_type_parser( - name: &str, - parser: T, -) -> (&'static mut T, CoreTypeParser) { - let parser = Box::leak(Box::new(parser)); - let mut callback = BNTypeParserCallbacks { - context: parser as *mut _ as *mut c_void, - getOptionText: Some(cb_get_option_text::), - preprocessSource: Some(cb_preprocess_source::), - parseTypesFromSource: Some(cb_parse_types_from_source::), - parseTypeString: Some(cb_parse_type_string::), - freeString: Some(cb_free_string), - freeResult: Some(cb_free_result), - freeErrorList: Some(cb_free_error_list), - }; - let name = name.to_cstr(); - let result = unsafe { BNRegisterTypeParser(name.as_ptr(), &mut callback) }; - let core = unsafe { CoreTypeParser::from_raw(NonNull::new(result).unwrap()) }; - (parser, core) -} - -#[repr(transparent)] -pub struct CoreTypeParser { - pub(crate) handle: NonNull, -} - -impl CoreTypeParser { - pub(crate) unsafe fn from_raw(handle: NonNull) -> Self { - Self { handle } - } - - pub fn parsers() -> Array { - let mut count = 0; - let result = unsafe { BNGetTypeParserList(&mut count) }; - unsafe { Array::new(result, count, ()) } - } - - pub fn parser_by_name(name: &str) -> Option { - let name_raw = name.to_cstr(); - let result = unsafe { BNGetTypeParserByName(name_raw.as_ptr()) }; - NonNull::new(result).map(|x| unsafe { Self::from_raw(x) }) - } - - pub fn name(&self) -> String { - let result = unsafe { BNGetTypeParserName(self.handle.as_ptr()) }; - assert!(!result.is_null()); - unsafe { BnString::into_string(result) } - } -} - -impl TypeParser for CoreTypeParser { - fn get_option_text(&self, option: TypeParserOption, value: &str) -> Option { - let mut output = std::ptr::null_mut(); - let value_ptr = std::ptr::null_mut(); - let result = unsafe { - BNGetTypeParserOptionText(self.handle.as_ptr(), option, value_ptr, &mut output) - }; - result.then(|| { - assert!(!output.is_null()); - unsafe { BnString::into_string(value_ptr) } - }) - } - - fn preprocess_source( - &self, - source: &str, - file_name: &str, - platform: &Platform, - existing_types: &TypeContainer, - options: &[String], - include_dirs: &[String], - ) -> Result> { - let source_cstr = BnString::new(source); - let file_name_cstr = BnString::new(file_name); - let mut result = std::ptr::null_mut(); - let mut errors = std::ptr::null_mut(); - let mut error_count = 0; - let success = unsafe { - BNTypeParserPreprocessSource( - self.handle.as_ptr(), - source_cstr.as_ptr(), - file_name_cstr.as_ptr(), - platform.handle, - existing_types.handle.as_ptr(), - options.as_ptr() as *const *const c_char, - options.len(), - include_dirs.as_ptr() as *const *const c_char, - include_dirs.len(), - &mut result, - &mut errors, - &mut error_count, - ) - }; - if success { - assert!(!result.is_null()); - let bn_result = unsafe { BnString::into_string(result) }; - Ok(bn_result) - } else { - let errors: Array = unsafe { Array::new(errors, error_count, ()) }; - Err(errors.to_vec()) - } - } - - fn parse_types_from_source( - &self, - source: &str, - file_name: &str, - platform: &Platform, - existing_types: &TypeContainer, - options: &[String], - include_dirs: &[String], - auto_type_source: &str, - ) -> Result> { - let source_cstr = BnString::new(source); - let file_name_cstr = BnString::new(file_name); - let auto_type_source = BnString::new(auto_type_source); - let mut raw_result = BNTypeParserResult::default(); - let mut errors = std::ptr::null_mut(); - let mut error_count = 0; - let success = unsafe { - BNTypeParserParseTypesFromSource( - self.handle.as_ptr(), - source_cstr.as_ptr(), - file_name_cstr.as_ptr(), - platform.handle, - existing_types.handle.as_ptr(), - options.as_ptr() as *const *const c_char, - options.len(), - include_dirs.as_ptr() as *const *const c_char, - include_dirs.len(), - auto_type_source.as_ptr(), - &mut raw_result, - &mut errors, - &mut error_count, - ) - }; - if success { - let result = TypeParserResult::from_raw(&raw_result); - // NOTE: This is safe because the core allocated the TypeParserResult - TypeParserResult::free_raw(raw_result); - Ok(result) - } else { - let errors: Array = unsafe { Array::new(errors, error_count, ()) }; - Err(errors.to_vec()) - } - } - - fn parse_type_string( - &self, - source: &str, - platform: &Platform, - existing_types: &TypeContainer, - ) -> Result> { - let source_cstr = BnString::new(source); - let mut output = BNQualifiedNameAndType::default(); - let mut errors = std::ptr::null_mut(); - let mut error_count = 0; - let result = unsafe { - BNTypeParserParseTypeString( - self.handle.as_ptr(), - source_cstr.as_ptr(), - platform.handle, - existing_types.handle.as_ptr(), - &mut output, - &mut errors, - &mut error_count, - ) - }; - if result { - Ok(QualifiedNameAndType::from_owned_raw(output)) - } else { - let errors: Array = unsafe { Array::new(errors, error_count, ()) }; - Err(errors.to_vec()) - } - } -} - -impl Default for CoreTypeParser { - fn default() -> Self { - // TODO: This should return a ref - unsafe { Self::from_raw(NonNull::new(BNGetDefaultTypeParser()).unwrap()) } - } -} - -// TODO: Impl this on platform. -pub trait TypeParser { - /// Get the string representation of an option for passing to parse_type_*. - /// Returns a string representing the option if the parser supports it, - /// otherwise None - /// - /// * `option` - Option type - /// * `value` - Option value - fn get_option_text(&self, option: TypeParserOption, value: &str) -> Option; - - /// Preprocess a block of source, returning the source that would be parsed - /// - /// * `source` - Source code to process - /// * `file_name` - Name of the file containing the source (does not need to exist on disk) - /// * `platform` - Platform to assume the source is relevant to - /// * `existing_types` - Optional collection of all existing types to use for parsing context - /// * `options` - Optional string arguments to pass as options, e.g. command line arguments - /// * `include_dirs` - Optional list of directories to include in the header search path - fn preprocess_source( - &self, - source: &str, - file_name: &str, - platform: &Platform, - existing_types: &TypeContainer, - options: &[String], - include_dirs: &[String], - ) -> Result>; - - /// Parse an entire block of source into types, variables, and functions - /// - /// * `source` - Source code to parse - /// * `file_name` - Name of the file containing the source (optional: exists on disk) - /// * `platform` - Platform to assume the types are relevant to - /// * `existing_types` - Optional container of all existing types to use for parsing context - /// * `options` - Optional string arguments to pass as options, e.g. command line arguments - /// * `include_dirs` - Optional list of directories to include in the header search path - /// * `auto_type_source` - Optional source of types if used for automatically generated types - fn parse_types_from_source( - &self, - source: &str, - file_name: &str, - platform: &Platform, - existing_types: &TypeContainer, - options: &[String], - include_dirs: &[String], - auto_type_source: &str, - ) -> Result>; - - /// Parse a single type and name from a string containing their definition. - /// - /// * `source` - Source code to parse - /// * `platform` - Platform to assume the types are relevant to - /// * `existing_types` - Optional container of all existing types to use for parsing context - fn parse_type_string( - &self, - source: &str, - platform: &Platform, - existing_types: &TypeContainer, - ) -> Result>; -} - -impl CoreArrayProvider for CoreTypeParser { - type Raw = *mut BNTypeParser; - type Context = (); - type Wrapped<'a> = Self; -} - -unsafe impl CoreArrayProviderInner for CoreTypeParser { - unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { - BNFreeTypeParserList(raw) - } - - unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { - // TODO: Because handle is a NonNull we should prob make Self::Raw that as well... - let handle = NonNull::new(*raw).unwrap(); - CoreTypeParser::from_raw(handle) - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct TypeParserError { - pub severity: TypeParserErrorSeverity, - pub message: String, - pub file_name: String, - pub line: u64, - pub column: u64, -} - -impl TypeParserError { - pub(crate) fn from_raw(value: &BNTypeParserError) -> Self { - Self { - severity: value.severity, - message: raw_to_string(value.message).unwrap(), - file_name: raw_to_string(value.fileName).unwrap(), - line: value.line, - column: value.column, - } - } - - pub(crate) fn from_owned_raw(value: BNTypeParserError) -> Self { - let owned = Self::from_raw(&value); - Self::free_raw(value); - owned - } - - pub(crate) fn into_raw(value: Self) -> BNTypeParserError { - BNTypeParserError { - severity: value.severity, - message: BnString::into_raw(BnString::new(value.message)), - fileName: BnString::into_raw(BnString::new(value.file_name)), - line: value.line, - column: value.column, - } - } - - pub(crate) fn free_raw(value: BNTypeParserError) { - unsafe { BnString::free_raw(value.message) }; - unsafe { BnString::free_raw(value.fileName) }; - } - - pub fn new( - severity: TypeParserErrorSeverity, - message: String, - file_name: String, - line: u64, - column: u64, - ) -> Self { - Self { - severity, - message, - file_name, - line, - column, - } - } -} - -impl CoreArrayProvider for TypeParserError { - type Raw = BNTypeParserError; - type Context = (); - type Wrapped<'a> = Self; -} - -unsafe impl CoreArrayProviderInner for TypeParserError { - unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { - unsafe { BNFreeTypeParserErrors(raw, count) } - } - - unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { - Self::from_raw(raw) - } -} - -#[derive(Debug, Eq, PartialEq, Default)] -pub struct TypeParserResult { - pub types: Vec, - pub variables: Vec, - pub functions: Vec, -} - -impl TypeParserResult { - pub(crate) fn from_raw(value: &BNTypeParserResult) -> Self { - let raw_types = unsafe { std::slice::from_raw_parts(value.types, value.typeCount) }; - let types = raw_types.iter().map(ParsedType::from_raw).collect(); - let raw_variables = - unsafe { std::slice::from_raw_parts(value.variables, value.variableCount) }; - let variables = raw_variables.iter().map(ParsedType::from_raw).collect(); - let raw_functions = - unsafe { std::slice::from_raw_parts(value.functions, value.functionCount) }; - let functions = raw_functions.iter().map(ParsedType::from_raw).collect(); - TypeParserResult { - types, - variables, - functions, - } - } - - /// Return a rust allocated type parser result, free using [`Self::free_owned_raw`]. - /// - /// Under no circumstance should you call [`Self::free_raw`] on the returned result. - pub(crate) fn into_raw(value: Self) -> BNTypeParserResult { - let boxed_raw_types: Box<[BNParsedType]> = value - .types - .into_iter() - // NOTE: Freed with [`Self::free_owned_raw`]. - .map(ParsedType::into_raw) - .collect(); - let boxed_raw_variables: Box<[BNParsedType]> = value - .variables - .into_iter() - // NOTE: Freed with [`Self::free_owned_raw`]. - .map(ParsedType::into_raw) - .collect(); - let boxed_raw_functions: Box<[BNParsedType]> = value - .functions - .into_iter() - // NOTE: Freed with [`Self::free_owned_raw`]. - .map(ParsedType::into_raw) - .collect(); - BNTypeParserResult { - typeCount: boxed_raw_types.len(), - // NOTE: Freed with [`Self::free_owned_raw`]. - types: Box::leak(boxed_raw_types).as_mut_ptr(), - variableCount: boxed_raw_variables.len(), - // NOTE: Freed with [`Self::free_owned_raw`]. - variables: Box::leak(boxed_raw_variables).as_mut_ptr(), - functionCount: boxed_raw_functions.len(), - // NOTE: Freed with [`Self::free_owned_raw`]. - functions: Box::leak(boxed_raw_functions).as_mut_ptr(), - } - } - - pub(crate) fn free_raw(mut value: BNTypeParserResult) { - // SAFETY: `value` must be a properly initialized BNTypeParserResult. - // SAFETY: `value` must be core allocated. - unsafe { BNFreeTypeParserResult(&mut value) }; - } - - pub(crate) fn free_owned_raw(value: BNTypeParserResult) { - let raw_types = std::ptr::slice_from_raw_parts_mut(value.types, value.typeCount); - // Free the rust allocated types list - let boxed_types = unsafe { Box::from_raw(raw_types) }; - for parsed_type in boxed_types { - ParsedType::free_raw(parsed_type); - } - let raw_variables = - std::ptr::slice_from_raw_parts_mut(value.variables, value.variableCount); - // Free the rust allocated variables list - let boxed_variables = unsafe { Box::from_raw(raw_variables) }; - for parsed_type in boxed_variables { - ParsedType::free_raw(parsed_type); - } - let raw_functions = - std::ptr::slice_from_raw_parts_mut(value.functions, value.functionCount); - // Free the rust allocated functions list - let boxed_functions = unsafe { Box::from_raw(raw_functions) }; - for parsed_type in boxed_functions { - ParsedType::free_raw(parsed_type); - } - } -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct ParsedType { - pub name: QualifiedName, - pub ty: Ref, - pub user: bool, -} - -impl ParsedType { - pub(crate) fn from_raw(value: &BNParsedType) -> Self { - Self { - name: QualifiedName::from_raw(&value.name), - ty: unsafe { Type::from_raw(value.type_).to_owned() }, - user: value.isUser, - } - } - - pub(crate) fn from_owned_raw(value: BNParsedType) -> Self { - let owned = Self::from_raw(&value); - Self::free_raw(value); - owned - } - - pub(crate) fn into_raw(value: Self) -> BNParsedType { - BNParsedType { - name: QualifiedName::into_raw(value.name), - type_: unsafe { Ref::into_raw(value.ty) }.handle, - isUser: value.user, - } - } - - pub(crate) fn free_raw(value: BNParsedType) { - QualifiedName::free_raw(value.name); - let _ = unsafe { Type::ref_from_raw(value.type_) }; - } - - pub fn new(name: QualifiedName, ty: Ref, user: bool) -> Self { - Self { name, ty, user } - } -} - -impl CoreArrayProvider for ParsedType { - type Raw = BNParsedType; - type Context = (); - type Wrapped<'b> = Self; -} - -unsafe impl CoreArrayProviderInner for ParsedType { - unsafe fn free(_raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { - // Expected to be freed with BNFreeTypeParserResult - // TODO ^ because of the above, we should not provide an array provider for this - } - - unsafe fn wrap_raw<'b>(raw: &'b Self::Raw, _context: &'b Self::Context) -> Self::Wrapped<'b> { - ParsedType::from_raw(raw) - } -} - -unsafe extern "C" fn cb_get_option_text( - ctxt: *mut ::std::os::raw::c_void, - option: BNTypeParserOption, - value: *const c_char, - result: *mut *mut c_char, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - if let Some(inner_result) = ctxt.get_option_text(option, &raw_to_string(value).unwrap()) { - let bn_inner_result = BnString::new(inner_result); - // NOTE: Dropped by `cb_free_string` - *result = BnString::into_raw(bn_inner_result); - true - } else { - *result = std::ptr::null_mut(); - false - } -} - -unsafe extern "C" fn cb_preprocess_source( - ctxt: *mut c_void, - source: *const c_char, - file_name: *const c_char, - platform: *mut BNPlatform, - existing_types: *mut BNTypeContainer, - options: *const *const c_char, - option_count: usize, - include_dirs: *const *const c_char, - include_dir_count: usize, - result: *mut *mut c_char, - errors: *mut *mut BNTypeParserError, - error_count: *mut usize, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - let platform = Platform { handle: platform }; - let existing_types_ptr = NonNull::new(existing_types).unwrap(); - let existing_types = TypeContainer::from_raw(existing_types_ptr); - let options_raw = unsafe { std::slice::from_raw_parts(options, option_count) }; - let options: Vec<_> = options_raw - .iter() - .filter_map(|&r| raw_to_string(r)) - .collect(); - let includes_raw = unsafe { std::slice::from_raw_parts(include_dirs, include_dir_count) }; - let includes: Vec<_> = includes_raw - .iter() - .filter_map(|&r| raw_to_string(r)) - .collect(); - match ctxt.preprocess_source( - &raw_to_string(source).unwrap(), - &raw_to_string(file_name).unwrap(), - &platform, - &existing_types, - &options, - &includes, - ) { - Ok(inner_result) => { - let bn_inner_result = BnString::new(inner_result); - // NOTE: Dropped by `cb_free_string` - *result = BnString::into_raw(bn_inner_result); - *errors = std::ptr::null_mut(); - *error_count = 0; - true - } - Err(inner_errors) => { - *result = std::ptr::null_mut(); - *error_count = inner_errors.len(); - // NOTE: Leaking errors here, dropped by `cb_free_error_list`. - let inner_errors: Box<[_]> = inner_errors - .into_iter() - .map(TypeParserError::into_raw) - .collect(); - // NOTE: Dropped by `cb_free_error_list` - *errors = Box::leak(inner_errors).as_mut_ptr(); - false - } - } -} - -unsafe extern "C" fn cb_parse_types_from_source( - ctxt: *mut c_void, - source: *const c_char, - file_name: *const c_char, - platform: *mut BNPlatform, - existing_types: *mut BNTypeContainer, - options: *const *const c_char, - option_count: usize, - include_dirs: *const *const c_char, - include_dir_count: usize, - auto_type_source: *const c_char, - result: *mut BNTypeParserResult, - errors: *mut *mut BNTypeParserError, - error_count: *mut usize, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - let platform = Platform { handle: platform }; - let existing_types_ptr = NonNull::new(existing_types).unwrap(); - let existing_types = TypeContainer::from_raw(existing_types_ptr); - let options_raw = unsafe { std::slice::from_raw_parts(options, option_count) }; - let options: Vec<_> = options_raw - .iter() - .filter_map(|&r| raw_to_string(r)) - .collect(); - let includes_raw = unsafe { std::slice::from_raw_parts(include_dirs, include_dir_count) }; - let includes: Vec<_> = includes_raw - .iter() - .filter_map(|&r| raw_to_string(r)) - .collect(); - match ctxt.parse_types_from_source( - &raw_to_string(source).unwrap(), - &raw_to_string(file_name).unwrap(), - &platform, - &existing_types, - &options, - &includes, - &raw_to_string(auto_type_source).unwrap(), - ) { - Ok(type_parser_result) => { - *result = TypeParserResult::into_raw(type_parser_result); - *errors = std::ptr::null_mut(); - *error_count = 0; - true - } - Err(inner_errors) => { - *error_count = inner_errors.len(); - let inner_errors: Box<[_]> = inner_errors - .into_iter() - .map(TypeParserError::into_raw) - .collect(); - *result = Default::default(); - // NOTE: Dropped by cb_free_error_list - *errors = Box::leak(inner_errors).as_mut_ptr(); - false - } - } -} - -unsafe extern "C" fn cb_parse_type_string( - ctxt: *mut c_void, - source: *const c_char, - platform: *mut BNPlatform, - existing_types: *mut BNTypeContainer, - result: *mut BNQualifiedNameAndType, - errors: *mut *mut BNTypeParserError, - error_count: *mut usize, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - let platform = Platform { handle: platform }; - let existing_types_ptr = NonNull::new(existing_types).unwrap(); - let existing_types = TypeContainer::from_raw(existing_types_ptr); - match ctxt.parse_type_string(&raw_to_string(source).unwrap(), &platform, &existing_types) { - Ok(inner_result) => { - *result = QualifiedNameAndType::into_raw(inner_result); - *errors = std::ptr::null_mut(); - *error_count = 0; - true - } - Err(inner_errors) => { - *error_count = inner_errors.len(); - let inner_errors: Box<[_]> = inner_errors - .into_iter() - .map(TypeParserError::into_raw) - .collect(); - *result = Default::default(); - // NOTE: Dropped by cb_free_error_list - *errors = Box::leak(inner_errors).as_mut_ptr(); - false - } - } -} - -unsafe extern "C" fn cb_free_string(_ctxt: *mut c_void, string: *mut c_char) { - // SAFETY: The returned string is just BnString - BnString::free_raw(string); -} - -unsafe extern "C" fn cb_free_result(_ctxt: *mut c_void, result: *mut BNTypeParserResult) { - TypeParserResult::free_owned_raw(*result); -} - -unsafe extern "C" fn cb_free_error_list( - _ctxt: *mut c_void, - errors: *mut BNTypeParserError, - error_count: usize, -) { - let errors = std::ptr::slice_from_raw_parts_mut(errors, error_count); - let boxed_errors = Box::from_raw(errors); - for error in boxed_errors { - TypeParserError::free_raw(error); - } -} diff --git a/rust/src/type_printer.rs b/rust/src/type_printer.rs deleted file mode 100644 index a7495f1c..00000000 --- a/rust/src/type_printer.rs +++ /dev/null @@ -1,977 +0,0 @@ -#![allow(unused)] - -use crate::binary_view::BinaryView; -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 binaryninjacore_sys::*; -use std::ffi::{c_char, c_int, c_void}; -use std::ptr::NonNull; - -pub type TokenEscapingType = BNTokenEscapingType; -pub type TypeDefinitionLineType = BNTypeDefinitionLineType; - -/// Register a custom parser with the API -pub fn register_type_printer( - name: &str, - parser: T, -) -> (&'static mut T, CoreTypePrinter) { - let parser = Box::leak(Box::new(parser)); - let mut callback = BNTypePrinterCallbacks { - context: parser as *mut _ as *mut c_void, - getTypeTokens: Some(cb_get_type_tokens::), - getTypeTokensBeforeName: Some(cb_get_type_tokens_before_name::), - getTypeTokensAfterName: Some(cb_get_type_tokens_after_name::), - getTypeString: Some(cb_get_type_string::), - getTypeStringBeforeName: Some(cb_get_type_string_before_name::), - getTypeStringAfterName: Some(cb_get_type_string_after_name::), - getTypeLines: Some(cb_get_type_lines::), - printAllTypes: Some(cb_print_all_types::), - freeTokens: Some(cb_free_tokens), - freeString: Some(cb_free_string), - freeLines: Some(cb_free_lines), - }; - let raw_name = name.to_cstr(); - let result = unsafe { BNRegisterTypePrinter(raw_name.as_ptr(), &mut callback) }; - let core = unsafe { CoreTypePrinter::from_raw(NonNull::new(result).unwrap()) }; - (parser, core) -} - -#[repr(transparent)] -pub struct CoreTypePrinter { - pub(crate) handle: NonNull, -} - -impl CoreTypePrinter { - pub(crate) unsafe fn from_raw(handle: NonNull) -> CoreTypePrinter { - Self { handle } - } - - pub fn printers() -> Array { - let mut count = 0; - let result = unsafe { BNGetTypePrinterList(&mut count) }; - assert!(!result.is_null()); - unsafe { Array::new(result, count, ()) } - } - - pub fn printer_by_name(name: &str) -> Option { - let name_raw = name.to_cstr(); - let result = unsafe { BNGetTypePrinterByName(name_raw.as_ptr()) }; - NonNull::new(result).map(|x| unsafe { Self::from_raw(x) }) - } - - pub fn name(&self) -> String { - let result = unsafe { BNGetTypePrinterName(self.handle.as_ptr()) }; - assert!(!result.is_null()); - unsafe { BnString::into_string(result) } - } - - pub fn get_type_tokens>( - &self, - type_: &Type, - platform: &Platform, - name: T, - base_confidence: u8, - escaping: TokenEscapingType, - ) -> Option> { - let mut result_count = 0; - let mut result = std::ptr::null_mut(); - let mut raw_name = QualifiedName::into_raw(name.into()); - let success = unsafe { - BNGetTypePrinterTypeTokens( - self.handle.as_ptr(), - type_.handle, - platform.handle, - &mut raw_name, - base_confidence, - escaping, - &mut result, - &mut result_count, - ) - }; - QualifiedName::free_raw(raw_name); - success.then(|| { - assert!(!result.is_null()); - unsafe { Array::new(result, result_count, ()) } - }) - } - - pub fn get_type_tokens_before_name( - &self, - type_: &Type, - platform: &Platform, - base_confidence: u8, - parent_type: &Type, - escaping: TokenEscapingType, - ) -> Option> { - let mut result_count = 0; - let mut result = std::ptr::null_mut(); - let success = unsafe { - BNGetTypePrinterTypeTokensBeforeName( - self.handle.as_ptr(), - type_.handle, - platform.handle, - base_confidence, - parent_type.handle, - escaping, - &mut result, - &mut result_count, - ) - }; - success.then(|| { - assert!(!result.is_null()); - unsafe { Array::new(result, result_count, ()) } - }) - } - - pub fn get_type_tokens_after_name( - &self, - type_: &Type, - platform: &Platform, - base_confidence: u8, - parent_type: &Type, - escaping: TokenEscapingType, - ) -> Option> { - let mut result_count = 0; - let mut result = std::ptr::null_mut(); - let success = unsafe { - BNGetTypePrinterTypeTokensAfterName( - self.handle.as_ptr(), - type_.handle, - platform.handle, - base_confidence, - parent_type.handle, - escaping, - &mut result, - &mut result_count, - ) - }; - success.then(|| { - assert!(!result.is_null()); - unsafe { Array::new(result, result_count, ()) } - }) - } - - pub fn get_type_string>( - &self, - type_: &Type, - platform: &Platform, - name: T, - escaping: TokenEscapingType, - ) -> Option { - let mut result = std::ptr::null_mut(); - let mut raw_name = QualifiedName::into_raw(name.into()); - let success = unsafe { - BNGetTypePrinterTypeString( - self.handle.as_ptr(), - type_.handle, - platform.handle, - &mut raw_name, - escaping, - &mut result, - ) - }; - QualifiedName::free_raw(raw_name); - success.then(|| unsafe { - assert!(!result.is_null()); - BnString::from_raw(result) - }) - } - - pub fn get_type_string_before_name( - &self, - type_: &Type, - platform: &Platform, - escaping: BNTokenEscapingType, - ) -> Option { - let mut result = std::ptr::null_mut(); - let success = unsafe { - BNGetTypePrinterTypeStringAfterName( - self.handle.as_ptr(), - type_.handle, - platform.handle, - escaping, - &mut result, - ) - }; - success.then(|| unsafe { - assert!(!result.is_null()); - BnString::from_raw(result) - }) - } - - pub fn get_type_string_after_name( - &self, - type_: &Type, - platform: &Platform, - escaping: TokenEscapingType, - ) -> Option { - let mut result = std::ptr::null_mut(); - let success = unsafe { - BNGetTypePrinterTypeStringBeforeName( - self.handle.as_ptr(), - type_.handle, - platform.handle, - escaping, - &mut result, - ) - }; - success.then(|| unsafe { - assert!(!result.is_null()); - BnString::from_raw(result) - }) - } - - pub fn get_type_lines>( - &self, - type_: &Type, - types: &TypeContainer, - name: T, - padding_cols: isize, - collapsed: bool, - escaping: TokenEscapingType, - ) -> Option> { - let mut result_count = 0; - let mut result = std::ptr::null_mut(); - let mut raw_name = QualifiedName::into_raw(name.into()); - let success = unsafe { - BNGetTypePrinterTypeLines( - self.handle.as_ptr(), - type_.handle, - types.handle.as_ptr(), - &mut raw_name, - padding_cols as c_int, - collapsed, - escaping, - &mut result, - &mut result_count, - ) - }; - QualifiedName::free_raw(raw_name); - success.then(|| { - assert!(!result.is_null()); - unsafe { Array::::new(result, result_count, ()) } - }) - } - - /// Print all types to a single big string, including headers, sections, etc - /// - /// * `types` - All types to print - /// * `data` - Binary View in which all the types are defined - /// * `padding_cols` - Maximum number of bytes represented by each padding line - /// * `escaping` - Style of escaping literals which may not be parsable - pub fn default_print_all_types( - &self, - types: T, - data: &BinaryView, - padding_cols: isize, - escaping: TokenEscapingType, - ) -> Option - where - T: Iterator, - I: Into, - { - let mut result = std::ptr::null_mut(); - let (mut raw_names, mut raw_types): (Vec, Vec<_>) = types - .map(|t| { - let t = t.into(); - // Leak both to the core and then free afterwards. - ( - QualifiedName::into_raw(t.name), - unsafe { Ref::into_raw(t.ty) }.handle, - ) - }) - .unzip(); - let success = unsafe { - BNTypePrinterDefaultPrintAllTypes( - self.handle.as_ptr(), - raw_names.as_mut_ptr(), - raw_types.as_mut_ptr(), - raw_types.len(), - data.handle, - padding_cols as c_int, - escaping, - &mut result, - ) - }; - for raw_name in raw_names { - QualifiedName::free_raw(raw_name); - } - for raw_type in raw_types { - let _ = unsafe { Type::ref_from_raw(raw_type) }; - } - success.then(|| unsafe { - assert!(!result.is_null()); - BnString::from_raw(result) - }) - } - - pub fn print_all_types( - &self, - types: T, - data: &BinaryView, - padding_cols: isize, - escaping: TokenEscapingType, - ) -> Option - where - T: IntoIterator, - I: Into, - { - let mut result = std::ptr::null_mut(); - // TODO: I dislike how this iter unzip looks like... but its how to avoid allocating again... - let (mut raw_names, mut raw_types): (Vec, Vec<_>) = types - .into_iter() - .map(|t| { - let t = t.into(); - // Leak both to the core and then free afterwards. - ( - QualifiedName::into_raw(t.name), - unsafe { Ref::into_raw(t.ty) }.handle, - ) - }) - .unzip(); - let success = unsafe { - BNTypePrinterPrintAllTypes( - self.handle.as_ptr(), - raw_names.as_mut_ptr(), - raw_types.as_mut_ptr(), - raw_types.len(), - data.handle, - padding_cols as c_int, - escaping, - &mut result, - ) - }; - for raw_name in raw_names { - QualifiedName::free_raw(raw_name); - } - for raw_type in raw_types { - let _ = unsafe { Type::ref_from_raw(raw_type) }; - } - success.then(|| unsafe { - assert!(!result.is_null()); - BnString::from_raw(result) - }) - } -} - -impl Default for CoreTypePrinter { - fn default() -> Self { - // TODO: Remove this entirely, there is no "default", its view specific lets not make this some defined behavior. - let default_settings = crate::settings::Settings::new(); - let name = default_settings.get_string("analysis.types.printerName"); - Self::printer_by_name(&name).unwrap() - } -} - -impl CoreArrayProvider for CoreTypePrinter { - type Raw = *mut BNTypePrinter; - type Context = (); - type Wrapped<'a> = Self; -} - -unsafe impl CoreArrayProviderInner for CoreTypePrinter { - unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { - BNFreeTypePrinterList(raw) - } - - unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { - // TODO: Because handle is a NonNull we should prob make Self::Raw that as well... - let handle = NonNull::new(*raw).unwrap(); - CoreTypePrinter::from_raw(handle) - } -} - -pub trait TypePrinter { - /// Generate a single-line text representation of a type, Returns a List - /// of text tokens representing the type. - /// - /// * `type_` - Type to print - /// * `platform` - Platform responsible for this type - /// * `name` - Name of the type - /// * `base_confidence` - Confidence to use for tokens created for this type - /// * `escaping` - Style of escaping literals which may not be parsable - fn get_type_tokens>( - &self, - type_: Ref, - platform: Option>, - name: T, - base_confidence: u8, - escaping: TokenEscapingType, - ) -> Option>; - - /// In a single-line text representation of a type, generate the tokens that - /// should be printed before the type's name. Returns a list of text tokens - /// representing the type - /// - /// * `type_` - Type to print - /// * `platform` - Platform responsible for this type - /// * `base_confidence` - Confidence to use for tokens created for this type - /// * `parent_type` - Type of the parent of this type, or None - /// * `escaping` - Style of escaping literals which may not be parsable - fn get_type_tokens_before_name( - &self, - type_: Ref, - platform: Option>, - base_confidence: u8, - parent_type: Option>, - escaping: TokenEscapingType, - ) -> Option>; - - /// In a single-line text representation of a type, generate the tokens - /// that should be printed after the type's name. Returns a list of text - /// tokens representing the type - /// - /// * `type_` - Type to print - /// * `platform` - Platform responsible for this type - /// * `base_confidence` - Confidence to use for tokens created for this type - /// * `parent_type` - Type of the parent of this type, or None - /// * `escaping` - Style of escaping literals which may not be parsable - fn get_type_tokens_after_name( - &self, - type_: Ref, - platform: Option>, - base_confidence: u8, - parent_type: Option>, - escaping: TokenEscapingType, - ) -> Option>; - - /// Generate a single-line text representation of a type. Returns a string - /// representing the type - /// - /// * `type_` - Type to print - /// * `platform` - Platform responsible for this type - /// * `name` - Name of the type - /// * `escaping` - Style of escaping literals which may not be parsable - fn get_type_string>( - &self, - type_: Ref, - platform: Option>, - name: T, - escaping: TokenEscapingType, - ) -> Option; - - /// In a single-line text representation of a type, generate the string that - /// should be printed before the type's name. Returns a string representing - /// the type - /// - /// * `type_` - Type to print - /// * `platform` - Platform responsible for this type - /// * `escaping` - Style of escaping literals which may not be parsable - fn get_type_string_before_name( - &self, - type_: Ref, - platform: Option>, - escaping: TokenEscapingType, - ) -> Option; - - /// In a single-line text representation of a type, generate the string that - /// should be printed after the type's name. Returns a string representing - /// the type - /// - /// * `type_` - Type to print - /// * `platform` - Platform responsible for this type - /// * `escaping` - Style of escaping literals which may not be parsable - fn get_type_string_after_name( - &self, - type_: Ref, - platform: Option>, - escaping: TokenEscapingType, - ) -> Option; - - /// Generate a multi-line representation of a type. Returns a list of type - /// definition lines - /// - /// * `type_` - Type to print - /// * `types` - Type Container containing the type and dependencies - /// * `name` - Name of the type - /// * `padding_cols` - Maximum number of bytes represented by each padding line - /// * `collapsed` - Whether to collapse structure/enum blocks - /// * `escaping` - Style of escaping literals which may not be parsable - fn get_type_lines>( - &self, - type_: Ref, - types: &TypeContainer, - name: T, - padding_cols: isize, - collapsed: bool, - escaping: TokenEscapingType, - ) -> Option>; - - /// Print all types to a single big string, including headers, sections, - /// etc. - /// - /// * `types` - All types to print - /// * `data` - Binary View in which all the types are defined - /// * `padding_cols` - Maximum number of bytes represented by each padding line - /// * `escaping` - Style of escaping literals which may not be parsable - fn print_all_types( - &self, - names: Vec, - types: Vec>, - data: Ref, - padding_cols: isize, - escaping: TokenEscapingType, - ) -> Option; -} - -// TODO: This needs an extreme amount of documentation... -#[derive(Clone)] -pub struct TypeDefinitionLine { - pub line_type: TypeDefinitionLineType, - pub tokens: Vec, - pub ty: Ref, - pub parent_type: Option>, - // TODO: Document what the root type is. - pub root_type: Option>, - pub root_type_name: Option, - // TODO: Document the base type, and why its a ntr instead of type + name like root type - pub base_type: Option>, - // TODO: These can also be optional? - pub base_offset: u64, - pub offset: u64, - pub field_index: usize, -} - -impl TypeDefinitionLine { - pub(crate) fn from_raw(value: &BNTypeDefinitionLine) -> Self { - Self { - line_type: value.lineType, - tokens: { - let raw_tokens = unsafe { std::slice::from_raw_parts(value.tokens, value.count) }; - raw_tokens - .iter() - .map(InstructionTextToken::from_raw) - .collect() - }, - ty: unsafe { Type::from_raw(value.type_).to_owned() }, - parent_type: match value.parentType.is_null() { - false => Some(unsafe { Type::from_raw(value.parentType).to_owned() }), - true => None, - }, - root_type: match value.rootType.is_null() { - false => Some(unsafe { Type::from_raw(value.rootType).to_owned() }), - true => None, - }, - root_type_name: match value.rootTypeName.is_null() { - false => Some(raw_to_string(value.rootTypeName).unwrap()), - true => None, - }, - base_type: match value.baseType.is_null() { - false => Some(unsafe { NamedTypeReference::from_raw(value.baseType).to_owned() }), - true => None, - }, - base_offset: value.baseOffset, - offset: value.offset, - field_index: value.fieldIndex, - } - } - - /// The raw value must have been allocated by rust. See [`Self::free_owned_raw`] for details. - pub(crate) fn from_owned_raw(value: BNTypeDefinitionLine) -> Self { - let owned = Self::from_raw(&value); - Self::free_owned_raw(value); - owned - } - - pub(crate) fn into_raw(value: Self) -> BNTypeDefinitionLine { - // NOTE: This is leaking [BNInstructionTextToken::text], [BNInstructionTextToken::typeNames]. - let tokens: Box<[BNInstructionTextToken]> = value - .tokens - .into_iter() - .map(InstructionTextToken::into_raw) - .collect(); - BNTypeDefinitionLine { - lineType: value.line_type, - count: tokens.len(), - // NOTE: This is leaking tokens. Must free with `cb_free_lines`. - tokens: Box::leak(tokens).as_mut_ptr(), - // NOTE: This is leaking a ref to ty. Must free with `cb_free_lines`. - type_: unsafe { Ref::into_raw(value.ty) }.handle, - // NOTE: This is leaking a ref to parent_type. Must free with `cb_free_lines`. - parentType: value - .parent_type - .map(|t| unsafe { Ref::into_raw(t) }.handle) - .unwrap_or(std::ptr::null_mut()), - // NOTE: This is leaking a ref to root_type. Must free with `cb_free_lines`. - rootType: value - .root_type - .map(|t| unsafe { Ref::into_raw(t) }.handle) - .unwrap_or(std::ptr::null_mut()), - // NOTE: This is leaking root_type_name. Must free with `cb_free_lines`. - rootTypeName: value - .root_type_name - .map(|s| BnString::into_raw(BnString::new(s))) - .unwrap_or(std::ptr::null_mut()), - // NOTE: This is leaking a ref to base_type. Must free with `cb_free_lines`. - baseType: value - .base_type - .map(|t| unsafe { Ref::into_raw(t) }.handle) - .unwrap_or(std::ptr::null_mut()), - baseOffset: value.base_offset, - offset: value.offset, - fieldIndex: value.field_index, - } - } - - /// This is unique from the typical `from_raw` as the allocation of InstructionTextToken requires it be from rust, hence the "owned" free. - pub(crate) fn free_owned_raw(raw: BNTypeDefinitionLine) { - if !raw.tokens.is_null() { - let tokens = std::ptr::slice_from_raw_parts_mut(raw.tokens, raw.count); - // SAFETY: raw.tokens must have been allocated by rust. - let boxed_tokens = unsafe { Box::from_raw(tokens) }; - for token in boxed_tokens { - InstructionTextToken::free_raw(token); - } - } - if !raw.type_.is_null() { - // SAFETY: raw.type_ must have been ref incremented in conjunction with this free - let _ = unsafe { Type::ref_from_raw(raw.type_) }; - } - if !raw.parentType.is_null() { - // SAFETY: raw.parentType must have been ref incremented in conjunction with this free - let _ = unsafe { Type::ref_from_raw(raw.parentType) }; - } - if !raw.rootType.is_null() { - // SAFETY: raw.rootType must have been ref incremented in conjunction with this free - let _ = unsafe { Type::ref_from_raw(raw.rootType) }; - } - if !raw.rootTypeName.is_null() { - // SAFETY: raw.rootTypeName must have been ref incremented in conjunction with this free - let _ = unsafe { BnString::from_raw(raw.rootTypeName) }; - } - if !raw.baseType.is_null() { - // SAFETY: raw.baseType must have been ref incremented in conjunction with this free - let _ = unsafe { NamedTypeReference::ref_from_raw(raw.baseType) }; - } - } -} - -impl CoreArrayProvider for TypeDefinitionLine { - type Raw = BNTypeDefinitionLine; - type Context = (); - type Wrapped<'a> = Self; -} - -unsafe impl CoreArrayProviderInner for TypeDefinitionLine { - unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { - unsafe { BNFreeTypeDefinitionLineList(raw, count) }; - } - - unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { - Self::from_raw(raw) - } -} - -unsafe extern "C" fn cb_get_type_tokens( - ctxt: *mut ::std::os::raw::c_void, - type_: *mut BNType, - platform: *mut BNPlatform, - name: *mut BNQualifiedName, - base_confidence: u8, - escaping: BNTokenEscapingType, - result: *mut *mut BNInstructionTextToken, - result_count: *mut usize, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - // NOTE: The caller is responsible for freeing name. - let qualified_name = QualifiedName::from_raw(&*name); - let inner_result = ctxt.get_type_tokens( - unsafe { Type::ref_from_raw(type_) }, - match platform.is_null() { - false => Some(Platform::ref_from_raw(platform)), - true => None, - }, - qualified_name, - base_confidence, - escaping, - ); - if let Some(inner_result) = inner_result { - let raw_text_tokens: Box<[BNInstructionTextToken]> = inner_result - .into_iter() - .map(InstructionTextToken::into_raw) - .collect(); - *result_count = raw_text_tokens.len(); - // NOTE: Dropped by the cb_free_tokens - *result = Box::leak(raw_text_tokens).as_mut_ptr(); - true - } else { - *result = std::ptr::null_mut(); - *result_count = 0; - false - } -} - -unsafe extern "C" fn cb_get_type_tokens_before_name( - ctxt: *mut ::std::os::raw::c_void, - type_: *mut BNType, - platform: *mut BNPlatform, - base_confidence: u8, - parent_type: *mut BNType, - escaping: BNTokenEscapingType, - result: *mut *mut BNInstructionTextToken, - result_count: *mut usize, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - let inner_result = ctxt.get_type_tokens_before_name( - Type::ref_from_raw(type_), - match platform.is_null() { - false => Some(Platform::ref_from_raw(platform)), - true => None, - }, - base_confidence, - match parent_type.is_null() { - false => Some(Type::ref_from_raw(parent_type)), - true => None, - }, - escaping, - ); - if let Some(inner_result) = inner_result { - let raw_text_tokens: Box<[BNInstructionTextToken]> = inner_result - .into_iter() - .map(InstructionTextToken::into_raw) - .collect(); - *result_count = raw_text_tokens.len(); - // NOTE: Dropped by the cb_free_tokens - *result = Box::leak(raw_text_tokens).as_mut_ptr(); - true - } else { - *result = std::ptr::null_mut(); - *result_count = 0; - false - } -} - -unsafe extern "C" fn cb_get_type_tokens_after_name( - ctxt: *mut ::std::os::raw::c_void, - type_: *mut BNType, - platform: *mut BNPlatform, - base_confidence: u8, - parent_type: *mut BNType, - escaping: BNTokenEscapingType, - result: *mut *mut BNInstructionTextToken, - result_count: *mut usize, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - let inner_result = ctxt.get_type_tokens_after_name( - Type::ref_from_raw(type_), - match platform.is_null() { - false => Some(Platform::ref_from_raw(platform)), - true => None, - }, - base_confidence, - match parent_type.is_null() { - false => Some(Type::ref_from_raw(parent_type)), - true => None, - }, - escaping, - ); - if let Some(inner_result) = inner_result { - let raw_text_tokens: Box<[BNInstructionTextToken]> = inner_result - .into_iter() - .map(InstructionTextToken::into_raw) - .collect(); - *result_count = raw_text_tokens.len(); - // NOTE: Dropped by the cb_free_tokens - *result = Box::leak(raw_text_tokens).as_mut_ptr(); - true - } else { - *result = std::ptr::null_mut(); - *result_count = 0; - false - } -} - -unsafe extern "C" fn cb_get_type_string( - ctxt: *mut ::std::os::raw::c_void, - type_: *mut BNType, - platform: *mut BNPlatform, - name: *mut BNQualifiedName, - escaping: BNTokenEscapingType, - result: *mut *mut ::std::os::raw::c_char, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - // NOTE: The caller is responsible for freeing name. - let qualified_name = QualifiedName::from_raw(&*name); - let inner_result = ctxt.get_type_string( - Type::ref_from_raw(type_), - match platform.is_null() { - false => Some(Platform::ref_from_raw(platform)), - true => None, - }, - qualified_name, - escaping, - ); - if let Some(inner_result) = inner_result { - let raw_string = BnString::new(inner_result); - // NOTE: Dropped by `cb_free_string` - *result = BnString::into_raw(raw_string); - true - } else { - *result = std::ptr::null_mut(); - false - } -} - -unsafe extern "C" fn cb_get_type_string_before_name( - ctxt: *mut ::std::os::raw::c_void, - type_: *mut BNType, - platform: *mut BNPlatform, - escaping: BNTokenEscapingType, - result: *mut *mut ::std::os::raw::c_char, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - let inner_result = ctxt.get_type_string_before_name( - Type::ref_from_raw(type_), - match platform.is_null() { - false => Some(Platform::ref_from_raw(platform)), - true => None, - }, - escaping, - ); - if let Some(inner_result) = inner_result { - // NOTE: Dropped by `cb_free_string` - let raw_string = BnString::new(inner_result); - *result = BnString::into_raw(raw_string); - true - } else { - *result = std::ptr::null_mut(); - false - } -} - -unsafe extern "C" fn cb_get_type_string_after_name( - ctxt: *mut ::std::os::raw::c_void, - type_: *mut BNType, - platform: *mut BNPlatform, - escaping: BNTokenEscapingType, - result: *mut *mut ::std::os::raw::c_char, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - let inner_result = ctxt.get_type_string_after_name( - Type::ref_from_raw(type_), - match platform.is_null() { - false => Some(Platform::ref_from_raw(platform)), - true => None, - }, - escaping, - ); - if let Some(inner_result) = inner_result { - let raw_string = BnString::new(inner_result); - // NOTE: Dropped by `cb_free_string` - *result = BnString::into_raw(raw_string); - true - } else { - *result = std::ptr::null_mut(); - false - } -} - -unsafe extern "C" fn cb_get_type_lines( - ctxt: *mut ::std::os::raw::c_void, - type_: *mut BNType, - types: *mut BNTypeContainer, - name: *mut BNQualifiedName, - padding_cols: ::std::os::raw::c_int, - collapsed: bool, - escaping: BNTokenEscapingType, - result: *mut *mut BNTypeDefinitionLine, - result_count: *mut usize, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - // NOTE: The caller is responsible for freeing name. - let qualified_name = QualifiedName::from_raw(&*name); - let types_ptr = NonNull::new(types).unwrap(); - let types = TypeContainer::from_raw(types_ptr); - let inner_result = ctxt.get_type_lines( - Type::ref_from_raw(type_), - &types, - qualified_name, - padding_cols as isize, - collapsed, - escaping, - ); - if let Some(inner_result) = inner_result { - let boxed_raw_lines: Box<[_]> = inner_result - .into_iter() - .map(TypeDefinitionLine::into_raw) - .collect(); - *result_count = boxed_raw_lines.len(); - // NOTE: Dropped by `cb_free_lines` - *result = Box::leak(boxed_raw_lines).as_mut_ptr(); - true - } else { - *result = std::ptr::null_mut(); - *result_count = 0; - false - } -} - -unsafe extern "C" fn cb_print_all_types( - ctxt: *mut ::std::os::raw::c_void, - names: *mut BNQualifiedName, - types: *mut *mut BNType, - type_count: usize, - data: *mut BNBinaryView, - padding_cols: ::std::os::raw::c_int, - escaping: BNTokenEscapingType, - result: *mut *mut ::std::os::raw::c_char, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - let raw_names = std::slice::from_raw_parts(names, type_count); - // NOTE: The caller is responsible for freeing raw_names. - let names: Vec<_> = raw_names.iter().map(QualifiedName::from_raw).collect(); - let raw_types = std::slice::from_raw_parts(types, type_count); - // NOTE: The caller is responsible for freeing raw_types. - let types: Vec<_> = raw_types.iter().map(|&t| Type::ref_from_raw(t)).collect(); - let inner_result = ctxt.print_all_types( - names, - types, - BinaryView::ref_from_raw(data), - padding_cols as isize, - escaping, - ); - if let Some(inner_result) = inner_result { - let raw_string = BnString::new(inner_result); - // NOTE: Dropped by `cb_free_string` - *result = BnString::into_raw(raw_string); - true - } else { - *result = std::ptr::null_mut(); - false - } -} - -unsafe extern "C" fn cb_free_string(_ctxt: *mut c_void, string: *mut c_char) { - // SAFETY: The returned string is just BnString - BnString::free_raw(string); -} - -unsafe extern "C" fn cb_free_tokens( - _ctxt: *mut ::std::os::raw::c_void, - tokens: *mut BNInstructionTextToken, - count: usize, -) { - let tokens = std::ptr::slice_from_raw_parts_mut(tokens, count); - // SAFETY: tokens must have been allocated by rust. - let boxed_tokens = Box::from_raw(tokens); - for token in boxed_tokens { - InstructionTextToken::free_raw(token); - } -} - -unsafe extern "C" fn cb_free_lines( - _ctxt: *mut ::std::os::raw::c_void, - lines: *mut BNTypeDefinitionLine, - count: usize, -) { - let lines = std::ptr::slice_from_raw_parts_mut(lines, count); - // SAFETY: lines must have been allocated by rust. - let boxes_lines = Box::from_raw(lines); - for line in boxes_lines { - TypeDefinitionLine::free_owned_raw(line); - } -} diff --git a/rust/src/types.rs b/rust/src/types.rs index 1387ffb3..cfdc865b 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/types/archive.rs b/rust/src/types/archive.rs new file mode 100644 index 00000000..15f4cbfe --- /dev/null +++ b/rust/src/types/archive.rs @@ -0,0 +1,1174 @@ +use crate::progress::{NoProgressCallback, ProgressCallback}; +use binaryninjacore_sys::*; +use std::ffi::{c_char, c_void, CStr, CString}; +use std::fmt::{Debug, Display, Formatter}; +use std::hash::Hash; +use std::path::{Path, PathBuf}; +use std::ptr::NonNull; + +use crate::data_buffer::DataBuffer; +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::types::{ + QualifiedName, QualifiedNameAndType, QualifiedNameTypeAndId, Type, TypeContainer, +}; + +#[repr(transparent)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TypeArchiveId(pub String); + +impl Display for TypeArchiveId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{}", self.0)) + } +} + +#[repr(transparent)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TypeArchiveSnapshotId(pub String); + +impl TypeArchiveSnapshotId { + pub fn unset() -> Self { + Self("".to_string()) + } +} + +impl Display for TypeArchiveSnapshotId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("{}", self.0)) + } +} + +impl CoreArrayProvider for TypeArchiveSnapshotId { + type Raw = *mut c_char; + type Context = (); + type Wrapped<'a> = TypeArchiveSnapshotId; +} + +unsafe impl CoreArrayProviderInner for TypeArchiveSnapshotId { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeStringList(raw, count) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + let str = CStr::from_ptr(*raw).to_str().unwrap().to_string(); + TypeArchiveSnapshotId(str) + } +} + +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. +pub struct TypeArchive { + pub(crate) handle: NonNull, +} + +impl TypeArchive { + pub(crate) unsafe fn from_raw(handle: NonNull) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: NonNull) -> Ref { + Ref::new(Self { handle }) + } + + /// Open the Type Archive at the given path, if it exists. + pub fn open(path: impl AsRef) -> Option> { + let raw_path = path.as_ref().to_cstr(); + let handle = unsafe { BNOpenTypeArchive(raw_path.as_ptr()) }; + NonNull::new(handle).map(|handle| unsafe { TypeArchive::ref_from_raw(handle) }) + } + + /// Create a Type Archive at the given path, returning `None` if it could not be created. + /// + /// If the file has already been created and is not a valid type archive this will return `None`. + pub fn create(path: impl AsRef, platform: &Platform) -> Option> { + let raw_path = path.as_ref().to_cstr(); + let handle = unsafe { BNCreateTypeArchive(raw_path.as_ptr(), platform.handle) }; + NonNull::new(handle).map(|handle| unsafe { TypeArchive::ref_from_raw(handle) }) + } + + /// Create a Type Archive at the given path and id, returning `None` if it could not be created. + /// + /// If the file has already been created and is not a valid type archive this will return `None`. + pub fn create_with_id( + path: impl AsRef, + id: &TypeArchiveId, + platform: &Platform, + ) -> Option> { + let raw_path = path.as_ref().to_cstr(); + let id = id.0.as_str().to_cstr(); + let handle = + unsafe { BNCreateTypeArchiveWithId(raw_path.as_ptr(), platform.handle, id.as_ptr()) }; + NonNull::new(handle).map(|handle| unsafe { TypeArchive::ref_from_raw(handle) }) + } + + /// Get a reference to the Type Archive with the known id, if one exists. + pub fn lookup_by_id(id: &TypeArchiveId) -> Option> { + let id = id.0.as_str().to_cstr(); + let handle = unsafe { BNLookupTypeArchiveById(id.as_ptr()) }; + NonNull::new(handle).map(|handle| unsafe { TypeArchive::ref_from_raw(handle) }) + } + + /// Get the path to the Type Archive's file + pub fn path(&self) -> Option { + let result = unsafe { BNGetTypeArchivePath(self.handle.as_ptr()) }; + assert!(!result.is_null()); + let path_str = unsafe { BnString::into_string(result) }; + Some(PathBuf::from(path_str)) + } + + /// Get the guid for a Type Archive + pub fn id(&self) -> TypeArchiveId { + let result = unsafe { BNGetTypeArchiveId(self.handle.as_ptr()) }; + assert!(!result.is_null()); + let result_str = unsafe { BnString::from_raw(result) }; + TypeArchiveId(result_str.to_string_lossy().to_string()) + } + + /// Get the associated Platform for a Type Archive + pub fn platform(&self) -> Ref { + let result = unsafe { BNGetTypeArchivePlatform(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { Platform::ref_from_raw(result) } + } + + /// Get the id of the current snapshot in the type archive + pub fn current_snapshot_id(&self) -> TypeArchiveSnapshotId { + let result = unsafe { BNGetTypeArchiveCurrentSnapshotId(self.handle.as_ptr()) }; + assert!(!result.is_null()); + let id = unsafe { BnString::into_string(result) }; + TypeArchiveSnapshotId(id) + } + + /// Revert the type archive's current snapshot to the given snapshot + pub fn set_current_snapshot_id(&self, id: &TypeArchiveSnapshotId) { + let snapshot = id.clone().to_cstr(); + unsafe { BNSetTypeArchiveCurrentSnapshot(self.handle.as_ptr(), snapshot.as_ptr()) } + } + + /// Get a list of every snapshot's id + pub fn all_snapshot_ids(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetTypeArchiveAllSnapshotIds(self.handle.as_ptr(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get the ids of the parents to the given snapshot + pub fn get_snapshot_parent_ids( + &self, + snapshot: &TypeArchiveSnapshotId, + ) -> Option> { + let mut count = 0; + let snapshot = snapshot.clone().to_cstr(); + let result = unsafe { + BNGetTypeArchiveSnapshotParentIds(self.handle.as_ptr(), snapshot.as_ptr(), &mut count) + }; + (!result.is_null()).then(|| unsafe { Array::new(result, count, ()) }) + } + + /// Get the ids of the children to the given snapshot + pub fn get_snapshot_child_ids( + &self, + snapshot: &TypeArchiveSnapshotId, + ) -> Option> { + let mut count = 0; + let snapshot = snapshot.clone().to_cstr(); + let result = unsafe { + BNGetTypeArchiveSnapshotChildIds(self.handle.as_ptr(), snapshot.as_ptr(), &mut count) + }; + (!result.is_null()).then(|| unsafe { Array::new(result, count, ()) }) + } + + /// Add a named type to the type archive. Type must have all dependant named types added + /// prior to being added, or this function will fail. + /// If the type already exists, it will be overwritten. + /// + /// * `named_type` - Named type to add + pub fn add_type(&self, named_type: QualifiedNameAndType) -> bool { + self.add_types(vec![named_type]) + } + + /// Add named types to the type archive. Types must have all dependant named + /// types prior to being added, or included in the list, or this function will fail. + /// Types already existing with any added names will be overwritten. + /// + /// * `named_types` - Names and definitions of new types + pub fn add_types(&self, named_types: Vec) -> bool { + let new_types_raw: Vec<_> = named_types + .into_iter() + .map(QualifiedNameAndType::into_raw) + .collect(); + let result = unsafe { + BNAddTypeArchiveTypes( + self.handle.as_ptr(), + new_types_raw.as_ptr(), + new_types_raw.len(), + ) + }; + for new_type in new_types_raw { + QualifiedNameAndType::free_raw(new_type); + } + result + } + + /// Change the name of an existing type in the type archive. Returns false if failed. + /// + /// * `old_name` - Old type name in archive + /// * `new_name` - New type name + pub fn rename_type(&self, old_name: QualifiedName, new_name: QualifiedName) -> bool { + if let Some(id) = self.get_type_id(old_name) { + self.rename_type_by_id(&id, new_name) + } else { + false + } + } + + /// Change the name of an existing type in the type archive. Returns false if failed. + /// + /// * `id` - Old id of type in archive + /// * `new_name` - New type name + pub fn rename_type_by_id(&self, id: &str, new_name: QualifiedName) -> bool { + let id = id.to_cstr(); + let raw_name = QualifiedName::into_raw(new_name); + let result = + unsafe { BNRenameTypeArchiveType(self.handle.as_ptr(), id.as_ptr(), &raw_name) }; + QualifiedName::free_raw(raw_name); + result + } + + /// Delete an existing type in the type archive. + pub fn delete_type(&self, name: QualifiedName) -> bool { + if let Some(type_id) = self.get_type_id(name) { + self.delete_type_by_id(&type_id) + } else { + false + } + } + + /// Delete an existing type in the type archive. + pub fn delete_type_by_id(&self, id: &str) -> bool { + let id = id.to_cstr(); + unsafe { BNDeleteTypeArchiveType(self.handle.as_ptr(), id.as_ptr()) } + } + + /// Retrieve a stored type in the archive + /// + /// * `name` - Type name + pub fn get_type_by_name(&self, name: QualifiedName) -> Option> { + self.get_type_by_name_from_snapshot(name, &TypeArchiveSnapshotId::unset()) + } + + /// Retrieve a stored type in the archive + /// + /// * `name` - Type name + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_by_name_from_snapshot( + &self, + name: QualifiedName, + snapshot: &TypeArchiveSnapshotId, + ) -> Option> { + let raw_name = QualifiedName::into_raw(name); + let snapshot = snapshot.clone().to_cstr(); + let result = unsafe { + BNGetTypeArchiveTypeByName(self.handle.as_ptr(), &raw_name, snapshot.as_ptr()) + }; + QualifiedName::free_raw(raw_name); + (!result.is_null()).then(|| unsafe { Type::ref_from_raw(result) }) + } + + /// Retrieve a stored type in the archive by id + /// + /// * `id` - Type id + pub fn get_type_by_id(&self, id: &str) -> Option> { + self.get_type_by_id_from_snapshot(id, &TypeArchiveSnapshotId::unset()) + } + + /// Retrieve a stored type in the archive by id + /// + /// * `id` - Type id + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_by_id_from_snapshot( + &self, + id: &str, + snapshot: &TypeArchiveSnapshotId, + ) -> Option> { + let id = id.to_cstr(); + let snapshot = snapshot.clone().to_cstr(); + let result = unsafe { + BNGetTypeArchiveTypeById(self.handle.as_ptr(), id.as_ptr(), snapshot.as_ptr()) + }; + (!result.is_null()).then(|| unsafe { Type::ref_from_raw(result) }) + } + + /// Retrieve a type's name by its id + /// + /// * `id` - Type id + pub fn get_type_name_by_id(&self, id: &str) -> QualifiedName { + self.get_type_name_by_id_from_snapshot(id, &TypeArchiveSnapshotId::unset()) + } + + /// Retrieve a type's name by its id + /// + /// * `id` - Type id + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_name_by_id_from_snapshot( + &self, + id: &str, + snapshot: &TypeArchiveSnapshotId, + ) -> QualifiedName { + let id = id.to_cstr(); + let snapshot = snapshot.clone().to_cstr(); + let result = unsafe { + BNGetTypeArchiveTypeName(self.handle.as_ptr(), id.as_ptr(), snapshot.as_ptr()) + }; + QualifiedName::from_owned_raw(result) + } + + /// Retrieve a type's id by its name + /// + /// * `name` - Type name + pub fn get_type_id(&self, name: QualifiedName) -> Option { + self.get_type_id_from_snapshot(name, &TypeArchiveSnapshotId::unset()) + } + + /// Retrieve a type's id by its name + /// + /// * `name` - Type name + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_id_from_snapshot( + &self, + name: QualifiedName, + snapshot: &TypeArchiveSnapshotId, + ) -> Option { + let raw_name = QualifiedName::into_raw(name); + let snapshot = snapshot.clone().to_cstr(); + let result = + unsafe { BNGetTypeArchiveTypeId(self.handle.as_ptr(), &raw_name, snapshot.as_ptr()) }; + QualifiedName::free_raw(raw_name); + (!result.is_null()).then(|| unsafe { BnString::into_string(result) }) + } + + /// Retrieve all stored types in the archive at a snapshot + pub fn get_types_and_ids(&self) -> Array { + self.get_types_and_ids_from_snapshot(&TypeArchiveSnapshotId::unset()) + } + + /// Retrieve all stored types in the archive at a snapshot + /// + /// * `snapshot` - Snapshot id to search for types + pub fn get_types_and_ids_from_snapshot( + &self, + snapshot: &TypeArchiveSnapshotId, + ) -> Array { + let mut count = 0; + let snapshot = snapshot.clone().to_cstr(); + let result = + unsafe { BNGetTypeArchiveTypes(self.handle.as_ptr(), snapshot.as_ptr(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get a list of all types' ids in the archive at a snapshot + pub fn get_type_ids(&self) -> Array { + self.get_type_ids_from_snapshot(&TypeArchiveSnapshotId::unset()) + } + + /// Get a list of all types' ids in the archive at a snapshot + /// + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_ids_from_snapshot(&self, snapshot: &TypeArchiveSnapshotId) -> Array { + let mut count = 0; + let snapshot = snapshot.clone().to_cstr(); + let result = + unsafe { BNGetTypeArchiveTypeIds(self.handle.as_ptr(), snapshot.as_ptr(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get a list of all types' names in the archive at a snapshot + pub fn get_type_names(&self) -> Array { + self.get_type_names_from_snapshot(&TypeArchiveSnapshotId::unset()) + } + + /// Get a list of all types' names in the archive at a snapshot + /// + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_names_from_snapshot( + &self, + snapshot: &TypeArchiveSnapshotId, + ) -> Array { + let mut count = 0; + let snapshot = snapshot.clone().to_cstr(); + let result = unsafe { + BNGetTypeArchiveTypeNames(self.handle.as_ptr(), snapshot.as_ptr(), &mut count) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get a list of all types' names and ids in the archive at the latest snapshot + pub fn get_type_names_and_ids(&self) -> (Array, Array) { + self.get_type_names_and_ids_from_snapshot(&TypeArchiveSnapshotId::unset()) + } + + /// Get a list of all types' names and ids in the archive at a specific snapshot + /// + /// * `snapshot` - Snapshot id to search for types + pub fn get_type_names_and_ids_from_snapshot( + &self, + snapshot: &TypeArchiveSnapshotId, + ) -> (Array, Array) { + let mut count = 0; + let snapshot = snapshot.clone().to_cstr(); + let mut names = std::ptr::null_mut(); + let mut ids = std::ptr::null_mut(); + let result = unsafe { + BNGetTypeArchiveTypeNamesAndIds( + self.handle.as_ptr(), + snapshot.as_ptr(), + &mut names, + &mut ids, + &mut count, + ) + }; + assert!(result); + (unsafe { Array::new(names, count, ()) }, unsafe { + Array::new(ids, count, ()) + }) + } + + /// Get all types a given type references directly + /// + /// * `id` - Source type id + pub fn get_outgoing_direct_references(&self, id: &str) -> Array { + self.get_outgoing_direct_references_from_snapshot(id, &TypeArchiveSnapshotId::unset()) + } + + /// Get all types a given type references directly + /// + /// * `id` - Source type id + /// * `snapshot` - Snapshot id to search for types + pub fn get_outgoing_direct_references_from_snapshot( + &self, + id: &str, + snapshot: &TypeArchiveSnapshotId, + ) -> Array { + let id = id.to_cstr(); + let snapshot = snapshot.clone().to_cstr(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveOutgoingDirectTypeReferences( + self.handle.as_ptr(), + id.as_ptr(), + snapshot.as_ptr(), + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get all types a given type references, and any types that the referenced types reference + /// + /// * `id` - Source type id + pub fn get_outgoing_recursive_references(&self, id: &str) -> Array { + self.get_outgoing_recursive_references_from_snapshot(id, &TypeArchiveSnapshotId::unset()) + } + + /// Get all types a given type references, and any types that the referenced types reference + /// + /// * `id` - Source type id + /// * `snapshot` - Snapshot id to search for types + pub fn get_outgoing_recursive_references_from_snapshot( + &self, + id: &str, + snapshot: &TypeArchiveSnapshotId, + ) -> Array { + let id = id.to_cstr(); + let snapshot = snapshot.clone().to_cstr(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveOutgoingRecursiveTypeReferences( + self.handle.as_ptr(), + id.as_ptr(), + snapshot.as_ptr(), + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get all types that reference a given type + /// + /// * `id` - Target type id + pub fn get_incoming_direct_references(&self, id: &str) -> Array { + self.get_incoming_direct_references_with_snapshot(id, &TypeArchiveSnapshotId::unset()) + } + + /// Get all types that reference a given type + /// + /// * `id` - Target type id + /// * `snapshot` - Snapshot id to search for types + pub fn get_incoming_direct_references_with_snapshot( + &self, + id: &str, + snapshot: &TypeArchiveSnapshotId, + ) -> Array { + let id = id.to_cstr(); + let snapshot = snapshot.clone().to_cstr(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveIncomingDirectTypeReferences( + self.handle.as_ptr(), + id.as_ptr(), + snapshot.as_ptr(), + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Get all types that reference a given type, and all types that reference them, recursively + /// + /// * `id` - Target type id + pub fn get_incoming_recursive_references(&self, id: &str) -> Array { + self.get_incoming_recursive_references_with_snapshot(id, &TypeArchiveSnapshotId::unset()) + } + + /// Get all types that reference a given type, and all types that reference them, recursively + /// + /// * `id` - Target type id + /// * `snapshot` - Snapshot id to search for types, or empty string to search the latest snapshot + pub fn get_incoming_recursive_references_with_snapshot( + &self, + id: &str, + snapshot: &TypeArchiveSnapshotId, + ) -> Array { + let id = id.to_cstr(); + let snapshot = snapshot.clone().to_cstr(); + let mut count = 0; + let result = unsafe { + BNGetTypeArchiveIncomingRecursiveTypeReferences( + self.handle.as_ptr(), + id.as_ptr(), + snapshot.as_ptr(), + &mut count, + ) + }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Look up a metadata entry in the archive + pub fn query_metadata(&self, key: &str) -> Option> { + let key = key.to_cstr(); + let result = unsafe { BNTypeArchiveQueryMetadata(self.handle.as_ptr(), key.as_ptr()) }; + (!result.is_null()).then(|| unsafe { Metadata::ref_from_raw(result) }) + } + + /// Store a key/value pair in the archive's metadata storage + /// + /// * `key` - key value to associate the Metadata object with + /// * `md` - object to store. + pub fn store_metadata(&self, key: &str, md: &Metadata) { + let key = key.to_cstr(); + let result = + unsafe { BNTypeArchiveStoreMetadata(self.handle.as_ptr(), key.as_ptr(), md.handle) }; + assert!(result); + } + + /// Delete a given metadata entry in the archive from the `key` + pub fn remove_metadata(&self, key: &str) -> bool { + let key = key.to_cstr(); + unsafe { BNTypeArchiveRemoveMetadata(self.handle.as_ptr(), key.as_ptr()) } + } + + /// Turn a given `snapshot` id into a data stream + pub fn serialize_snapshot(&self, snapshot: &TypeArchiveSnapshotId) -> DataBuffer { + let snapshot = snapshot.clone().to_cstr(); + let result = + unsafe { BNTypeArchiveSerializeSnapshot(self.handle.as_ptr(), snapshot.as_ptr()) }; + assert!(!result.is_null()); + DataBuffer::from_raw(result) + } + + /// Take a serialized snapshot `data` stream and create a new snapshot from it + pub fn deserialize_snapshot(&self, data: &DataBuffer) -> TypeArchiveSnapshotId { + let result = + unsafe { BNTypeArchiveDeserializeSnapshot(self.handle.as_ptr(), data.as_raw()) }; + assert!(!result.is_null()); + let id = unsafe { BnString::into_string(result) }; + TypeArchiveSnapshotId(id) + } + + /// Register a notification listener + pub fn register_notification_callback( + &self, + callback: T, + ) -> TypeArchiveCallbackHandle { + // SAFETY free on [TypeArchiveCallbackHandle::Drop] + let callback = Box::leak(Box::new(callback)); + let mut notification = BNTypeArchiveNotification { + context: callback as *mut T as *mut c_void, + typeAdded: Some(cb_type_added::), + typeUpdated: Some(cb_type_updated::), + typeRenamed: Some(cb_type_renamed::), + typeDeleted: Some(cb_type_deleted::), + }; + unsafe { BNRegisterTypeArchiveNotification(self.handle.as_ptr(), &mut notification) } + TypeArchiveCallbackHandle { + callback, + type_archive: self.to_owned(), + } + } + + // NOTE NotificationClosure is left private, there is no need for the user + // to know or use it. + #[allow(private_interfaces)] + pub fn register_notification_closure( + &self, + type_added: A, + type_updated: U, + type_renamed: R, + type_deleted: D, + ) -> TypeArchiveCallbackHandle> + where + A: FnMut(&TypeArchive, &str, &Type), + U: FnMut(&TypeArchive, &str, &Type, &Type), + R: FnMut(&TypeArchive, &str, &QualifiedName, &QualifiedName), + D: FnMut(&TypeArchive, &str, &Type), + { + self.register_notification_callback(NotificationClosure { + fun_type_added: type_added, + fun_type_updated: type_updated, + fun_type_renamed: type_renamed, + fun_type_deleted: type_deleted, + }) + } + + /// Close a type archive, disconnecting it from any active views and closing + /// any open file handles + pub fn close(&self) { + unsafe { BNCloseTypeArchive(self.handle.as_ptr()) } + } + + /// Determine if `file` is a Type Archive + pub fn is_type_archive(file: &Path) -> bool { + let file = file.to_cstr(); + unsafe { BNIsTypeArchive(file.as_ptr()) } + } + + ///// Get the TypeContainer interface for this Type Archive, presenting types + ///// at the current snapshot in the archive. + pub fn type_container(&self) -> TypeContainer { + let result = unsafe { BNGetTypeArchiveTypeContainer(self.handle.as_ptr()) }; + unsafe { TypeContainer::from_raw(NonNull::new(result).unwrap()) } + } + + /// Do some function in a transaction making a new snapshot whose id is passed to func. If func throws, + /// the transaction will be rolled back and the snapshot will not be created. + /// + /// * `func` - Function to call + /// * `parents` - Parent snapshot ids + /// + /// Returns Created snapshot id + pub fn new_snapshot_transaction( + &self, + mut function: F, + parents: &[TypeArchiveSnapshotId], + ) -> TypeArchiveSnapshotId + where + F: FnMut(&TypeArchiveSnapshotId) -> bool, + { + unsafe extern "C" fn cb_callback bool>( + ctxt: *mut c_void, + id: *const c_char, + ) -> bool { + let fun: &mut F = &mut *(ctxt as *mut F); + let id_str = raw_to_string(id).unwrap(); + fun(&TypeArchiveSnapshotId(id_str)) + } + + let parents_cstr: Vec<_> = parents.iter().map(|p| p.clone().to_cstr()).collect(); + let parents_raw: Vec<_> = parents_cstr.iter().map(|p| p.as_ptr()).collect(); + let result = unsafe { + BNTypeArchiveNewSnapshotTransaction( + self.handle.as_ptr(), + Some(cb_callback::), + &mut function as *mut F as *mut c_void, + parents_raw.as_ptr(), + parents.len(), + ) + }; + assert!(!result.is_null()); + let id_str = unsafe { BnString::into_string(result) }; + TypeArchiveSnapshotId(id_str) + } + + /// Merge two snapshots in the archive to produce a new snapshot + /// + /// * `base_snapshot` - Common ancestor of snapshots + /// * `first_snapshot` - First snapshot to merge + /// * `second_snapshot` - Second snapshot to merge + /// * `merge_conflicts` - List of all conflicting types, id <-> target snapshot + /// * `progress` - Function to call for progress updates + /// + /// Returns Snapshot id, if merge was successful, otherwise the List of + /// conflicting type ids + pub fn merge_snapshots( + &self, + base_snapshot: &TypeArchiveSnapshotId, + first_snapshot: &TypeArchiveSnapshotId, + second_snapshot: &TypeArchiveSnapshotId, + merge_conflicts: M, + ) -> Result> + where + M: IntoIterator, + { + self.merge_snapshots_with_progress( + base_snapshot, + first_snapshot, + second_snapshot, + merge_conflicts, + NoProgressCallback, + ) + } + + /// Merge two snapshots in the archive to produce a new snapshot + /// + /// * `base_snapshot` - Common ancestor of snapshots + /// * `first_snapshot` - First snapshot to merge + /// * `second_snapshot` - Second snapshot to merge + /// * `merge_conflicts` - List of all conflicting types, id <-> target snapshot + /// * `progress` - Function to call for progress updates + /// + /// Returns Snapshot id, if merge was successful, otherwise the List of + /// conflicting type ids + pub fn merge_snapshots_with_progress( + &self, + base_snapshot: &TypeArchiveSnapshotId, + first_snapshot: &TypeArchiveSnapshotId, + second_snapshot: &TypeArchiveSnapshotId, + merge_conflicts: M, + mut progress: PC, + ) -> Result> + where + M: IntoIterator, + PC: ProgressCallback, + { + let base_snapshot = base_snapshot.0.as_str().to_cstr(); + let first_snapshot = first_snapshot.0.as_str().to_cstr(); + let second_snapshot = second_snapshot.0.as_str().to_cstr(); + let (merge_keys, merge_values): (Vec, Vec) = merge_conflicts + .into_iter() + .map(|(k, v)| (BnString::new(k), BnString::new(v))) + .unzip(); + // SAFETY BnString and `*const c_char` are transparent + let merge_keys_raw = merge_keys.as_ptr() as *const *const c_char; + let merge_values_raw = merge_values.as_ptr() as *const *const c_char; + + let mut conflicts_errors = std::ptr::null_mut(); + let mut conflicts_errors_count = 0; + + let mut result = std::ptr::null_mut(); + + let success = unsafe { + BNTypeArchiveMergeSnapshots( + self.handle.as_ptr(), + base_snapshot.as_ptr(), + first_snapshot.as_ptr(), + second_snapshot.as_ptr(), + merge_keys_raw, + merge_values_raw, + merge_keys.len(), + &mut conflicts_errors, + &mut conflicts_errors_count, + &mut result, + Some(PC::cb_progress_callback), + &mut progress as *mut PC as *mut c_void, + ) + }; + + if success { + assert!(!result.is_null()); + Ok(unsafe { BnString::from_raw(result) }) + } else { + assert!(!conflicts_errors.is_null()); + Err(unsafe { Array::new(conflicts_errors, conflicts_errors_count, ()) }) + } + } +} + +impl ToOwned for TypeArchive { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for TypeArchive { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: NonNull::new(BNNewTypeArchiveReference(handle.handle.as_ptr())).unwrap(), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeTypeArchiveReference(handle.handle.as_ptr()); + } +} + +impl PartialEq for TypeArchive { + fn eq(&self, other: &Self) -> bool { + self.id() == other.id() + } +} +impl Eq for TypeArchive {} + +impl Hash for TypeArchive { + fn hash(&self, state: &mut H) { + self.id().hash(state); + } +} + +impl Debug for TypeArchive { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TypeArchive") + .field("id", &self.id()) + .field("path", &self.path()) + .field("current_snapshot_id", &self.current_snapshot_id()) + .field("platform", &self.platform()) + .finish() + } +} + +impl CoreArrayProvider for TypeArchive { + type Raw = *mut BNTypeArchive; + type Context = (); + type Wrapped<'a> = Guard<'a, TypeArchive>; +} + +unsafe impl CoreArrayProviderInner for TypeArchive { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeTypeArchiveList(raw, count) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + let raw_ptr = NonNull::new(*raw).unwrap(); + Guard::new(Self::from_raw(raw_ptr), context) + } +} + +pub struct TypeArchiveCallbackHandle { + callback: *mut T, + type_archive: Ref, +} + +impl Drop for TypeArchiveCallbackHandle { + fn drop(&mut self) { + let mut notification = BNTypeArchiveNotification { + context: self.callback as *mut c_void, + typeAdded: Some(cb_type_added::), + typeUpdated: Some(cb_type_updated::), + typeRenamed: Some(cb_type_renamed::), + typeDeleted: Some(cb_type_deleted::), + }; + // unregister the notification callback + unsafe { + BNUnregisterTypeArchiveNotification( + self.type_archive.handle.as_ptr(), + &mut notification, + ) + } + // free the context created at [TypeArchive::register_notification_callback] + drop(unsafe { Box::from_raw(self.callback) }); + } +} + +pub trait TypeArchiveNotificationCallback { + /// Called when a type is added to the archive + /// + /// * `archive` - Source Type archive + /// * `id` - Id of type added + /// * `definition` - Definition of type + fn type_added(&mut self, _archive: &TypeArchive, _id: &str, _definition: &Type) {} + + /// Called when a type in the archive is updated to a new definition + /// + /// * `archive` - Source Type archive + /// * `id` - Id of type + /// * `old_definition` - Previous definition + /// * `new_definition` - Current definition + fn type_updated( + &mut self, + _archive: &TypeArchive, + _id: &str, + _old_definition: &Type, + _new_definition: &Type, + ) { + } + + /// Called when a type in the archive is renamed + /// + /// * `archive` - Source Type archive + /// * `id` - Type id + /// * `old_name` - Previous name + /// * `new_name` - Current name + fn type_renamed( + &mut self, + _archive: &TypeArchive, + _id: &str, + _old_name: &QualifiedName, + _new_name: &QualifiedName, + ) { + } + + /// Called when a type in the archive is deleted from the archive + /// + /// * `archive` - Source Type archive + /// * `id` - Id of type deleted + /// * `definition` - Definition of type deleted + fn type_deleted(&mut self, _archive: &TypeArchive, _id: &str, _definition: &Type) {} +} + +struct NotificationClosure +where + A: FnMut(&TypeArchive, &str, &Type), + U: FnMut(&TypeArchive, &str, &Type, &Type), + R: FnMut(&TypeArchive, &str, &QualifiedName, &QualifiedName), + D: FnMut(&TypeArchive, &str, &Type), +{ + fun_type_added: A, + fun_type_updated: U, + fun_type_renamed: R, + fun_type_deleted: D, +} + +impl TypeArchiveNotificationCallback for NotificationClosure +where + A: FnMut(&TypeArchive, &str, &Type), + U: FnMut(&TypeArchive, &str, &Type, &Type), + R: FnMut(&TypeArchive, &str, &QualifiedName, &QualifiedName), + D: FnMut(&TypeArchive, &str, &Type), +{ + fn type_added(&mut self, archive: &TypeArchive, id: &str, definition: &Type) { + (self.fun_type_added)(archive, id, definition) + } + + fn type_updated( + &mut self, + archive: &TypeArchive, + id: &str, + old_definition: &Type, + new_definition: &Type, + ) { + (self.fun_type_updated)(archive, id, old_definition, new_definition) + } + + fn type_renamed( + &mut self, + archive: &TypeArchive, + id: &str, + old_name: &QualifiedName, + new_name: &QualifiedName, + ) { + (self.fun_type_renamed)(archive, id, old_name, new_name) + } + + fn type_deleted(&mut self, archive: &TypeArchive, id: &str, definition: &Type) { + (self.fun_type_deleted)(archive, id, definition) + } +} + +unsafe extern "C" fn cb_type_added( + ctxt: *mut ::std::os::raw::c_void, + archive: *mut BNTypeArchive, + id: *const ::std::os::raw::c_char, + definition: *mut BNType, +) { + let ctxt: &mut T = &mut *(ctxt as *mut T); + // `archive` is owned by the caller. + let archive = unsafe { TypeArchive::from_raw(NonNull::new(archive).unwrap()) }; + ctxt.type_added( + &archive, + unsafe { CStr::from_ptr(id).to_string_lossy().as_ref() }, + &Type { handle: definition }, + ) +} +unsafe extern "C" fn cb_type_updated( + ctxt: *mut ::std::os::raw::c_void, + archive: *mut BNTypeArchive, + id: *const ::std::os::raw::c_char, + old_definition: *mut BNType, + new_definition: *mut BNType, +) { + let ctxt: &mut T = &mut *(ctxt as *mut T); + // `archive` is owned by the caller. + let archive = unsafe { TypeArchive::from_raw(NonNull::new(archive).unwrap()) }; + ctxt.type_updated( + &archive, + unsafe { CStr::from_ptr(id).to_string_lossy().as_ref() }, + &Type { + handle: old_definition, + }, + &Type { + handle: new_definition, + }, + ) +} +unsafe extern "C" fn cb_type_renamed( + ctxt: *mut ::std::os::raw::c_void, + archive: *mut BNTypeArchive, + id: *const ::std::os::raw::c_char, + old_name: *const BNQualifiedName, + new_name: *const BNQualifiedName, +) { + let ctxt: &mut T = &mut *(ctxt as *mut T); + // `old_name` is freed by the caller + let old_name = QualifiedName::from_raw(&*old_name); + // `new_name` is freed by the caller + let new_name = QualifiedName::from_raw(&*new_name); + // `archive` is owned by the caller. + let archive = unsafe { TypeArchive::from_raw(NonNull::new(archive).unwrap()) }; + ctxt.type_renamed( + &archive, + unsafe { CStr::from_ptr(id).to_string_lossy().as_ref() }, + &old_name, + &new_name, + ) +} +unsafe extern "C" fn cb_type_deleted( + ctxt: *mut ::std::os::raw::c_void, + archive: *mut BNTypeArchive, + id: *const ::std::os::raw::c_char, + definition: *mut BNType, +) { + let ctxt: &mut T = &mut *(ctxt as *mut T); + // `archive` is owned by the caller. + let archive = unsafe { TypeArchive::from_raw(NonNull::new(archive).unwrap()) }; + ctxt.type_deleted( + &archive, + unsafe { CStr::from_ptr(id).to_string_lossy().as_ref() }, + &Type { handle: definition }, + ) +} + +#[repr(transparent)] +pub struct TypeArchiveMergeConflict { + handle: NonNull, +} + +impl TypeArchiveMergeConflict { + pub(crate) unsafe fn from_raw(handle: NonNull) -> Self { + Self { handle } + } + + #[allow(unused)] + pub(crate) unsafe fn ref_from_raw(handle: NonNull) -> Ref { + Ref::new(Self { handle }) + } + + pub fn get_type_archive(&self) -> Option> { + let value = unsafe { BNTypeArchiveMergeConflictGetTypeArchive(self.handle.as_ptr()) }; + NonNull::new(value).map(|handle| unsafe { TypeArchive::ref_from_raw(handle) }) + } + + pub fn type_id(&self) -> String { + let value = unsafe { BNTypeArchiveMergeConflictGetTypeId(self.handle.as_ptr()) }; + assert!(!value.is_null()); + unsafe { BnString::into_string(value) } + } + + pub fn base_snapshot_id(&self) -> TypeArchiveSnapshotId { + let value = unsafe { BNTypeArchiveMergeConflictGetBaseSnapshotId(self.handle.as_ptr()) }; + assert!(!value.is_null()); + let id = unsafe { BnString::into_string(value) }; + TypeArchiveSnapshotId(id) + } + + pub fn first_snapshot_id(&self) -> TypeArchiveSnapshotId { + let value = unsafe { BNTypeArchiveMergeConflictGetFirstSnapshotId(self.handle.as_ptr()) }; + assert!(!value.is_null()); + let id = unsafe { BnString::into_string(value) }; + TypeArchiveSnapshotId(id) + } + + pub fn second_snapshot_id(&self) -> TypeArchiveSnapshotId { + let value = unsafe { BNTypeArchiveMergeConflictGetSecondSnapshotId(self.handle.as_ptr()) }; + assert!(!value.is_null()); + let id = unsafe { BnString::into_string(value) }; + TypeArchiveSnapshotId(id) + } + + /// Call this when you've resolved the conflict to save the result. + pub fn success(&self, result: &str) -> bool { + let result = result.to_cstr(); + unsafe { BNTypeArchiveMergeConflictSuccess(self.handle.as_ptr(), result.as_ptr()) } + } +} + +impl Debug for TypeArchiveMergeConflict { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TypeArchiveMergeConflict") + .field("type_id", &self.type_id()) + .field("base_snapshot_id", &self.base_snapshot_id()) + .field("first_snapshot_id", &self.first_snapshot_id()) + .field("second_snapshot_id", &self.second_snapshot_id()) + .finish() + } +} + +impl ToOwned for TypeArchiveMergeConflict { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for TypeArchiveMergeConflict { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: NonNull::new(BNNewTypeArchiveMergeConflictReference( + handle.handle.as_ptr(), + )) + .unwrap(), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeTypeArchiveMergeConflict(handle.handle.as_ptr()); + } +} + +impl CoreArrayProvider for TypeArchiveMergeConflict { + type Raw = *mut BNTypeArchiveMergeConflict; + type Context = (); + type Wrapped<'a> = Guard<'a, Self>; +} + +unsafe impl CoreArrayProviderInner for TypeArchiveMergeConflict { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeTypeArchiveMergeConflictList(raw, count) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + let raw_ptr = NonNull::new(*raw).unwrap(); + Guard::new(Self::from_raw(raw_ptr), context) + } +} diff --git a/rust/src/types/container.rs b/rust/src/types/container.rs new file mode 100644 index 00000000..5096ac78 --- /dev/null +++ b/rust/src/types/container.rs @@ -0,0 +1,409 @@ +// TODO: Add these! +// The `TypeContainer` class should not generally be instantiated directly. Instances +// can be retrieved from the following properties and methods in the API: +// * [BinaryView::type_container] +// * [BinaryView::auto_type_container] +// * [BinaryView::user_type_container] +// * [Platform::type_container] +// * [TypeLibrary::type_container] +// * [DebugInfo::get_type_container] + +use crate::platform::Platform; +use crate::progress::{NoProgressCallback, ProgressCallback}; +use crate::rc::{Array, Ref}; +use crate::string::{raw_to_string, BnString, IntoCStr}; +use crate::types::{QualifiedName, QualifiedNameAndType, Type, TypeParserError, TypeParserResult}; +use binaryninjacore_sys::*; +use std::collections::HashMap; +use std::ffi::{c_char, c_void}; +use std::fmt::{Debug, Formatter}; +use std::ptr::NonNull; + +pub type TypeContainerType = BNTypeContainerType; + +/// A `TypeContainer` is a generic interface to access various Binary Ninja models +/// that contain types. Types are stored with both a unique id and a unique name. +#[repr(transparent)] +pub struct TypeContainer { + pub handle: NonNull, +} + +impl TypeContainer { + pub(crate) unsafe fn from_raw(handle: NonNull) -> Self { + // NOTE: There does not seem to be any shared ref counting for type containers, it seems if the + // NOTE: binary view is freed the type container will be freed and cause this to become invalid + // NOTE: but this is how the C++ and Python bindings operate so i guess its fine? + // TODO: I really dont get how some of the usage of the TypeContainer doesnt free the underlying container. + // TODO: So for now we always duplicate the type container + let cloned_ptr = NonNull::new(BNDuplicateTypeContainer(handle.as_ptr())); + Self { + handle: cloned_ptr.unwrap(), + } + } + + /// Get an empty type container that contains no types (immutable) + pub fn empty() -> TypeContainer { + let result = unsafe { BNGetEmptyTypeContainer() }; + unsafe { Self::from_raw(NonNull::new(result).unwrap()) } + } + + /// Get an id string for the Type Container. This will be unique within a given + /// analysis session, but may not be globally unique. + pub fn id(&self) -> String { + let result = unsafe { BNTypeContainerGetId(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::into_string(result) } + } + + /// Get a user-friendly name for the Type Container. + pub fn name(&self) -> String { + let result = unsafe { BNTypeContainerGetName(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::into_string(result) } + } + + /// Get the type of underlying model the Type Container is accessing. + pub fn container_type(&self) -> TypeContainerType { + unsafe { BNTypeContainerGetType(self.handle.as_ptr()) } + } + + /// If the Type Container supports mutable operations (add, rename, delete) + pub fn is_mutable(&self) -> bool { + unsafe { BNTypeContainerIsMutable(self.handle.as_ptr()) } + } + + /// Get the Platform object associated with this Type Container. All Type Containers + /// have exactly one associated Platform (as opposed to, e.g. Type Libraries). + pub fn platform(&self) -> Ref { + let result = unsafe { BNTypeContainerGetPlatform(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { Platform::ref_from_raw(result) } + } + + /// Add or update types to a Type Container. If the Type Container already contains + /// a type with the same name as a type being added, the existing type will be + /// replaced with the definition given to this function, and references will be + /// updated in the source model. + pub fn add_types(&self, types: I) -> bool + where + I: IntoIterator, + T: Into, + { + self.add_types_with_progress(types, NoProgressCallback) + } + + pub fn add_types_with_progress(&self, types: I, mut progress: P) -> bool + where + I: IntoIterator, + T: Into, + P: ProgressCallback, + { + // TODO: I dislike how this iter unzip looks like... but its how to avoid allocating again... + let (raw_names, mut raw_types): (Vec, Vec<_>) = types + .into_iter() + .map(|t| { + let t = t.into(); + // Leaked to be freed after the call to core. + ( + QualifiedName::into_raw(t.name), + unsafe { Ref::into_raw(t.ty) }.handle, + ) + }) + .unzip(); + + let mut result_names = std::ptr::null_mut(); + let mut result_ids = std::ptr::null_mut(); + let mut result_count = 0; + + let success = unsafe { + BNTypeContainerAddTypes( + self.handle.as_ptr(), + raw_names.as_ptr(), + raw_types.as_mut_ptr(), + raw_types.len(), + Some(P::cb_progress_callback), + &mut progress as *mut P as *mut c_void, + &mut result_names, + &mut result_ids, + &mut result_count, + ) + }; + + for name in raw_names { + QualifiedName::free_raw(name); + } + for ty in raw_types { + let _ = unsafe { Type::ref_from_raw(ty) }; + } + success + } + + /// Rename a type in the Type Container. All references to this type will be updated + /// (by id) to use the new name. + /// + /// Returns true if the type was renamed. + pub fn rename_type>(&self, name: T, type_id: &str) -> bool { + let type_id = type_id.to_cstr(); + let raw_name = QualifiedName::into_raw(name.into()); + let success = + unsafe { BNTypeContainerRenameType(self.handle.as_ptr(), type_id.as_ptr(), &raw_name) }; + QualifiedName::free_raw(raw_name); + success + } + + /// Delete a type in the Type Container. Behavior of references to this type is + /// not specified and you may end up with broken references if any still exist. + /// + /// Returns true if the type was deleted. + pub fn delete_type(&self, type_id: &str) -> bool { + let type_id = type_id.to_cstr(); + unsafe { BNTypeContainerDeleteType(self.handle.as_ptr(), type_id.as_ptr()) } + } + + /// Get the unique id of the type in the Type Container with the given name. + /// + /// If no type with that name exists, returns None. + pub fn type_id>(&self, name: T) -> Option { + let mut result = std::ptr::null_mut(); + let raw_name = QualifiedName::into_raw(name.into()); + let success = + unsafe { BNTypeContainerGetTypeId(self.handle.as_ptr(), &raw_name, &mut result) }; + QualifiedName::free_raw(raw_name); + success.then(|| unsafe { BnString::into_string(result) }) + } + + /// Get the unique name of the type in the Type Container with the given id. + /// + /// If no type with that id exists, returns None. + pub fn type_name(&self, type_id: &str) -> Option { + let type_id = type_id.to_cstr(); + let mut result = BNQualifiedName::default(); + let success = unsafe { + BNTypeContainerGetTypeName(self.handle.as_ptr(), type_id.as_ptr(), &mut result) + }; + success.then(|| QualifiedName::from_owned_raw(result)) + } + + /// Get the definition of the type in the Type Container with the given id. + /// + /// If no type with that id exists, returns None. + pub fn type_by_id(&self, type_id: &str) -> Option> { + let type_id = type_id.to_cstr(); + let mut result = std::ptr::null_mut(); + let success = unsafe { + BNTypeContainerGetTypeById(self.handle.as_ptr(), type_id.as_ptr(), &mut result) + }; + success.then(|| unsafe { Type::ref_from_raw(result) }) + } + + /// Get the definition of the type in the Type Container with the given name. + /// + /// If no type with that name exists, returns None. + pub fn type_by_name>(&self, name: T) -> Option> { + let mut result = std::ptr::null_mut(); + let raw_name = QualifiedName::into_raw(name.into()); + let success = + unsafe { BNTypeContainerGetTypeByName(self.handle.as_ptr(), &raw_name, &mut result) }; + QualifiedName::free_raw(raw_name); + success.then(|| unsafe { Type::ref_from_raw(result) }) + } + + /// Get a mapping of all types in a Type Container. + pub fn types(&self) -> Option)>> { + let mut type_ids = std::ptr::null_mut(); + let mut type_names = std::ptr::null_mut(); + let mut type_types = std::ptr::null_mut(); + let mut type_count = 0; + let success = unsafe { + BNTypeContainerGetTypes( + self.handle.as_ptr(), + &mut type_ids, + &mut type_names, + &mut type_types, + &mut type_count, + ) + }; + success.then(|| unsafe { + let raw_ids = std::slice::from_raw_parts(type_ids, type_count); + let raw_names = std::slice::from_raw_parts(type_names, type_count); + let raw_types = std::slice::from_raw_parts(type_types, type_count); + let mut map = HashMap::new(); + for (idx, raw_id) in raw_ids.iter().enumerate() { + let id = raw_to_string(*raw_id).expect("Valid string"); + // Take the qualified name as a ref as the name should not be freed. + let name = QualifiedName::from_raw(&raw_names[idx]); + // Take the type as an owned ref, as the returned type was not already incremented. + let ty = Type::from_raw(raw_types[idx]).to_owned(); + map.insert(id, (name, ty)); + } + BNFreeStringList(type_ids, type_count); + BNFreeTypeNameList(type_names, type_count); + BNFreeTypeList(type_types, type_count); + map + }) + } + + /// Get all type ids in a Type Container. + pub fn type_ids(&self) -> Option> { + let mut type_ids = std::ptr::null_mut(); + let mut type_count = 0; + let success = unsafe { + BNTypeContainerGetTypeIds(self.handle.as_ptr(), &mut type_ids, &mut type_count) + }; + success.then(|| unsafe { Array::new(type_ids, type_count, ()) }) + } + + /// Get all type names in a Type Container. + pub fn type_names(&self) -> Option> { + let mut type_ids = std::ptr::null_mut(); + let mut type_count = 0; + let success = unsafe { + BNTypeContainerGetTypeNames(self.handle.as_ptr(), &mut type_ids, &mut type_count) + }; + success.then(|| unsafe { Array::new(type_ids, type_count, ()) }) + } + + /// Get a mapping of all type ids and type names in a Type Container. + pub fn type_names_and_ids(&self) -> Option<(Array, Array)> { + let mut type_ids = std::ptr::null_mut(); + let mut type_names = std::ptr::null_mut(); + let mut type_count = 0; + let success = unsafe { + BNTypeContainerGetTypeNamesAndIds( + self.handle.as_ptr(), + &mut type_ids, + &mut type_names, + &mut type_count, + ) + }; + success.then(|| unsafe { + let ids = Array::new(type_ids, type_count, ()); + let names = Array::new(type_names, type_count, ()); + (ids, names) + }) + } + + /// Parse a single type and name from a string containing their definition, with + /// knowledge of the types in the Type Container. + /// + /// * `source` - Source code to parse + /// * `import_dependencies` - If Type Library / Type Archive types should be imported during parsing + pub fn parse_type_string( + &self, + source: &str, + import_dependencies: bool, + ) -> Result> { + let source = source.to_cstr(); + let mut result = BNQualifiedNameAndType::default(); + let mut errors = std::ptr::null_mut(); + let mut error_count = 0; + let success = unsafe { + BNTypeContainerParseTypeString( + self.handle.as_ptr(), + source.as_ptr(), + import_dependencies, + &mut result, + &mut errors, + &mut error_count, + ) + }; + if success { + Ok(QualifiedNameAndType::from_owned_raw(result)) + } else { + assert!(!errors.is_null()); + Err(unsafe { Array::new(errors, error_count, ()) }) + } + } + + /// Parse an entire block of source into types, variables, and functions, with + /// knowledge of the types in the Type Container. + /// + /// * `source` - Source code to parse + /// * `file_name` - Name of the file containing the source (optional: exists on disk) + /// * `options` - String arguments to pass as options, e.g. command line arguments + /// * `include_dirs` - List of directories to include in the header search path + /// * `auto_type_source` - Source of types if used for automatically generated types + /// * `import_dependencies` - If Type Library / Type Archive types should be imported during parsing + pub fn parse_types_from_source( + &self, + source: &str, + filename: &str, + options: O, + include_directories: I, + auto_type_source: &str, + import_dependencies: bool, + ) -> Result> + where + O: IntoIterator, + I: IntoIterator, + { + let source = source.to_cstr(); + let filename = filename.to_cstr(); + let options: Vec<_> = options.into_iter().map(|o| o.to_cstr()).collect(); + let options_raw: Vec<*const c_char> = options.iter().map(|o| o.as_ptr()).collect(); + let include_directories: Vec<_> = include_directories + .into_iter() + .map(|d| d.to_cstr()) + .collect(); + let include_directories_raw: Vec<*const c_char> = + include_directories.iter().map(|d| d.as_ptr()).collect(); + let auto_type_source = auto_type_source.to_cstr(); + let mut raw_result = BNTypeParserResult::default(); + let mut errors = std::ptr::null_mut(); + let mut error_count = 0; + let success = unsafe { + BNTypeContainerParseTypesFromSource( + self.handle.as_ptr(), + source.as_ptr(), + filename.as_ptr(), + options_raw.as_ptr(), + options_raw.len(), + include_directories_raw.as_ptr(), + include_directories_raw.len(), + auto_type_source.as_ptr(), + import_dependencies, + &mut raw_result, + &mut errors, + &mut error_count, + ) + }; + if success { + let result = TypeParserResult::from_raw(&raw_result); + // NOTE: This is safe because the core allocated the TypeParserResult + TypeParserResult::free_raw(raw_result); + Ok(result) + } else { + assert!(!errors.is_null()); + Err(unsafe { Array::new(errors, error_count, ()) }) + } + } +} + +impl Debug for TypeContainer { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TypeContainer") + .field("id", &self.id()) + .field("name", &self.name()) + .field("container_type", &self.container_type()) + .field("is_mutable", &self.is_mutable()) + .field("type_names", &self.type_names().unwrap().to_vec()) + .finish() + } +} + +impl Drop for TypeContainer { + fn drop(&mut self) { + unsafe { BNFreeTypeContainer(self.handle.as_ptr()) } + } +} + +impl Clone for TypeContainer { + fn clone(&self) -> Self { + unsafe { + let cloned_ptr = NonNull::new(BNDuplicateTypeContainer(self.handle.as_ptr())); + Self { + handle: cloned_ptr.unwrap(), + } + } + } +} diff --git a/rust/src/types/enumeration.rs b/rust/src/types/enumeration.rs new file mode 100644 index 00000000..70c266e1 --- /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/types/library.rs b/rust/src/types/library.rs new file mode 100644 index 00000000..89f5e48f --- /dev/null +++ b/rust/src/types/library.rs @@ -0,0 +1,365 @@ +use binaryninjacore_sys::*; +use std::fmt::{Debug, Formatter}; + +use crate::rc::{Guard, RefCountable}; +use crate::{ + architecture::CoreArchitecture, + metadata::Metadata, + platform::Platform, + rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref}, + string::{BnString, IntoCStr}, + types::{QualifiedName, QualifiedNameAndType, Type}, +}; +use std::path::Path; +use std::ptr::NonNull; + +#[repr(transparent)] +pub struct TypeLibrary { + handle: NonNull, +} + +impl TypeLibrary { + pub(crate) unsafe fn from_raw(handle: NonNull) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: NonNull) -> Ref { + Ref::new(Self { handle }) + } + + #[allow(clippy::mut_from_ref)] + pub(crate) unsafe fn as_raw(&self) -> &mut BNTypeLibrary { + &mut *self.handle.as_ptr() + } + + pub fn new_duplicated(&self) -> Ref { + unsafe { Self::ref_from_raw(NonNull::new(BNDuplicateTypeLibrary(self.as_raw())).unwrap()) } + } + + /// Creates an empty type library object with a random GUID and the provided name. + pub fn new(arch: CoreArchitecture, name: &str) -> Ref { + let name = name.to_cstr(); + let new_lib = unsafe { BNNewTypeLibrary(arch.handle, name.as_ptr()) }; + unsafe { TypeLibrary::ref_from_raw(NonNull::new(new_lib).unwrap()) } + } + + pub fn all(arch: CoreArchitecture) -> Array { + let mut count = 0; + let result = unsafe { BNGetArchitectureTypeLibraries(arch.handle, &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Decompresses a type library file to a file on disk. + pub fn decompress_to_file(path: &Path, output_path: &Path) -> bool { + let path = path.to_cstr(); + let output = output_path.to_cstr(); + unsafe { BNTypeLibraryDecompressToFile(path.as_ptr(), output.as_ptr()) } + } + + /// Loads a finalized type library instance from file + pub fn load_from_file(path: &Path) -> Option> { + let path = path.to_cstr(); + let handle = unsafe { BNLoadTypeLibraryFromFile(path.as_ptr()) }; + NonNull::new(handle).map(|h| unsafe { TypeLibrary::ref_from_raw(h) }) + } + + /// Saves a finalized type library instance to file + pub fn write_to_file(&self, path: &Path) -> bool { + let path = path.to_cstr(); + unsafe { BNWriteTypeLibraryToFile(self.as_raw(), path.as_ptr()) } + } + + /// Looks up the first type library found with a matching name. Keep in mind that names are not + /// necessarily unique. + /// + /// NOTE: If the type library architecture's associated platform has not been initialized, this will + /// return `None`. To make sure that the platform has been initialized, one should instead get the type + /// libraries through [`Platform::get_type_libraries_by_name`]. + pub fn from_name(arch: CoreArchitecture, name: &str) -> Option> { + let name = name.to_cstr(); + let handle = unsafe { BNLookupTypeLibraryByName(arch.handle, name.as_ptr()) }; + NonNull::new(handle).map(|h| unsafe { TypeLibrary::ref_from_raw(h) }) + } + + /// Attempts to grab a type library associated with the provided Architecture and GUID pair. + /// + /// NOTE: If the associated platform for the architecture has not been initialized, + /// this will return `None`. Avoid calling this outside of a view context. + pub fn from_guid(arch: CoreArchitecture, guid: &str) -> Option> { + let guid = guid.to_cstr(); + let handle = unsafe { BNLookupTypeLibraryByGuid(arch.handle, guid.as_ptr()) }; + NonNull::new(handle).map(|h| unsafe { TypeLibrary::ref_from_raw(h) }) + } + + /// The Architecture this type library is associated with + pub fn arch(&self) -> CoreArchitecture { + let arch = unsafe { BNGetTypeLibraryArchitecture(self.as_raw()) }; + assert!(!arch.is_null()); + unsafe { CoreArchitecture::from_raw(arch) } + } + + /// The primary name associated with this type library + pub fn name(&self) -> String { + let result = unsafe { BNGetTypeLibraryName(self.as_raw()) }; + assert!(!result.is_null()); + unsafe { BnString::into_string(result) } + } + + /// Sets the name of a type library instance that has not been finalized + pub fn set_name(&self, value: &str) { + let value = value.to_cstr(); + unsafe { BNSetTypeLibraryName(self.as_raw(), value.as_ptr()) } + } + + /// The `dependency_name` of a library is the name used to record dependencies across + /// type libraries. This allows, for example, a library with the name "musl_libc" to have + /// dependencies on it recorded as "libc_generic", allowing a type library to be used across + /// multiple platforms where each has a specific libc that also provides the name "libc_generic" + /// as an `alternate_name`. + pub fn dependency_name(&self) -> String { + let result = unsafe { BNGetTypeLibraryDependencyName(self.as_raw()) }; + assert!(!result.is_null()); + unsafe { BnString::into_string(result) } + } + + /// Sets the dependency name of a type library instance that has not been finalized + pub fn set_dependency_name(&self, value: &str) { + let value = value.to_cstr(); + unsafe { BNSetTypeLibraryDependencyName(self.as_raw(), value.as_ptr()) } + } + + /// Returns the GUID associated with the type library + pub fn guid(&self) -> String { + let result = unsafe { BNGetTypeLibraryGuid(self.as_raw()) }; + assert!(!result.is_null()); + unsafe { BnString::into_string(result) } + } + + /// Sets the GUID of a type library instance that has not been finalized + pub fn set_guid(&self, value: &str) { + let value = value.to_cstr(); + unsafe { BNSetTypeLibraryGuid(self.as_raw(), value.as_ptr()) } + } + + /// A list of extra names that will be considered a match by [Platform::get_type_libraries_by_name] + pub fn alternate_names(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetTypeLibraryAlternateNames(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Adds an extra name to this type library used during library lookups and dependency resolution + pub fn add_alternate_name(&self, value: &str) { + let value = value.to_cstr(); + unsafe { BNAddTypeLibraryAlternateName(self.as_raw(), value.as_ptr()) } + } + + /// Returns a list of all platform names that this type library will register with during platform + /// type registration. + /// + /// This returns strings, not Platform objects, as type libraries can be distributed with support for + /// Platforms that may not be present. + pub fn platform_names(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetTypeLibraryPlatforms(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// Associate a platform with a type library instance that has not been finalized. + /// + /// This will cause the library to be searchable by [Platform::get_type_libraries_by_name] + /// when loaded. + /// + /// This does not have side affects until finalization of the type library. + pub fn add_platform(&self, plat: &Platform) { + unsafe { BNAddTypeLibraryPlatform(self.as_raw(), plat.handle) } + } + + /// Clears the list of platforms associated with a type library instance that has not been finalized + pub fn clear_platforms(&self) { + unsafe { BNClearTypeLibraryPlatforms(self.as_raw()) } + } + + /// Flags a newly created type library instance as finalized and makes it available for Platform and Architecture + /// type library searches + pub fn finalize(&self) -> bool { + unsafe { BNFinalizeTypeLibrary(self.as_raw()) } + } + + /// Retrieves a metadata associated with the given key stored in the type library + pub fn query_metadata(&self, key: &str) -> Option> { + let key = key.to_cstr(); + let result = unsafe { BNTypeLibraryQueryMetadata(self.as_raw(), key.as_ptr()) }; + (!result.is_null()).then(|| unsafe { Metadata::ref_from_raw(result) }) + } + + /// Stores an object for the given key in the current type library. Objects stored using + /// `store_metadata` can be retrieved from any reference to the library. Objects stored are not arbitrary python + /// objects! The values stored must be able to be held in a Metadata object. See [Metadata] + /// for more information. Python objects could obviously be serialized using pickle but this intentionally + /// a task left to the user since there is the potential security issues. + /// + /// This is primarily intended as a way to store Platform specific information relevant to BinaryView implementations; + /// for example the PE BinaryViewType uses type library metadata to retrieve ordinal information, when available. + /// + /// * `key` - key value to associate the Metadata object with + /// * `md` - object to store. + pub fn store_metadata(&self, key: &str, md: &Metadata) { + let key = key.to_cstr(); + unsafe { BNTypeLibraryStoreMetadata(self.as_raw(), key.as_ptr(), md.handle) } + } + + /// Removes the metadata associated with key from the current type library. + pub fn remove_metadata(&self, key: &str) { + let key = key.to_cstr(); + unsafe { BNTypeLibraryRemoveMetadata(self.as_raw(), key.as_ptr()) } + } + + /// Retrieves the metadata associated with the current type library. + pub fn metadata(&self) -> Ref { + let md_handle = unsafe { BNTypeLibraryGetMetadata(self.as_raw()) }; + assert!(!md_handle.is_null()); + unsafe { Metadata::ref_from_raw(md_handle) } + } + + // TODO: implement TypeContainer + // /// Type Container for all TYPES within the Type Library. Objects are not included. + // /// The Type Container's Platform will be the first platform associated with the Type Library. + // pub fn type_container(&self) -> TypeContainer { + // let result = unsafe{ BNGetTypeLibraryTypeContainer(self.as_raw())}; + // unsafe{TypeContainer::from_raw(NonNull::new(result).unwrap())} + // } + + /// Directly inserts a named object into the type library's object store. + /// This is not done recursively, so care should be taken that types referring to other types + /// through NamedTypeReferences are already appropriately prepared. + /// + /// To add types and objects from an existing BinaryView, it is recommended to use + /// `export_object_to_library `, which will automatically pull in + /// all referenced types and record additional dependencies as needed. + pub fn add_named_object(&self, name: QualifiedName, type_: &Type) { + let mut raw_name = QualifiedName::into_raw(name); + unsafe { BNAddTypeLibraryNamedObject(self.as_raw(), &mut raw_name, type_.handle) } + QualifiedName::free_raw(raw_name); + } + + /// Directly inserts a named object into the type library's object store. + /// This is not done recursively, so care should be taken that types referring to other types + /// through NamedTypeReferences are already appropriately prepared. + /// + /// To add types and objects from an existing BinaryView, it is recommended to use + /// `export_type_to_library `, which will automatically pull in + /// all referenced types and record additional dependencies as needed. + pub fn add_named_type(&self, name: QualifiedName, type_: &Type) { + let mut raw_name = QualifiedName::into_raw(name); + unsafe { BNAddTypeLibraryNamedType(self.as_raw(), &mut raw_name, type_.handle) } + QualifiedName::free_raw(raw_name); + } + + /// Manually flag NamedTypeReferences to the given QualifiedName as originating from another source + /// TypeLibrary with the given dependency name. + /// + ///
+ /// + /// Use this api with extreme caution. + /// + ///
+ pub fn add_type_source(&self, name: QualifiedName, source: &str) { + let source = source.to_cstr(); + let mut raw_name = QualifiedName::into_raw(name); + unsafe { BNAddTypeLibraryNamedTypeSource(self.as_raw(), &mut raw_name, source.as_ptr()) } + QualifiedName::free_raw(raw_name); + } + + /// Direct extracts a reference to a contained object -- when + /// attempting to extract types from a library into a BinaryView, consider using + /// `import_library_object ` instead. + pub fn get_named_object(&self, name: QualifiedName) -> Option> { + let mut raw_name = QualifiedName::into_raw(name); + let t = unsafe { BNGetTypeLibraryNamedObject(self.as_raw(), &mut raw_name) }; + QualifiedName::free_raw(raw_name); + (!t.is_null()).then(|| unsafe { Type::ref_from_raw(t) }) + } + + /// Direct extracts a reference to a contained type -- when + /// attempting to extract types from a library into a BinaryView, consider using + /// `import_library_type ` instead. + pub fn get_named_type(&self, name: QualifiedName) -> Option> { + let mut raw_name = QualifiedName::into_raw(name); + let t = unsafe { BNGetTypeLibraryNamedType(self.as_raw(), &mut raw_name) }; + QualifiedName::free_raw(raw_name); + (!t.is_null()).then(|| unsafe { Type::ref_from_raw(t) }) + } + + /// A dict containing all named objects (functions, exported variables) provided by a type library + pub fn named_objects(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetTypeLibraryNamedObjects(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + /// A dict containing all named types provided by a type library + pub fn named_types(&self) -> Array { + let mut count = 0; + let result = unsafe { BNGetTypeLibraryNamedTypes(self.as_raw(), &mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } +} + +impl Debug for TypeLibrary { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("TypeLibrary") + .field("name", &self.name()) + .field("dependency_name", &self.dependency_name()) + .field("arch", &self.arch()) + .field("guid", &self.guid()) + .field("alternate_names", &self.alternate_names().to_vec()) + .field("platform_names", &self.platform_names().to_vec()) + .field("metadata", &self.metadata()) + // These two are too verbose. + // .field("named_objects", &self.named_objects().to_vec()) + // .field("named_types", &self.named_types().to_vec()) + .finish() + } +} + +unsafe impl RefCountable for TypeLibrary { + unsafe fn inc_ref(handle: &Self) -> Ref { + Ref::new(Self { + handle: NonNull::new(BNNewTypeLibraryReference(handle.handle.as_ptr())).unwrap(), + }) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeTypeLibrary(handle.handle.as_ptr()); + } +} + +impl ToOwned for TypeLibrary { + type Owned = Ref; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +impl CoreArrayProvider for TypeLibrary { + type Raw = *mut BNTypeLibrary; + type Context = (); + type Wrapped<'a> = Guard<'a, Self>; +} + +unsafe impl CoreArrayProviderInner for TypeLibrary { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeTypeLibraryList(raw, count) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> { + Guard::new(Self::from_raw(NonNull::new(*raw).unwrap()), context) + } +} diff --git a/rust/src/types/parser.rs b/rust/src/types/parser.rs new file mode 100644 index 00000000..1ee07188 --- /dev/null +++ b/rust/src/types/parser.rs @@ -0,0 +1,687 @@ +#![allow(unused)] +use binaryninjacore_sys::*; +use std::ffi::{c_char, c_void}; +use std::fmt::Debug; +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::types::{QualifiedName, QualifiedNameAndType, Type, TypeContainer}; + +pub type TypeParserErrorSeverity = BNTypeParserErrorSeverity; +pub type TypeParserOption = BNTypeParserOption; + +/// Register a custom parser with the API +pub fn register_type_parser( + name: &str, + parser: T, +) -> (&'static mut T, CoreTypeParser) { + let parser = Box::leak(Box::new(parser)); + let mut callback = BNTypeParserCallbacks { + context: parser as *mut _ as *mut c_void, + getOptionText: Some(cb_get_option_text::), + preprocessSource: Some(cb_preprocess_source::), + parseTypesFromSource: Some(cb_parse_types_from_source::), + parseTypeString: Some(cb_parse_type_string::), + freeString: Some(cb_free_string), + freeResult: Some(cb_free_result), + freeErrorList: Some(cb_free_error_list), + }; + let name = name.to_cstr(); + let result = unsafe { BNRegisterTypeParser(name.as_ptr(), &mut callback) }; + let core = unsafe { CoreTypeParser::from_raw(NonNull::new(result).unwrap()) }; + (parser, core) +} + +#[repr(transparent)] +pub struct CoreTypeParser { + pub(crate) handle: NonNull, +} + +impl CoreTypeParser { + pub(crate) unsafe fn from_raw(handle: NonNull) -> Self { + Self { handle } + } + + pub fn parsers() -> Array { + let mut count = 0; + let result = unsafe { BNGetTypeParserList(&mut count) }; + unsafe { Array::new(result, count, ()) } + } + + pub fn parser_by_name(name: &str) -> Option { + let name_raw = name.to_cstr(); + let result = unsafe { BNGetTypeParserByName(name_raw.as_ptr()) }; + NonNull::new(result).map(|x| unsafe { Self::from_raw(x) }) + } + + pub fn name(&self) -> String { + let result = unsafe { BNGetTypeParserName(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::into_string(result) } + } +} + +impl TypeParser for CoreTypeParser { + fn get_option_text(&self, option: TypeParserOption, value: &str) -> Option { + let mut output = std::ptr::null_mut(); + let value_ptr = std::ptr::null_mut(); + let result = unsafe { + BNGetTypeParserOptionText(self.handle.as_ptr(), option, value_ptr, &mut output) + }; + result.then(|| { + assert!(!output.is_null()); + unsafe { BnString::into_string(value_ptr) } + }) + } + + fn preprocess_source( + &self, + source: &str, + file_name: &str, + platform: &Platform, + existing_types: &TypeContainer, + options: &[String], + include_dirs: &[String], + ) -> Result> { + let source_cstr = BnString::new(source); + let file_name_cstr = BnString::new(file_name); + let mut result = std::ptr::null_mut(); + let mut errors = std::ptr::null_mut(); + let mut error_count = 0; + let success = unsafe { + BNTypeParserPreprocessSource( + self.handle.as_ptr(), + source_cstr.as_ptr(), + file_name_cstr.as_ptr(), + platform.handle, + existing_types.handle.as_ptr(), + options.as_ptr() as *const *const c_char, + options.len(), + include_dirs.as_ptr() as *const *const c_char, + include_dirs.len(), + &mut result, + &mut errors, + &mut error_count, + ) + }; + if success { + assert!(!result.is_null()); + let bn_result = unsafe { BnString::into_string(result) }; + Ok(bn_result) + } else { + let errors: Array = unsafe { Array::new(errors, error_count, ()) }; + Err(errors.to_vec()) + } + } + + fn parse_types_from_source( + &self, + source: &str, + file_name: &str, + platform: &Platform, + existing_types: &TypeContainer, + options: &[String], + include_dirs: &[String], + auto_type_source: &str, + ) -> Result> { + let source_cstr = BnString::new(source); + let file_name_cstr = BnString::new(file_name); + let auto_type_source = BnString::new(auto_type_source); + let mut raw_result = BNTypeParserResult::default(); + let mut errors = std::ptr::null_mut(); + let mut error_count = 0; + let success = unsafe { + BNTypeParserParseTypesFromSource( + self.handle.as_ptr(), + source_cstr.as_ptr(), + file_name_cstr.as_ptr(), + platform.handle, + existing_types.handle.as_ptr(), + options.as_ptr() as *const *const c_char, + options.len(), + include_dirs.as_ptr() as *const *const c_char, + include_dirs.len(), + auto_type_source.as_ptr(), + &mut raw_result, + &mut errors, + &mut error_count, + ) + }; + if success { + let result = TypeParserResult::from_raw(&raw_result); + // NOTE: This is safe because the core allocated the TypeParserResult + TypeParserResult::free_raw(raw_result); + Ok(result) + } else { + let errors: Array = unsafe { Array::new(errors, error_count, ()) }; + Err(errors.to_vec()) + } + } + + fn parse_type_string( + &self, + source: &str, + platform: &Platform, + existing_types: &TypeContainer, + ) -> Result> { + let source_cstr = BnString::new(source); + let mut output = BNQualifiedNameAndType::default(); + let mut errors = std::ptr::null_mut(); + let mut error_count = 0; + let result = unsafe { + BNTypeParserParseTypeString( + self.handle.as_ptr(), + source_cstr.as_ptr(), + platform.handle, + existing_types.handle.as_ptr(), + &mut output, + &mut errors, + &mut error_count, + ) + }; + if result { + Ok(QualifiedNameAndType::from_owned_raw(output)) + } else { + let errors: Array = unsafe { Array::new(errors, error_count, ()) }; + Err(errors.to_vec()) + } + } +} + +impl Default for CoreTypeParser { + fn default() -> Self { + // TODO: This should return a ref + unsafe { Self::from_raw(NonNull::new(BNGetDefaultTypeParser()).unwrap()) } + } +} + +// TODO: Impl this on platform. +pub trait TypeParser { + /// Get the string representation of an option for passing to parse_type_*. + /// Returns a string representing the option if the parser supports it, + /// otherwise None + /// + /// * `option` - Option type + /// * `value` - Option value + fn get_option_text(&self, option: TypeParserOption, value: &str) -> Option; + + /// Preprocess a block of source, returning the source that would be parsed + /// + /// * `source` - Source code to process + /// * `file_name` - Name of the file containing the source (does not need to exist on disk) + /// * `platform` - Platform to assume the source is relevant to + /// * `existing_types` - Optional collection of all existing types to use for parsing context + /// * `options` - Optional string arguments to pass as options, e.g. command line arguments + /// * `include_dirs` - Optional list of directories to include in the header search path + fn preprocess_source( + &self, + source: &str, + file_name: &str, + platform: &Platform, + existing_types: &TypeContainer, + options: &[String], + include_dirs: &[String], + ) -> Result>; + + /// Parse an entire block of source into types, variables, and functions + /// + /// * `source` - Source code to parse + /// * `file_name` - Name of the file containing the source (optional: exists on disk) + /// * `platform` - Platform to assume the types are relevant to + /// * `existing_types` - Optional container of all existing types to use for parsing context + /// * `options` - Optional string arguments to pass as options, e.g. command line arguments + /// * `include_dirs` - Optional list of directories to include in the header search path + /// * `auto_type_source` - Optional source of types if used for automatically generated types + fn parse_types_from_source( + &self, + source: &str, + file_name: &str, + platform: &Platform, + existing_types: &TypeContainer, + options: &[String], + include_dirs: &[String], + auto_type_source: &str, + ) -> Result>; + + /// Parse a single type and name from a string containing their definition. + /// + /// * `source` - Source code to parse + /// * `platform` - Platform to assume the types are relevant to + /// * `existing_types` - Optional container of all existing types to use for parsing context + fn parse_type_string( + &self, + source: &str, + platform: &Platform, + existing_types: &TypeContainer, + ) -> Result>; +} + +impl CoreArrayProvider for CoreTypeParser { + type Raw = *mut BNTypeParser; + type Context = (); + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for CoreTypeParser { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeTypeParserList(raw) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + // TODO: Because handle is a NonNull we should prob make Self::Raw that as well... + let handle = NonNull::new(*raw).unwrap(); + CoreTypeParser::from_raw(handle) + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TypeParserError { + pub severity: TypeParserErrorSeverity, + pub message: String, + pub file_name: String, + pub line: u64, + pub column: u64, +} + +impl TypeParserError { + pub(crate) fn from_raw(value: &BNTypeParserError) -> Self { + Self { + severity: value.severity, + message: raw_to_string(value.message).unwrap(), + file_name: raw_to_string(value.fileName).unwrap(), + line: value.line, + column: value.column, + } + } + + pub(crate) fn from_owned_raw(value: BNTypeParserError) -> Self { + let owned = Self::from_raw(&value); + Self::free_raw(value); + owned + } + + pub(crate) fn into_raw(value: Self) -> BNTypeParserError { + BNTypeParserError { + severity: value.severity, + message: BnString::into_raw(BnString::new(value.message)), + fileName: BnString::into_raw(BnString::new(value.file_name)), + line: value.line, + column: value.column, + } + } + + pub(crate) fn free_raw(value: BNTypeParserError) { + unsafe { BnString::free_raw(value.message) }; + unsafe { BnString::free_raw(value.fileName) }; + } + + pub fn new( + severity: TypeParserErrorSeverity, + message: String, + file_name: String, + line: u64, + column: u64, + ) -> Self { + Self { + severity, + message, + file_name, + line, + column, + } + } +} + +impl CoreArrayProvider for TypeParserError { + type Raw = BNTypeParserError; + type Context = (); + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for TypeParserError { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + unsafe { BNFreeTypeParserErrors(raw, count) } + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::from_raw(raw) + } +} + +#[derive(Debug, Eq, PartialEq, Default)] +pub struct TypeParserResult { + pub types: Vec, + pub variables: Vec, + pub functions: Vec, +} + +impl TypeParserResult { + pub(crate) fn from_raw(value: &BNTypeParserResult) -> Self { + let raw_types = unsafe { std::slice::from_raw_parts(value.types, value.typeCount) }; + let types = raw_types.iter().map(ParsedType::from_raw).collect(); + let raw_variables = + unsafe { std::slice::from_raw_parts(value.variables, value.variableCount) }; + let variables = raw_variables.iter().map(ParsedType::from_raw).collect(); + let raw_functions = + unsafe { std::slice::from_raw_parts(value.functions, value.functionCount) }; + let functions = raw_functions.iter().map(ParsedType::from_raw).collect(); + TypeParserResult { + types, + variables, + functions, + } + } + + /// Return a rust allocated type parser result, free using [`Self::free_owned_raw`]. + /// + /// Under no circumstance should you call [`Self::free_raw`] on the returned result. + pub(crate) fn into_raw(value: Self) -> BNTypeParserResult { + let boxed_raw_types: Box<[BNParsedType]> = value + .types + .into_iter() + // NOTE: Freed with [`Self::free_owned_raw`]. + .map(ParsedType::into_raw) + .collect(); + let boxed_raw_variables: Box<[BNParsedType]> = value + .variables + .into_iter() + // NOTE: Freed with [`Self::free_owned_raw`]. + .map(ParsedType::into_raw) + .collect(); + let boxed_raw_functions: Box<[BNParsedType]> = value + .functions + .into_iter() + // NOTE: Freed with [`Self::free_owned_raw`]. + .map(ParsedType::into_raw) + .collect(); + BNTypeParserResult { + typeCount: boxed_raw_types.len(), + // NOTE: Freed with [`Self::free_owned_raw`]. + types: Box::leak(boxed_raw_types).as_mut_ptr(), + variableCount: boxed_raw_variables.len(), + // NOTE: Freed with [`Self::free_owned_raw`]. + variables: Box::leak(boxed_raw_variables).as_mut_ptr(), + functionCount: boxed_raw_functions.len(), + // NOTE: Freed with [`Self::free_owned_raw`]. + functions: Box::leak(boxed_raw_functions).as_mut_ptr(), + } + } + + pub(crate) fn free_raw(mut value: BNTypeParserResult) { + // SAFETY: `value` must be a properly initialized BNTypeParserResult. + // SAFETY: `value` must be core allocated. + unsafe { BNFreeTypeParserResult(&mut value) }; + } + + pub(crate) fn free_owned_raw(value: BNTypeParserResult) { + let raw_types = std::ptr::slice_from_raw_parts_mut(value.types, value.typeCount); + // Free the rust allocated types list + let boxed_types = unsafe { Box::from_raw(raw_types) }; + for parsed_type in boxed_types { + ParsedType::free_raw(parsed_type); + } + let raw_variables = + std::ptr::slice_from_raw_parts_mut(value.variables, value.variableCount); + // Free the rust allocated variables list + let boxed_variables = unsafe { Box::from_raw(raw_variables) }; + for parsed_type in boxed_variables { + ParsedType::free_raw(parsed_type); + } + let raw_functions = + std::ptr::slice_from_raw_parts_mut(value.functions, value.functionCount); + // Free the rust allocated functions list + let boxed_functions = unsafe { Box::from_raw(raw_functions) }; + for parsed_type in boxed_functions { + ParsedType::free_raw(parsed_type); + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct ParsedType { + pub name: QualifiedName, + pub ty: Ref, + pub user: bool, +} + +impl ParsedType { + pub(crate) fn from_raw(value: &BNParsedType) -> Self { + Self { + name: QualifiedName::from_raw(&value.name), + ty: unsafe { Type::from_raw(value.type_).to_owned() }, + user: value.isUser, + } + } + + pub(crate) fn from_owned_raw(value: BNParsedType) -> Self { + let owned = Self::from_raw(&value); + Self::free_raw(value); + owned + } + + pub(crate) fn into_raw(value: Self) -> BNParsedType { + BNParsedType { + name: QualifiedName::into_raw(value.name), + type_: unsafe { Ref::into_raw(value.ty) }.handle, + isUser: value.user, + } + } + + pub(crate) fn free_raw(value: BNParsedType) { + QualifiedName::free_raw(value.name); + let _ = unsafe { Type::ref_from_raw(value.type_) }; + } + + pub fn new(name: QualifiedName, ty: Ref, user: bool) -> Self { + Self { name, ty, user } + } +} + +impl CoreArrayProvider for ParsedType { + type Raw = BNParsedType; + type Context = (); + type Wrapped<'b> = Self; +} + +unsafe impl CoreArrayProviderInner for ParsedType { + unsafe fn free(_raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + // Expected to be freed with BNFreeTypeParserResult + // TODO ^ because of the above, we should not provide an array provider for this + } + + unsafe fn wrap_raw<'b>(raw: &'b Self::Raw, _context: &'b Self::Context) -> Self::Wrapped<'b> { + ParsedType::from_raw(raw) + } +} + +unsafe extern "C" fn cb_get_option_text( + ctxt: *mut ::std::os::raw::c_void, + option: BNTypeParserOption, + value: *const c_char, + result: *mut *mut c_char, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + if let Some(inner_result) = ctxt.get_option_text(option, &raw_to_string(value).unwrap()) { + let bn_inner_result = BnString::new(inner_result); + // NOTE: Dropped by `cb_free_string` + *result = BnString::into_raw(bn_inner_result); + true + } else { + *result = std::ptr::null_mut(); + false + } +} + +unsafe extern "C" fn cb_preprocess_source( + ctxt: *mut c_void, + source: *const c_char, + file_name: *const c_char, + platform: *mut BNPlatform, + existing_types: *mut BNTypeContainer, + options: *const *const c_char, + option_count: usize, + include_dirs: *const *const c_char, + include_dir_count: usize, + result: *mut *mut c_char, + errors: *mut *mut BNTypeParserError, + error_count: *mut usize, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let platform = Platform { handle: platform }; + let existing_types_ptr = NonNull::new(existing_types).unwrap(); + let existing_types = TypeContainer::from_raw(existing_types_ptr); + let options_raw = unsafe { std::slice::from_raw_parts(options, option_count) }; + let options: Vec<_> = options_raw + .iter() + .filter_map(|&r| raw_to_string(r)) + .collect(); + let includes_raw = unsafe { std::slice::from_raw_parts(include_dirs, include_dir_count) }; + let includes: Vec<_> = includes_raw + .iter() + .filter_map(|&r| raw_to_string(r)) + .collect(); + match ctxt.preprocess_source( + &raw_to_string(source).unwrap(), + &raw_to_string(file_name).unwrap(), + &platform, + &existing_types, + &options, + &includes, + ) { + Ok(inner_result) => { + let bn_inner_result = BnString::new(inner_result); + // NOTE: Dropped by `cb_free_string` + *result = BnString::into_raw(bn_inner_result); + *errors = std::ptr::null_mut(); + *error_count = 0; + true + } + Err(inner_errors) => { + *result = std::ptr::null_mut(); + *error_count = inner_errors.len(); + // NOTE: Leaking errors here, dropped by `cb_free_error_list`. + let inner_errors: Box<[_]> = inner_errors + .into_iter() + .map(TypeParserError::into_raw) + .collect(); + // NOTE: Dropped by `cb_free_error_list` + *errors = Box::leak(inner_errors).as_mut_ptr(); + false + } + } +} + +unsafe extern "C" fn cb_parse_types_from_source( + ctxt: *mut c_void, + source: *const c_char, + file_name: *const c_char, + platform: *mut BNPlatform, + existing_types: *mut BNTypeContainer, + options: *const *const c_char, + option_count: usize, + include_dirs: *const *const c_char, + include_dir_count: usize, + auto_type_source: *const c_char, + result: *mut BNTypeParserResult, + errors: *mut *mut BNTypeParserError, + error_count: *mut usize, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let platform = Platform { handle: platform }; + let existing_types_ptr = NonNull::new(existing_types).unwrap(); + let existing_types = TypeContainer::from_raw(existing_types_ptr); + let options_raw = unsafe { std::slice::from_raw_parts(options, option_count) }; + let options: Vec<_> = options_raw + .iter() + .filter_map(|&r| raw_to_string(r)) + .collect(); + let includes_raw = unsafe { std::slice::from_raw_parts(include_dirs, include_dir_count) }; + let includes: Vec<_> = includes_raw + .iter() + .filter_map(|&r| raw_to_string(r)) + .collect(); + match ctxt.parse_types_from_source( + &raw_to_string(source).unwrap(), + &raw_to_string(file_name).unwrap(), + &platform, + &existing_types, + &options, + &includes, + &raw_to_string(auto_type_source).unwrap(), + ) { + Ok(type_parser_result) => { + *result = TypeParserResult::into_raw(type_parser_result); + *errors = std::ptr::null_mut(); + *error_count = 0; + true + } + Err(inner_errors) => { + *error_count = inner_errors.len(); + let inner_errors: Box<[_]> = inner_errors + .into_iter() + .map(TypeParserError::into_raw) + .collect(); + *result = Default::default(); + // NOTE: Dropped by cb_free_error_list + *errors = Box::leak(inner_errors).as_mut_ptr(); + false + } + } +} + +unsafe extern "C" fn cb_parse_type_string( + ctxt: *mut c_void, + source: *const c_char, + platform: *mut BNPlatform, + existing_types: *mut BNTypeContainer, + result: *mut BNQualifiedNameAndType, + errors: *mut *mut BNTypeParserError, + error_count: *mut usize, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let platform = Platform { handle: platform }; + let existing_types_ptr = NonNull::new(existing_types).unwrap(); + let existing_types = TypeContainer::from_raw(existing_types_ptr); + match ctxt.parse_type_string(&raw_to_string(source).unwrap(), &platform, &existing_types) { + Ok(inner_result) => { + *result = QualifiedNameAndType::into_raw(inner_result); + *errors = std::ptr::null_mut(); + *error_count = 0; + true + } + Err(inner_errors) => { + *error_count = inner_errors.len(); + let inner_errors: Box<[_]> = inner_errors + .into_iter() + .map(TypeParserError::into_raw) + .collect(); + *result = Default::default(); + // NOTE: Dropped by cb_free_error_list + *errors = Box::leak(inner_errors).as_mut_ptr(); + false + } + } +} + +unsafe extern "C" fn cb_free_string(_ctxt: *mut c_void, string: *mut c_char) { + // SAFETY: The returned string is just BnString + BnString::free_raw(string); +} + +unsafe extern "C" fn cb_free_result(_ctxt: *mut c_void, result: *mut BNTypeParserResult) { + TypeParserResult::free_owned_raw(*result); +} + +unsafe extern "C" fn cb_free_error_list( + _ctxt: *mut c_void, + errors: *mut BNTypeParserError, + error_count: usize, +) { + let errors = std::ptr::slice_from_raw_parts_mut(errors, error_count); + let boxed_errors = Box::from_raw(errors); + for error in boxed_errors { + TypeParserError::free_raw(error); + } +} diff --git a/rust/src/types/printer.rs b/rust/src/types/printer.rs new file mode 100644 index 00000000..d168434e --- /dev/null +++ b/rust/src/types/printer.rs @@ -0,0 +1,976 @@ +#![allow(unused)] + +use crate::binary_view::BinaryView; +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::types::{NamedTypeReference, QualifiedName, QualifiedNameAndType, Type, TypeContainer}; +use binaryninjacore_sys::*; +use std::ffi::{c_char, c_int, c_void}; +use std::ptr::NonNull; + +pub type TokenEscapingType = BNTokenEscapingType; +pub type TypeDefinitionLineType = BNTypeDefinitionLineType; + +/// Register a custom parser with the API +pub fn register_type_printer( + name: &str, + parser: T, +) -> (&'static mut T, CoreTypePrinter) { + let parser = Box::leak(Box::new(parser)); + let mut callback = BNTypePrinterCallbacks { + context: parser as *mut _ as *mut c_void, + getTypeTokens: Some(cb_get_type_tokens::), + getTypeTokensBeforeName: Some(cb_get_type_tokens_before_name::), + getTypeTokensAfterName: Some(cb_get_type_tokens_after_name::), + getTypeString: Some(cb_get_type_string::), + getTypeStringBeforeName: Some(cb_get_type_string_before_name::), + getTypeStringAfterName: Some(cb_get_type_string_after_name::), + getTypeLines: Some(cb_get_type_lines::), + printAllTypes: Some(cb_print_all_types::), + freeTokens: Some(cb_free_tokens), + freeString: Some(cb_free_string), + freeLines: Some(cb_free_lines), + }; + let raw_name = name.to_cstr(); + let result = unsafe { BNRegisterTypePrinter(raw_name.as_ptr(), &mut callback) }; + let core = unsafe { CoreTypePrinter::from_raw(NonNull::new(result).unwrap()) }; + (parser, core) +} + +#[repr(transparent)] +pub struct CoreTypePrinter { + pub(crate) handle: NonNull, +} + +impl CoreTypePrinter { + pub(crate) unsafe fn from_raw(handle: NonNull) -> CoreTypePrinter { + Self { handle } + } + + pub fn printers() -> Array { + let mut count = 0; + let result = unsafe { BNGetTypePrinterList(&mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + pub fn printer_by_name(name: &str) -> Option { + let name_raw = name.to_cstr(); + let result = unsafe { BNGetTypePrinterByName(name_raw.as_ptr()) }; + NonNull::new(result).map(|x| unsafe { Self::from_raw(x) }) + } + + pub fn name(&self) -> String { + let result = unsafe { BNGetTypePrinterName(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::into_string(result) } + } + + pub fn get_type_tokens>( + &self, + type_: &Type, + platform: &Platform, + name: T, + base_confidence: u8, + escaping: TokenEscapingType, + ) -> Option> { + let mut result_count = 0; + let mut result = std::ptr::null_mut(); + let mut raw_name = QualifiedName::into_raw(name.into()); + let success = unsafe { + BNGetTypePrinterTypeTokens( + self.handle.as_ptr(), + type_.handle, + platform.handle, + &mut raw_name, + base_confidence, + escaping, + &mut result, + &mut result_count, + ) + }; + QualifiedName::free_raw(raw_name); + success.then(|| { + assert!(!result.is_null()); + unsafe { Array::new(result, result_count, ()) } + }) + } + + pub fn get_type_tokens_before_name( + &self, + type_: &Type, + platform: &Platform, + base_confidence: u8, + parent_type: &Type, + escaping: TokenEscapingType, + ) -> Option> { + let mut result_count = 0; + let mut result = std::ptr::null_mut(); + let success = unsafe { + BNGetTypePrinterTypeTokensBeforeName( + self.handle.as_ptr(), + type_.handle, + platform.handle, + base_confidence, + parent_type.handle, + escaping, + &mut result, + &mut result_count, + ) + }; + success.then(|| { + assert!(!result.is_null()); + unsafe { Array::new(result, result_count, ()) } + }) + } + + pub fn get_type_tokens_after_name( + &self, + type_: &Type, + platform: &Platform, + base_confidence: u8, + parent_type: &Type, + escaping: TokenEscapingType, + ) -> Option> { + let mut result_count = 0; + let mut result = std::ptr::null_mut(); + let success = unsafe { + BNGetTypePrinterTypeTokensAfterName( + self.handle.as_ptr(), + type_.handle, + platform.handle, + base_confidence, + parent_type.handle, + escaping, + &mut result, + &mut result_count, + ) + }; + success.then(|| { + assert!(!result.is_null()); + unsafe { Array::new(result, result_count, ()) } + }) + } + + pub fn get_type_string>( + &self, + type_: &Type, + platform: &Platform, + name: T, + escaping: TokenEscapingType, + ) -> Option { + let mut result = std::ptr::null_mut(); + let mut raw_name = QualifiedName::into_raw(name.into()); + let success = unsafe { + BNGetTypePrinterTypeString( + self.handle.as_ptr(), + type_.handle, + platform.handle, + &mut raw_name, + escaping, + &mut result, + ) + }; + QualifiedName::free_raw(raw_name); + success.then(|| unsafe { + assert!(!result.is_null()); + BnString::from_raw(result) + }) + } + + pub fn get_type_string_before_name( + &self, + type_: &Type, + platform: &Platform, + escaping: BNTokenEscapingType, + ) -> Option { + let mut result = std::ptr::null_mut(); + let success = unsafe { + BNGetTypePrinterTypeStringAfterName( + self.handle.as_ptr(), + type_.handle, + platform.handle, + escaping, + &mut result, + ) + }; + success.then(|| unsafe { + assert!(!result.is_null()); + BnString::from_raw(result) + }) + } + + pub fn get_type_string_after_name( + &self, + type_: &Type, + platform: &Platform, + escaping: TokenEscapingType, + ) -> Option { + let mut result = std::ptr::null_mut(); + let success = unsafe { + BNGetTypePrinterTypeStringBeforeName( + self.handle.as_ptr(), + type_.handle, + platform.handle, + escaping, + &mut result, + ) + }; + success.then(|| unsafe { + assert!(!result.is_null()); + BnString::from_raw(result) + }) + } + + pub fn get_type_lines>( + &self, + type_: &Type, + types: &TypeContainer, + name: T, + padding_cols: isize, + collapsed: bool, + escaping: TokenEscapingType, + ) -> Option> { + let mut result_count = 0; + let mut result = std::ptr::null_mut(); + let mut raw_name = QualifiedName::into_raw(name.into()); + let success = unsafe { + BNGetTypePrinterTypeLines( + self.handle.as_ptr(), + type_.handle, + types.handle.as_ptr(), + &mut raw_name, + padding_cols as c_int, + collapsed, + escaping, + &mut result, + &mut result_count, + ) + }; + QualifiedName::free_raw(raw_name); + success.then(|| { + assert!(!result.is_null()); + unsafe { Array::::new(result, result_count, ()) } + }) + } + + /// Print all types to a single big string, including headers, sections, etc + /// + /// * `types` - All types to print + /// * `data` - Binary View in which all the types are defined + /// * `padding_cols` - Maximum number of bytes represented by each padding line + /// * `escaping` - Style of escaping literals which may not be parsable + pub fn default_print_all_types( + &self, + types: T, + data: &BinaryView, + padding_cols: isize, + escaping: TokenEscapingType, + ) -> Option + where + T: Iterator, + I: Into, + { + let mut result = std::ptr::null_mut(); + let (mut raw_names, mut raw_types): (Vec, Vec<_>) = types + .map(|t| { + let t = t.into(); + // Leak both to the core and then free afterwards. + ( + QualifiedName::into_raw(t.name), + unsafe { Ref::into_raw(t.ty) }.handle, + ) + }) + .unzip(); + let success = unsafe { + BNTypePrinterDefaultPrintAllTypes( + self.handle.as_ptr(), + raw_names.as_mut_ptr(), + raw_types.as_mut_ptr(), + raw_types.len(), + data.handle, + padding_cols as c_int, + escaping, + &mut result, + ) + }; + for raw_name in raw_names { + QualifiedName::free_raw(raw_name); + } + for raw_type in raw_types { + let _ = unsafe { Type::ref_from_raw(raw_type) }; + } + success.then(|| unsafe { + assert!(!result.is_null()); + BnString::from_raw(result) + }) + } + + pub fn print_all_types( + &self, + types: T, + data: &BinaryView, + padding_cols: isize, + escaping: TokenEscapingType, + ) -> Option + where + T: IntoIterator, + I: Into, + { + let mut result = std::ptr::null_mut(); + // TODO: I dislike how this iter unzip looks like... but its how to avoid allocating again... + let (mut raw_names, mut raw_types): (Vec, Vec<_>) = types + .into_iter() + .map(|t| { + let t = t.into(); + // Leak both to the core and then free afterwards. + ( + QualifiedName::into_raw(t.name), + unsafe { Ref::into_raw(t.ty) }.handle, + ) + }) + .unzip(); + let success = unsafe { + BNTypePrinterPrintAllTypes( + self.handle.as_ptr(), + raw_names.as_mut_ptr(), + raw_types.as_mut_ptr(), + raw_types.len(), + data.handle, + padding_cols as c_int, + escaping, + &mut result, + ) + }; + for raw_name in raw_names { + QualifiedName::free_raw(raw_name); + } + for raw_type in raw_types { + let _ = unsafe { Type::ref_from_raw(raw_type) }; + } + success.then(|| unsafe { + assert!(!result.is_null()); + BnString::from_raw(result) + }) + } +} + +impl Default for CoreTypePrinter { + fn default() -> Self { + // TODO: Remove this entirely, there is no "default", its view specific lets not make this some defined behavior. + let default_settings = crate::settings::Settings::new(); + let name = default_settings.get_string("analysis.types.printerName"); + Self::printer_by_name(&name).unwrap() + } +} + +impl CoreArrayProvider for CoreTypePrinter { + type Raw = *mut BNTypePrinter; + type Context = (); + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for CoreTypePrinter { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeTypePrinterList(raw) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + // TODO: Because handle is a NonNull we should prob make Self::Raw that as well... + let handle = NonNull::new(*raw).unwrap(); + CoreTypePrinter::from_raw(handle) + } +} + +pub trait TypePrinter { + /// Generate a single-line text representation of a type, Returns a List + /// of text tokens representing the type. + /// + /// * `type_` - Type to print + /// * `platform` - Platform responsible for this type + /// * `name` - Name of the type + /// * `base_confidence` - Confidence to use for tokens created for this type + /// * `escaping` - Style of escaping literals which may not be parsable + fn get_type_tokens>( + &self, + type_: Ref, + platform: Option>, + name: T, + base_confidence: u8, + escaping: TokenEscapingType, + ) -> Option>; + + /// In a single-line text representation of a type, generate the tokens that + /// should be printed before the type's name. Returns a list of text tokens + /// representing the type + /// + /// * `type_` - Type to print + /// * `platform` - Platform responsible for this type + /// * `base_confidence` - Confidence to use for tokens created for this type + /// * `parent_type` - Type of the parent of this type, or None + /// * `escaping` - Style of escaping literals which may not be parsable + fn get_type_tokens_before_name( + &self, + type_: Ref, + platform: Option>, + base_confidence: u8, + parent_type: Option>, + escaping: TokenEscapingType, + ) -> Option>; + + /// In a single-line text representation of a type, generate the tokens + /// that should be printed after the type's name. Returns a list of text + /// tokens representing the type + /// + /// * `type_` - Type to print + /// * `platform` - Platform responsible for this type + /// * `base_confidence` - Confidence to use for tokens created for this type + /// * `parent_type` - Type of the parent of this type, or None + /// * `escaping` - Style of escaping literals which may not be parsable + fn get_type_tokens_after_name( + &self, + type_: Ref, + platform: Option>, + base_confidence: u8, + parent_type: Option>, + escaping: TokenEscapingType, + ) -> Option>; + + /// Generate a single-line text representation of a type. Returns a string + /// representing the type + /// + /// * `type_` - Type to print + /// * `platform` - Platform responsible for this type + /// * `name` - Name of the type + /// * `escaping` - Style of escaping literals which may not be parsable + fn get_type_string>( + &self, + type_: Ref, + platform: Option>, + name: T, + escaping: TokenEscapingType, + ) -> Option; + + /// In a single-line text representation of a type, generate the string that + /// should be printed before the type's name. Returns a string representing + /// the type + /// + /// * `type_` - Type to print + /// * `platform` - Platform responsible for this type + /// * `escaping` - Style of escaping literals which may not be parsable + fn get_type_string_before_name( + &self, + type_: Ref, + platform: Option>, + escaping: TokenEscapingType, + ) -> Option; + + /// In a single-line text representation of a type, generate the string that + /// should be printed after the type's name. Returns a string representing + /// the type + /// + /// * `type_` - Type to print + /// * `platform` - Platform responsible for this type + /// * `escaping` - Style of escaping literals which may not be parsable + fn get_type_string_after_name( + &self, + type_: Ref, + platform: Option>, + escaping: TokenEscapingType, + ) -> Option; + + /// Generate a multi-line representation of a type. Returns a list of type + /// definition lines + /// + /// * `type_` - Type to print + /// * `types` - Type Container containing the type and dependencies + /// * `name` - Name of the type + /// * `padding_cols` - Maximum number of bytes represented by each padding line + /// * `collapsed` - Whether to collapse structure/enum blocks + /// * `escaping` - Style of escaping literals which may not be parsable + fn get_type_lines>( + &self, + type_: Ref, + types: &TypeContainer, + name: T, + padding_cols: isize, + collapsed: bool, + escaping: TokenEscapingType, + ) -> Option>; + + /// Print all types to a single big string, including headers, sections, + /// etc. + /// + /// * `types` - All types to print + /// * `data` - Binary View in which all the types are defined + /// * `padding_cols` - Maximum number of bytes represented by each padding line + /// * `escaping` - Style of escaping literals which may not be parsable + fn print_all_types( + &self, + names: Vec, + types: Vec>, + data: Ref, + padding_cols: isize, + escaping: TokenEscapingType, + ) -> Option; +} + +// TODO: This needs an extreme amount of documentation... +#[derive(Clone)] +pub struct TypeDefinitionLine { + pub line_type: TypeDefinitionLineType, + pub tokens: Vec, + pub ty: Ref, + pub parent_type: Option>, + // TODO: Document what the root type is. + pub root_type: Option>, + pub root_type_name: Option, + // TODO: Document the base type, and why its a ntr instead of type + name like root type + pub base_type: Option>, + // TODO: These can also be optional? + pub base_offset: u64, + pub offset: u64, + pub field_index: usize, +} + +impl TypeDefinitionLine { + pub(crate) fn from_raw(value: &BNTypeDefinitionLine) -> Self { + Self { + line_type: value.lineType, + tokens: { + let raw_tokens = unsafe { std::slice::from_raw_parts(value.tokens, value.count) }; + raw_tokens + .iter() + .map(InstructionTextToken::from_raw) + .collect() + }, + ty: unsafe { Type::from_raw(value.type_).to_owned() }, + parent_type: match value.parentType.is_null() { + false => Some(unsafe { Type::from_raw(value.parentType).to_owned() }), + true => None, + }, + root_type: match value.rootType.is_null() { + false => Some(unsafe { Type::from_raw(value.rootType).to_owned() }), + true => None, + }, + root_type_name: match value.rootTypeName.is_null() { + false => Some(raw_to_string(value.rootTypeName).unwrap()), + true => None, + }, + base_type: match value.baseType.is_null() { + false => Some(unsafe { NamedTypeReference::from_raw(value.baseType).to_owned() }), + true => None, + }, + base_offset: value.baseOffset, + offset: value.offset, + field_index: value.fieldIndex, + } + } + + /// The raw value must have been allocated by rust. See [`Self::free_owned_raw`] for details. + pub(crate) fn from_owned_raw(value: BNTypeDefinitionLine) -> Self { + let owned = Self::from_raw(&value); + Self::free_owned_raw(value); + owned + } + + pub(crate) fn into_raw(value: Self) -> BNTypeDefinitionLine { + // NOTE: This is leaking [BNInstructionTextToken::text], [BNInstructionTextToken::typeNames]. + let tokens: Box<[BNInstructionTextToken]> = value + .tokens + .into_iter() + .map(InstructionTextToken::into_raw) + .collect(); + BNTypeDefinitionLine { + lineType: value.line_type, + count: tokens.len(), + // NOTE: This is leaking tokens. Must free with `cb_free_lines`. + tokens: Box::leak(tokens).as_mut_ptr(), + // NOTE: This is leaking a ref to ty. Must free with `cb_free_lines`. + type_: unsafe { Ref::into_raw(value.ty) }.handle, + // NOTE: This is leaking a ref to parent_type. Must free with `cb_free_lines`. + parentType: value + .parent_type + .map(|t| unsafe { Ref::into_raw(t) }.handle) + .unwrap_or(std::ptr::null_mut()), + // NOTE: This is leaking a ref to root_type. Must free with `cb_free_lines`. + rootType: value + .root_type + .map(|t| unsafe { Ref::into_raw(t) }.handle) + .unwrap_or(std::ptr::null_mut()), + // NOTE: This is leaking root_type_name. Must free with `cb_free_lines`. + rootTypeName: value + .root_type_name + .map(|s| BnString::into_raw(BnString::new(s))) + .unwrap_or(std::ptr::null_mut()), + // NOTE: This is leaking a ref to base_type. Must free with `cb_free_lines`. + baseType: value + .base_type + .map(|t| unsafe { Ref::into_raw(t) }.handle) + .unwrap_or(std::ptr::null_mut()), + baseOffset: value.base_offset, + offset: value.offset, + fieldIndex: value.field_index, + } + } + + /// This is unique from the typical `from_raw` as the allocation of InstructionTextToken requires it be from rust, hence the "owned" free. + pub(crate) fn free_owned_raw(raw: BNTypeDefinitionLine) { + if !raw.tokens.is_null() { + let tokens = std::ptr::slice_from_raw_parts_mut(raw.tokens, raw.count); + // SAFETY: raw.tokens must have been allocated by rust. + let boxed_tokens = unsafe { Box::from_raw(tokens) }; + for token in boxed_tokens { + InstructionTextToken::free_raw(token); + } + } + if !raw.type_.is_null() { + // SAFETY: raw.type_ must have been ref incremented in conjunction with this free + let _ = unsafe { Type::ref_from_raw(raw.type_) }; + } + if !raw.parentType.is_null() { + // SAFETY: raw.parentType must have been ref incremented in conjunction with this free + let _ = unsafe { Type::ref_from_raw(raw.parentType) }; + } + if !raw.rootType.is_null() { + // SAFETY: raw.rootType must have been ref incremented in conjunction with this free + let _ = unsafe { Type::ref_from_raw(raw.rootType) }; + } + if !raw.rootTypeName.is_null() { + // SAFETY: raw.rootTypeName must have been ref incremented in conjunction with this free + let _ = unsafe { BnString::from_raw(raw.rootTypeName) }; + } + if !raw.baseType.is_null() { + // SAFETY: raw.baseType must have been ref incremented in conjunction with this free + let _ = unsafe { NamedTypeReference::ref_from_raw(raw.baseType) }; + } + } +} + +impl CoreArrayProvider for TypeDefinitionLine { + type Raw = BNTypeDefinitionLine; + type Context = (); + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for TypeDefinitionLine { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + unsafe { BNFreeTypeDefinitionLineList(raw, count) }; + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::from_raw(raw) + } +} + +unsafe extern "C" fn cb_get_type_tokens( + ctxt: *mut ::std::os::raw::c_void, + type_: *mut BNType, + platform: *mut BNPlatform, + name: *mut BNQualifiedName, + base_confidence: u8, + escaping: BNTokenEscapingType, + result: *mut *mut BNInstructionTextToken, + result_count: *mut usize, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + // NOTE: The caller is responsible for freeing name. + let qualified_name = QualifiedName::from_raw(&*name); + let inner_result = ctxt.get_type_tokens( + unsafe { Type::ref_from_raw(type_) }, + match platform.is_null() { + false => Some(Platform::ref_from_raw(platform)), + true => None, + }, + qualified_name, + base_confidence, + escaping, + ); + if let Some(inner_result) = inner_result { + let raw_text_tokens: Box<[BNInstructionTextToken]> = inner_result + .into_iter() + .map(InstructionTextToken::into_raw) + .collect(); + *result_count = raw_text_tokens.len(); + // NOTE: Dropped by the cb_free_tokens + *result = Box::leak(raw_text_tokens).as_mut_ptr(); + true + } else { + *result = std::ptr::null_mut(); + *result_count = 0; + false + } +} + +unsafe extern "C" fn cb_get_type_tokens_before_name( + ctxt: *mut ::std::os::raw::c_void, + type_: *mut BNType, + platform: *mut BNPlatform, + base_confidence: u8, + parent_type: *mut BNType, + escaping: BNTokenEscapingType, + result: *mut *mut BNInstructionTextToken, + result_count: *mut usize, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let inner_result = ctxt.get_type_tokens_before_name( + Type::ref_from_raw(type_), + match platform.is_null() { + false => Some(Platform::ref_from_raw(platform)), + true => None, + }, + base_confidence, + match parent_type.is_null() { + false => Some(Type::ref_from_raw(parent_type)), + true => None, + }, + escaping, + ); + if let Some(inner_result) = inner_result { + let raw_text_tokens: Box<[BNInstructionTextToken]> = inner_result + .into_iter() + .map(InstructionTextToken::into_raw) + .collect(); + *result_count = raw_text_tokens.len(); + // NOTE: Dropped by the cb_free_tokens + *result = Box::leak(raw_text_tokens).as_mut_ptr(); + true + } else { + *result = std::ptr::null_mut(); + *result_count = 0; + false + } +} + +unsafe extern "C" fn cb_get_type_tokens_after_name( + ctxt: *mut ::std::os::raw::c_void, + type_: *mut BNType, + platform: *mut BNPlatform, + base_confidence: u8, + parent_type: *mut BNType, + escaping: BNTokenEscapingType, + result: *mut *mut BNInstructionTextToken, + result_count: *mut usize, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let inner_result = ctxt.get_type_tokens_after_name( + Type::ref_from_raw(type_), + match platform.is_null() { + false => Some(Platform::ref_from_raw(platform)), + true => None, + }, + base_confidence, + match parent_type.is_null() { + false => Some(Type::ref_from_raw(parent_type)), + true => None, + }, + escaping, + ); + if let Some(inner_result) = inner_result { + let raw_text_tokens: Box<[BNInstructionTextToken]> = inner_result + .into_iter() + .map(InstructionTextToken::into_raw) + .collect(); + *result_count = raw_text_tokens.len(); + // NOTE: Dropped by the cb_free_tokens + *result = Box::leak(raw_text_tokens).as_mut_ptr(); + true + } else { + *result = std::ptr::null_mut(); + *result_count = 0; + false + } +} + +unsafe extern "C" fn cb_get_type_string( + ctxt: *mut ::std::os::raw::c_void, + type_: *mut BNType, + platform: *mut BNPlatform, + name: *mut BNQualifiedName, + escaping: BNTokenEscapingType, + result: *mut *mut ::std::os::raw::c_char, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + // NOTE: The caller is responsible for freeing name. + let qualified_name = QualifiedName::from_raw(&*name); + let inner_result = ctxt.get_type_string( + Type::ref_from_raw(type_), + match platform.is_null() { + false => Some(Platform::ref_from_raw(platform)), + true => None, + }, + qualified_name, + escaping, + ); + if let Some(inner_result) = inner_result { + let raw_string = BnString::new(inner_result); + // NOTE: Dropped by `cb_free_string` + *result = BnString::into_raw(raw_string); + true + } else { + *result = std::ptr::null_mut(); + false + } +} + +unsafe extern "C" fn cb_get_type_string_before_name( + ctxt: *mut ::std::os::raw::c_void, + type_: *mut BNType, + platform: *mut BNPlatform, + escaping: BNTokenEscapingType, + result: *mut *mut ::std::os::raw::c_char, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let inner_result = ctxt.get_type_string_before_name( + Type::ref_from_raw(type_), + match platform.is_null() { + false => Some(Platform::ref_from_raw(platform)), + true => None, + }, + escaping, + ); + if let Some(inner_result) = inner_result { + // NOTE: Dropped by `cb_free_string` + let raw_string = BnString::new(inner_result); + *result = BnString::into_raw(raw_string); + true + } else { + *result = std::ptr::null_mut(); + false + } +} + +unsafe extern "C" fn cb_get_type_string_after_name( + ctxt: *mut ::std::os::raw::c_void, + type_: *mut BNType, + platform: *mut BNPlatform, + escaping: BNTokenEscapingType, + result: *mut *mut ::std::os::raw::c_char, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let inner_result = ctxt.get_type_string_after_name( + Type::ref_from_raw(type_), + match platform.is_null() { + false => Some(Platform::ref_from_raw(platform)), + true => None, + }, + escaping, + ); + if let Some(inner_result) = inner_result { + let raw_string = BnString::new(inner_result); + // NOTE: Dropped by `cb_free_string` + *result = BnString::into_raw(raw_string); + true + } else { + *result = std::ptr::null_mut(); + false + } +} + +unsafe extern "C" fn cb_get_type_lines( + ctxt: *mut ::std::os::raw::c_void, + type_: *mut BNType, + types: *mut BNTypeContainer, + name: *mut BNQualifiedName, + padding_cols: ::std::os::raw::c_int, + collapsed: bool, + escaping: BNTokenEscapingType, + result: *mut *mut BNTypeDefinitionLine, + result_count: *mut usize, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + // NOTE: The caller is responsible for freeing name. + let qualified_name = QualifiedName::from_raw(&*name); + let types_ptr = NonNull::new(types).unwrap(); + let types = TypeContainer::from_raw(types_ptr); + let inner_result = ctxt.get_type_lines( + Type::ref_from_raw(type_), + &types, + qualified_name, + padding_cols as isize, + collapsed, + escaping, + ); + if let Some(inner_result) = inner_result { + let boxed_raw_lines: Box<[_]> = inner_result + .into_iter() + .map(TypeDefinitionLine::into_raw) + .collect(); + *result_count = boxed_raw_lines.len(); + // NOTE: Dropped by `cb_free_lines` + *result = Box::leak(boxed_raw_lines).as_mut_ptr(); + true + } else { + *result = std::ptr::null_mut(); + *result_count = 0; + false + } +} + +unsafe extern "C" fn cb_print_all_types( + ctxt: *mut ::std::os::raw::c_void, + names: *mut BNQualifiedName, + types: *mut *mut BNType, + type_count: usize, + data: *mut BNBinaryView, + padding_cols: ::std::os::raw::c_int, + escaping: BNTokenEscapingType, + result: *mut *mut ::std::os::raw::c_char, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let raw_names = std::slice::from_raw_parts(names, type_count); + // NOTE: The caller is responsible for freeing raw_names. + let names: Vec<_> = raw_names.iter().map(QualifiedName::from_raw).collect(); + let raw_types = std::slice::from_raw_parts(types, type_count); + // NOTE: The caller is responsible for freeing raw_types. + let types: Vec<_> = raw_types.iter().map(|&t| Type::ref_from_raw(t)).collect(); + let inner_result = ctxt.print_all_types( + names, + types, + BinaryView::ref_from_raw(data), + padding_cols as isize, + escaping, + ); + if let Some(inner_result) = inner_result { + let raw_string = BnString::new(inner_result); + // NOTE: Dropped by `cb_free_string` + *result = BnString::into_raw(raw_string); + true + } else { + *result = std::ptr::null_mut(); + false + } +} + +unsafe extern "C" fn cb_free_string(_ctxt: *mut c_void, string: *mut c_char) { + // SAFETY: The returned string is just BnString + BnString::free_raw(string); +} + +unsafe extern "C" fn cb_free_tokens( + _ctxt: *mut ::std::os::raw::c_void, + tokens: *mut BNInstructionTextToken, + count: usize, +) { + let tokens = std::ptr::slice_from_raw_parts_mut(tokens, count); + // SAFETY: tokens must have been allocated by rust. + let boxed_tokens = Box::from_raw(tokens); + for token in boxed_tokens { + InstructionTextToken::free_raw(token); + } +} + +unsafe extern "C" fn cb_free_lines( + _ctxt: *mut ::std::os::raw::c_void, + lines: *mut BNTypeDefinitionLine, + count: usize, +) { + let lines = std::ptr::slice_from_raw_parts_mut(lines, count); + // SAFETY: lines must have been allocated by rust. + let boxes_lines = Box::from_raw(lines); + for line in boxes_lines { + TypeDefinitionLine::free_owned_raw(line); + } +} diff --git a/rust/src/types/structure.rs b/rust/src/types/structure.rs new file mode 100644 index 00000000..7de466a4 --- /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 } + } +} -- cgit v1.3.1