diff options
Diffstat (limited to 'rust/src/types')
| -rw-r--r-- | rust/src/types/archive.rs | 1174 | ||||
| -rw-r--r-- | rust/src/types/container.rs | 409 | ||||
| -rw-r--r-- | rust/src/types/enumeration.rs | 201 | ||||
| -rw-r--r-- | rust/src/types/library.rs | 365 | ||||
| -rw-r--r-- | rust/src/types/parser.rs | 687 | ||||
| -rw-r--r-- | rust/src/types/printer.rs | 976 | ||||
| -rw-r--r-- | rust/src/types/structure.rs | 696 |
7 files changed, 4508 insertions, 0 deletions
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<BNTypeArchive>, +} + +impl TypeArchive { + pub(crate) unsafe fn from_raw(handle: NonNull<BNTypeArchive>) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: NonNull<BNTypeArchive>) -> Ref<Self> { + Ref::new(Self { handle }) + } + + /// Open the Type Archive at the given path, if it exists. + pub fn open(path: impl AsRef<Path>) -> Option<Ref<TypeArchive>> { + 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<Path>, platform: &Platform) -> Option<Ref<TypeArchive>> { + 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<Path>, + id: &TypeArchiveId, + platform: &Platform, + ) -> Option<Ref<TypeArchive>> { + 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<Ref<TypeArchive>> { + 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<PathBuf> { + 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<Platform> { + 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<TypeArchiveSnapshotId> { + 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<Array<BnString>> { + 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<Array<BnString>> { + 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<QualifiedNameAndType>) -> 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<Ref<Type>> { + 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<Ref<Type>> { + 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<Ref<Type>> { + 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<Ref<Type>> { + 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<String> { + 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<String> { + 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<QualifiedNameTypeAndId> { + 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<QualifiedNameTypeAndId> { + 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<BnString> { + 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<BnString> { + 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<QualifiedName> { + 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<QualifiedName> { + 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<QualifiedName>, Array<BnString>) { + 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<QualifiedName>, Array<BnString>) { + 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<BnString> { + 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<BnString> { + 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<BnString> { + 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<BnString> { + 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<BnString> { + 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<BnString> { + 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<BnString> { + 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<BnString> { + 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<Ref<Metadata>> { + 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<T: TypeArchiveNotificationCallback>( + &self, + callback: T, + ) -> TypeArchiveCallbackHandle<T> { + // 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::<T>), + typeUpdated: Some(cb_type_updated::<T>), + typeRenamed: Some(cb_type_renamed::<T>), + typeDeleted: Some(cb_type_deleted::<T>), + }; + 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<A, U, R, D>( + &self, + type_added: A, + type_updated: U, + type_renamed: R, + type_deleted: D, + ) -> TypeArchiveCallbackHandle<NotificationClosure<A, U, R, D>> + 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<F>( + &self, + mut function: F, + parents: &[TypeArchiveSnapshotId], + ) -> TypeArchiveSnapshotId + where + F: FnMut(&TypeArchiveSnapshotId) -> bool, + { + unsafe extern "C" fn cb_callback<F: FnMut(&TypeArchiveSnapshotId) -> 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::<F>), + &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<M>( + &self, + base_snapshot: &TypeArchiveSnapshotId, + first_snapshot: &TypeArchiveSnapshotId, + second_snapshot: &TypeArchiveSnapshotId, + merge_conflicts: M, + ) -> Result<BnString, Array<BnString>> + where + M: IntoIterator<Item = (String, String)>, + { + 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<M, PC>( + &self, + base_snapshot: &TypeArchiveSnapshotId, + first_snapshot: &TypeArchiveSnapshotId, + second_snapshot: &TypeArchiveSnapshotId, + merge_conflicts: M, + mut progress: PC, + ) -> Result<BnString, Array<BnString>> + where + M: IntoIterator<Item = (String, String)>, + 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<BnString>, Vec<BnString>) = 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<Self>; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for TypeArchive { + unsafe fn inc_ref(handle: &Self) -> Ref<Self> { + 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<H: std::hash::Hasher>(&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<T: TypeArchiveNotificationCallback> { + callback: *mut T, + type_archive: Ref<TypeArchive>, +} + +impl<T: TypeArchiveNotificationCallback> Drop for TypeArchiveCallbackHandle<T> { + fn drop(&mut self) { + let mut notification = BNTypeArchiveNotification { + context: self.callback as *mut c_void, + typeAdded: Some(cb_type_added::<T>), + typeUpdated: Some(cb_type_updated::<T>), + typeRenamed: Some(cb_type_renamed::<T>), + typeDeleted: Some(cb_type_deleted::<T>), + }; + // 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<A, U, R, D> +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<A, U, R, D> TypeArchiveNotificationCallback for NotificationClosure<A, U, R, D> +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<T: TypeArchiveNotificationCallback>( + 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<T: TypeArchiveNotificationCallback>( + 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<T: TypeArchiveNotificationCallback>( + 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<T: TypeArchiveNotificationCallback>( + 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<BNTypeArchiveMergeConflict>, +} + +impl TypeArchiveMergeConflict { + pub(crate) unsafe fn from_raw(handle: NonNull<BNTypeArchiveMergeConflict>) -> Self { + Self { handle } + } + + #[allow(unused)] + pub(crate) unsafe fn ref_from_raw(handle: NonNull<BNTypeArchiveMergeConflict>) -> Ref<Self> { + Ref::new(Self { handle }) + } + + pub fn get_type_archive(&self) -> Option<Ref<TypeArchive>> { + 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<Self>; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +unsafe impl RefCountable for TypeArchiveMergeConflict { + unsafe fn inc_ref(handle: &Self) -> Ref<Self> { + 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<BNTypeContainer>, +} + +impl TypeContainer { + pub(crate) unsafe fn from_raw(handle: NonNull<BNTypeContainer>) -> 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<Platform> { + 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<I, T>(&self, types: I) -> bool + where + I: IntoIterator<Item = T>, + T: Into<QualifiedNameAndType>, + { + self.add_types_with_progress(types, NoProgressCallback) + } + + pub fn add_types_with_progress<I, T, P>(&self, types: I, mut progress: P) -> bool + where + I: IntoIterator<Item = T>, + T: Into<QualifiedNameAndType>, + 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<BNQualifiedName>, 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<T: Into<QualifiedName>>(&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<T: Into<QualifiedName>>(&self, name: T) -> Option<String> { + 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<QualifiedName> { + 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<Ref<Type>> { + 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<T: Into<QualifiedName>>(&self, name: T) -> Option<Ref<Type>> { + 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<HashMap<String, (QualifiedName, Ref<Type>)>> { + 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<Array<BnString>> { + 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<Array<QualifiedName>> { + 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<BnString>, Array<QualifiedName>)> { + 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<QualifiedNameAndType, Array<TypeParserError>> { + 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<O, I>( + &self, + source: &str, + filename: &str, + options: O, + include_directories: I, + auto_type_source: &str, + import_dependencies: bool, + ) -> Result<TypeParserResult, Array<TypeParserError>> + where + O: IntoIterator<Item = String>, + I: IntoIterator<Item = String>, + { + 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<Enumeration> { + 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<EnumerationMember> { + 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<Self> { + debug_assert!(!handle.is_null()); + Ref::new(Self { handle }) + } + + pub fn builder() -> EnumerationBuilder { + EnumerationBuilder::new() + } + + pub fn members(&self) -> Vec<EnumerationMember> { + 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> { + Self::ref_from_raw(BNNewEnumerationReference(handle.handle)) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeEnumeration(handle.handle); + } +} + +impl ToOwned for Enumeration { + type Owned = Ref<Self>; + + 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<BNTypeLibrary>, +} + +impl TypeLibrary { + pub(crate) unsafe fn from_raw(handle: NonNull<BNTypeLibrary>) -> Self { + Self { handle } + } + + pub(crate) unsafe fn ref_from_raw(handle: NonNull<BNTypeLibrary>) -> Ref<Self> { + 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<Self> { + 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<TypeLibrary> { + 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<TypeLibrary> { + 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<Ref<TypeLibrary>> { + 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<Ref<TypeLibrary>> { + 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<Ref<TypeLibrary>> { + 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<BnString> { + 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<BnString> { + 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<Ref<Metadata>> { + 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<Metadata> { + 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 <binaryview.BinaryView.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 <binaryview.BinaryView.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. + /// + /// <div class="warning"> + /// + /// Use this api with extreme caution. + /// + /// </div> + 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 <binaryview.BinaryView.import_library_object>` instead. + pub fn get_named_object(&self, name: QualifiedName) -> Option<Ref<Type>> { + 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 <binaryview.BinaryView.import_library_type>` instead. + pub fn get_named_type(&self, name: QualifiedName) -> Option<Ref<Type>> { + 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<QualifiedNameAndType> { + 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<QualifiedNameAndType> { + 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<Self> { + 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<Self>; + + 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<T: TypeParser>( + 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::<T>), + preprocessSource: Some(cb_preprocess_source::<T>), + parseTypesFromSource: Some(cb_parse_types_from_source::<T>), + parseTypeString: Some(cb_parse_type_string::<T>), + 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<BNTypeParser>, +} + +impl CoreTypeParser { + pub(crate) unsafe fn from_raw(handle: NonNull<BNTypeParser>) -> Self { + Self { handle } + } + + pub fn parsers() -> Array<CoreTypeParser> { + let mut count = 0; + let result = unsafe { BNGetTypeParserList(&mut count) }; + unsafe { Array::new(result, count, ()) } + } + + pub fn parser_by_name(name: &str) -> Option<CoreTypeParser> { + 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<String> { + 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<String, Vec<TypeParserError>> { + 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<TypeParserError> = 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<TypeParserResult, Vec<TypeParserError>> { + 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<TypeParserError> = unsafe { Array::new(errors, error_count, ()) }; + Err(errors.to_vec()) + } + } + + fn parse_type_string( + &self, + source: &str, + platform: &Platform, + existing_types: &TypeContainer, + ) -> Result<QualifiedNameAndType, Vec<TypeParserError>> { + 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<TypeParserError> = 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<String>; + + /// 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<String, Vec<TypeParserError>>; + + /// 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<TypeParserResult, Vec<TypeParserError>>; + + /// 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<QualifiedNameAndType, Vec<TypeParserError>>; +} + +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<ParsedType>, + pub variables: Vec<ParsedType>, + pub functions: Vec<ParsedType>, +} + +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<Type>, + 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<Type>, 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<T: TypeParser>( + 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<T: TypeParser>( + 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<T: TypeParser>( + 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<T: TypeParser>( + 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<T: TypePrinter>( + 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::<T>), + getTypeTokensBeforeName: Some(cb_get_type_tokens_before_name::<T>), + getTypeTokensAfterName: Some(cb_get_type_tokens_after_name::<T>), + getTypeString: Some(cb_get_type_string::<T>), + getTypeStringBeforeName: Some(cb_get_type_string_before_name::<T>), + getTypeStringAfterName: Some(cb_get_type_string_after_name::<T>), + getTypeLines: Some(cb_get_type_lines::<T>), + printAllTypes: Some(cb_print_all_types::<T>), + 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<BNTypePrinter>, +} + +impl CoreTypePrinter { + pub(crate) unsafe fn from_raw(handle: NonNull<BNTypePrinter>) -> CoreTypePrinter { + Self { handle } + } + + pub fn printers() -> Array<CoreTypePrinter> { + 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<CoreTypePrinter> { + 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<T: Into<QualifiedName>>( + &self, + type_: &Type, + platform: &Platform, + name: T, + base_confidence: u8, + escaping: TokenEscapingType, + ) -> Option<Array<InstructionTextToken>> { + 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<Array<InstructionTextToken>> { + 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<Array<InstructionTextToken>> { + 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<T: Into<QualifiedName>>( + &self, + type_: &Type, + platform: &Platform, + name: T, + escaping: TokenEscapingType, + ) -> Option<BnString> { + 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<BnString> { + 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<BnString> { + 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<T: Into<QualifiedName>>( + &self, + type_: &Type, + types: &TypeContainer, + name: T, + padding_cols: isize, + collapsed: bool, + escaping: TokenEscapingType, + ) -> Option<Array<TypeDefinitionLine>> { + 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::<TypeDefinitionLine>::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<T, I>( + &self, + types: T, + data: &BinaryView, + padding_cols: isize, + escaping: TokenEscapingType, + ) -> Option<BnString> + where + T: Iterator<Item = I>, + I: Into<QualifiedNameAndType>, + { + let mut result = std::ptr::null_mut(); + let (mut raw_names, mut raw_types): (Vec<BNQualifiedName>, 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<T, I>( + &self, + types: T, + data: &BinaryView, + padding_cols: isize, + escaping: TokenEscapingType, + ) -> Option<BnString> + where + T: IntoIterator<Item = I>, + I: Into<QualifiedNameAndType>, + { + 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<BNQualifiedName>, 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<T: Into<QualifiedName>>( + &self, + type_: Ref<Type>, + platform: Option<Ref<Platform>>, + name: T, + base_confidence: u8, + escaping: TokenEscapingType, + ) -> Option<Vec<InstructionTextToken>>; + + /// 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<Type>, + platform: Option<Ref<Platform>>, + base_confidence: u8, + parent_type: Option<Ref<Type>>, + escaping: TokenEscapingType, + ) -> Option<Vec<InstructionTextToken>>; + + /// 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<Type>, + platform: Option<Ref<Platform>>, + base_confidence: u8, + parent_type: Option<Ref<Type>>, + escaping: TokenEscapingType, + ) -> Option<Vec<InstructionTextToken>>; + + /// 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<T: Into<QualifiedName>>( + &self, + type_: Ref<Type>, + platform: Option<Ref<Platform>>, + name: T, + escaping: TokenEscapingType, + ) -> Option<String>; + + /// 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<Type>, + platform: Option<Ref<Platform>>, + escaping: TokenEscapingType, + ) -> Option<String>; + + /// 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<Type>, + platform: Option<Ref<Platform>>, + escaping: TokenEscapingType, + ) -> Option<String>; + + /// 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<T: Into<QualifiedName>>( + &self, + type_: Ref<Type>, + types: &TypeContainer, + name: T, + padding_cols: isize, + collapsed: bool, + escaping: TokenEscapingType, + ) -> Option<Vec<TypeDefinitionLine>>; + + /// 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<QualifiedName>, + types: Vec<Ref<Type>>, + data: Ref<BinaryView>, + padding_cols: isize, + escaping: TokenEscapingType, + ) -> Option<String>; +} + +// TODO: This needs an extreme amount of documentation... +#[derive(Clone)] +pub struct TypeDefinitionLine { + pub line_type: TypeDefinitionLineType, + pub tokens: Vec<InstructionTextToken>, + pub ty: Ref<Type>, + pub parent_type: Option<Ref<Type>>, + // TODO: Document what the root type is. + pub root_type: Option<Ref<Type>>, + pub root_type_name: Option<String>, + // TODO: Document the base type, and why its a ntr instead of type + name like root type + pub base_type: Option<Ref<NamedTypeReference>>, + // 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<T: TypePrinter>( + 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<T: TypePrinter>( + 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<T: TypePrinter>( + 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<T: TypePrinter>( + 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<T: TypePrinter>( + 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<T: TypePrinter>( + 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<T: TypePrinter>( + 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<T: TypePrinter>( + 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<Structure> { + 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<BNBaseStructure> = + 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<Conf<&'a Type>>>( + &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<Conf<&'a Type>>>( + &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<Conf<&'a Type>>>( + &mut self, + ty: T, + name: &str, + bit_offset: u64, + bit_width: Option<u8>, + 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<Conf<&'a Type>>>( + &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<Vec<StructureMember>> for StructureBuilder { + fn from(members: Vec<StructureMember>) -> 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<Self> { + 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<StructureMember> { + 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<StructureMember> { + 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<InheritedStructureMember> { + 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<BaseStructure> { + 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> { + Self::ref_from_raw(BNNewStructureReference(handle.handle)) + } + + unsafe fn dec_ref(handle: &Self) { + BNFreeStructure(handle.handle); + } +} + +impl ToOwned for Structure { + type Owned = Ref<Self>; + + fn to_owned(&self) -> Self::Owned { + unsafe { RefCountable::inc_ref(self) } + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct StructureMember { + pub ty: Conf<Ref<Type>>, + // 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<u8>, + pub bit_width: Option<u8>, +} + +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<Ref<Type>>, + 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<Ref<Type>>, + 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<NamedTypeReference>, + 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<NamedTypeReference>, + 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<NamedTypeReference>, + 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<NamedTypeReference>, offset: u64, width: u64) -> Self { + Self { ty, offset, width } + } +} |
