summaryrefslogtreecommitdiff
path: root/rust/src/types
diff options
context:
space:
mode:
Diffstat (limited to 'rust/src/types')
-rw-r--r--rust/src/types/archive.rs1174
-rw-r--r--rust/src/types/container.rs409
-rw-r--r--rust/src/types/enumeration.rs201
-rw-r--r--rust/src/types/library.rs365
-rw-r--r--rust/src/types/parser.rs687
-rw-r--r--rust/src/types/printer.rs976
-rw-r--r--rust/src/types/structure.rs696
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 }
+ }
+}