diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8b33c8d6f..5a16661d0 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -17,10 +17,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - # Ensure rustfmt is installed and setup problem matcher + # Ensure rustfmt is installed - uses: actions-rust-lang/setup-rust-toolchain@v1 with: components: rustfmt - name: Rustfmt Check - working-directory: ./rust uses: actions-rust-lang/rustfmt@v1 + with: + manifest-path: ./rust diff --git a/plugins/pdb-ng/src/type_parser.rs b/plugins/pdb-ng/src/type_parser.rs index 9996bbcad..627c20583 100644 --- a/plugins/pdb-ng/src/type_parser.rs +++ b/plugins/pdb-ng/src/type_parser.rs @@ -769,7 +769,7 @@ impl<'a, S: Source<'a> + 'a> PDBParserInstance<'a, S> { ClassKind::Interface => NamedTypeReferenceClass::StructNamedTypeClass, }; return Ok(Some(Box::new(ParsedType::Bare(Type::named_type( - &*NamedTypeReference::new(ntr_class, QualifiedName::from(class_name)), + &*NamedTypeReference::new(ntr_class, class_name), ))))); } @@ -1083,7 +1083,7 @@ impl<'a, S: Source<'a> + 'a> PDBParserInstance<'a, S> { NamedTypeReferenceClass::StructNamedTypeClass }; vt_bases.push(BaseStructure::new( - NamedTypeReference::new(ntr_class, vt_base_name.into()), + NamedTypeReference::new(ntr_class, vt_base_name), 0, vt_base_type.width(), )); @@ -1638,7 +1638,7 @@ impl<'a, S: Source<'a> + 'a> PDBParserInstance<'a, S> { let ntr_class = NamedTypeReferenceClass::EnumNamedTypeClass; return Ok(Some(Box::new(ParsedType::Bare(Type::named_type( - &*NamedTypeReference::new(ntr_class, QualifiedName::from(enum_name)), + &*NamedTypeReference::new(ntr_class, enum_name), ))))); } @@ -1766,7 +1766,7 @@ impl<'a, S: Source<'a> + 'a> PDBParserInstance<'a, S> { let ntr_class = NamedTypeReferenceClass::UnionNamedTypeClass; return Ok(Some(Box::new(ParsedType::Bare(Type::named_type( - &*NamedTypeReference::new(ntr_class, QualifiedName::from(union_name)), + &*NamedTypeReference::new(ntr_class, union_name), ))))); } diff --git a/plugins/warp/src/convert.rs b/plugins/warp/src/convert.rs index f4a5b71df..2b89332e6 100644 --- a/plugins/warp/src/convert.rs +++ b/plugins/warp/src/convert.rs @@ -434,11 +434,11 @@ pub fn to_bn_type(arch: &A, ty: &Type) -> BNRef { Some(guid) => BNNamedTypeReference::new_with_id( NamedTypeReferenceClass::UnknownNamedTypeClass, guid.to_string(), - base_struct_ntr_name.into(), + base_struct_ntr_name, ), None => BNNamedTypeReference::new( NamedTypeReferenceClass::UnknownNamedTypeClass, - base_struct_ntr_name.into(), + base_struct_ntr_name, ), }; base_structs.push(BNBaseStructure::new( @@ -548,19 +548,19 @@ pub fn to_bn_type(arch: &A, ty: &Type) -> BNRef { NamedTypeReference::new_with_id( NamedTypeReferenceClass::UnknownNamedTypeClass, guid_str, - ntr_name.into(), + ntr_name, ) } None => match c.name.as_ref() { Some(ntr_name) => NamedTypeReference::new( NamedTypeReferenceClass::UnknownNamedTypeClass, - ntr_name.into(), + ntr_name, ), None => { log::error!("Referrer with no reference! {:?}", c); NamedTypeReference::new( NamedTypeReferenceClass::UnknownNamedTypeClass, - "AHHHHHH".into(), + "AHHHHHH", ) } }, diff --git a/rust/src/binaryview.rs b/rust/src/binaryview.rs index 0d9570f3a..c02ed2b00 100644 --- a/rust/src/binaryview.rs +++ b/rust/src/binaryview.rs @@ -59,6 +59,7 @@ use std::{ops, result, slice}; use crate::rc::*; use crate::references::{CodeReference, DataReference}; use crate::string::*; +use crate::typecontainer::TypeContainer; use crate::variable::DataVariable; // TODO : general reorg of modules related to bv @@ -1474,6 +1475,34 @@ pub trait BinaryViewExt: BinaryViewBase { unsafe { Array::new(result, count, ()) } } + /// Type container for all types (user and auto) in the Binary View. + /// + /// NOTE: Modifying an auto type will promote it to a user type. + fn type_container(&self) -> TypeContainer { + let type_container_ptr = + NonNull::new(unsafe { BNGetAnalysisTypeContainer(self.as_ref().handle) }); + // NOTE: I have no idea how this isn't a UAF, see the note in `TypeContainer::from_raw` + unsafe { TypeContainer::from_raw(type_container_ptr.unwrap()) } + } + + /// Type container for user types in the Binary View. + fn user_type_container(&self) -> TypeContainer { + let type_container_ptr = + NonNull::new(unsafe { BNGetAnalysisUserTypeContainer(self.as_ref().handle) }); + // NOTE: I have no idea how this isn't a UAF, see the note in `TypeContainer::from_raw` + unsafe { TypeContainer::from_raw(type_container_ptr.unwrap()) } + } + + /// Type container for auto types in the Binary View. + /// + /// NOTE: Unlike [`Self::type_container`] modification of auto types will **NOT** promote it to a user type. + fn auto_type_container(&self) -> TypeContainer { + let type_container_ptr = + NonNull::new(unsafe { BNGetAnalysisAutoTypeContainer(self.as_ref().handle) }); + // NOTE: I have no idea how this isn't a UAF, see the note in `TypeContainer::from_raw` + unsafe { TypeContainer::from_raw(type_container_ptr.unwrap()) } + } + /// Make the contents of a type library available for type/import resolution fn add_type_library(&self, library: &TypeLibrary) { unsafe { BNAddBinaryViewTypeLibrary(self.as_ref().handle, library.as_raw()) } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 6e93fccb2..b29d857a0 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -148,7 +148,6 @@ pub mod fileaccessor; pub mod filemetadata; pub mod flowgraph; pub mod function; -pub mod typeparser; pub mod functionrecognizer; pub mod headless; pub mod hlil; @@ -172,7 +171,10 @@ pub mod symbol; pub mod tags; pub mod templatesimplifier; pub mod typearchive; +pub mod typecontainer; pub mod typelibrary; +pub mod typeparser; +pub mod typeprinter; pub mod types; pub mod update; pub mod variable; diff --git a/rust/src/platform.rs b/rust/src/platform.rs index e5e21515b..08d0beafe 100644 --- a/rust/src/platform.rs +++ b/rust/src/platform.rs @@ -14,19 +14,20 @@ //! Contains all information related to the execution environment of the binary, mainly the calling conventions used -use std::{borrow::Borrow, ffi, ptr}; - use binaryninjacore_sys::*; +use std::ptr::NonNull; +use std::{borrow::Borrow, ffi, ptr}; +use crate::typecontainer::TypeContainer; +use crate::typeparser::{TypeParserError, TypeParserErrorSeverity, TypeParserResult}; use crate::{ architecture::{Architecture, CoreArchitecture}, callingconvention::CallingConvention, rc::*, string::*, typelibrary::TypeLibrary, - types::{QualifiedName, QualifiedNameAndType, Type}, + types::QualifiedNameAndType, }; -use crate::typeparser::{TypeParserError, TypeParserErrorSeverity, TypeParserResult}; #[derive(PartialEq, Eq, Hash)] pub struct Platform { @@ -169,6 +170,14 @@ impl Platform { unsafe { CoreArchitecture::from_raw(BNGetPlatformArchitecture(self.handle)) } } + pub fn type_container(&self) -> TypeContainer { + let type_container_ptr = NonNull::new(unsafe { BNGetPlatformTypeContainer(self.handle) }); + // NOTE: I have no idea how this isn't a UAF, see the note in `TypeContainer::from_raw` + // TODO: We are cloning here for platforms but we dont need to do this for [BinaryViewExt::type_container] + // TODO: Why does this require that we, construct a TypeContainer, duplicate the type container, then drop the original. + unsafe { TypeContainer::from_raw(type_container_ptr.unwrap()).clone() } + } + pub fn get_type_libraries_by_name(&self, name: T) -> Array { let mut count = 0; let name = name.into_bytes_with_nul(); @@ -262,6 +271,7 @@ impl Platform { } } + // TODO: Documentation, specifically how this differs from the TypeParser impl pub fn preprocess_source( &self, source: &str, @@ -283,7 +293,7 @@ impl Platform { include_dirs.len(), ) }; - + if success { assert!(!result.is_null()); Ok(unsafe { BnString::from_raw(result) }) @@ -291,13 +301,15 @@ impl Platform { assert!(!error_string.is_null()); Err(TypeParserError::new( TypeParserErrorSeverity::FatalSeverity, - unsafe { BnString::from_raw(error_string) }, - file_name, + unsafe { BnString::from_raw(error_string) }.to_string(), + file_name.to_string(), 0, 0, )) } } + + // TODO: Documentation, specifically how this differs from the TypeParser impl pub fn parse_types_from_source( &self, src: &str, @@ -309,14 +321,14 @@ impl Platform { let file_name_cstr = BnString::new(filename); let auto_type_source = BnString::new(auto_type_source); - let mut result = BNTypeParserResult::default(); + let mut raw_result = BNTypeParserResult::default(); let mut error_string = ptr::null_mut(); let success = unsafe { BNParseTypesFromSource( self.handle, source_cstr.as_ptr(), file_name_cstr.as_ptr(), - &mut result, + &mut raw_result, &mut error_string, include_dirs.as_ptr() as *mut *const ffi::c_char, include_dirs.len(), @@ -325,19 +337,20 @@ impl Platform { }; if success { - Ok(unsafe { TypeParserResult::from_raw(result) }) + Ok(raw_result.into()) } else { assert!(!error_string.is_null()); Err(TypeParserError::new( TypeParserErrorSeverity::FatalSeverity, - unsafe { BnString::from_raw(error_string) }, - filename, + unsafe { BnString::from_raw(error_string) }.to_string(), + filename.to_string(), 0, 0, )) } } + // TODO: Documentation, specifically how this differs from the TypeParser impl pub fn parse_types_from_source_file( &self, filename: &str, @@ -362,13 +375,13 @@ impl Platform { }; if success { - Ok(unsafe { TypeParserResult::from_raw(result) }) + Ok(result.into()) } else { assert!(!error_string.is_null()); Err(TypeParserError::new( TypeParserErrorSeverity::FatalSeverity, - unsafe { BnString::from_raw(error_string) }, - filename, + unsafe { BnString::from_raw(error_string) }.to_string(), + filename.to_string(), 0, 0, )) diff --git a/rust/src/rc.rs b/rust/src/rc.rs index 559991360..87ec99d21 100644 --- a/rust/src/rc.rs +++ b/rust/src/rc.rs @@ -203,6 +203,7 @@ pub(crate) unsafe trait CoreArrayProviderInner: CoreArrayProvider { unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a>; } +// TODO: I would really like if we impld Debug for this, but lifetimes are hard! #[allow(private_bounds)] pub struct Array { contents: *mut P::Raw, @@ -210,19 +211,6 @@ pub struct Array { context: P::Context, } -unsafe impl

Sync for Array

-where - P: CoreArrayProviderInner, - P::Context: Sync, -{ -} -unsafe impl

Send for Array

-where - P: CoreArrayProviderInner, - P::Context: Send, -{ -} - #[allow(private_bounds)] impl Array

{ pub(crate) unsafe fn new(raw: *mut P::Raw, count: usize, context: P::Context) -> Self { @@ -242,10 +230,13 @@ impl Array

{ pub fn is_empty(&self) -> bool { self.count == 0 } -} -#[allow(private_bounds)] -impl Array

{ + pub fn to_vec(&self) -> Vec> { + let mut res = Vec::with_capacity(self.count); + res.extend(self.iter()); + res + } + #[inline] pub fn get(&self, index: usize) -> P::Wrapped<'_> { unsafe { @@ -262,6 +253,19 @@ impl Array

{ } } +unsafe impl

Sync for Array

+where + P: CoreArrayProviderInner, + P::Context: Sync, +{ +} +unsafe impl

Send for Array

+where + P: CoreArrayProviderInner, + P::Context: Send, +{ +} + impl<'a, P: CoreArrayProviderInner> IntoIterator for &'a Array

{ type Item = P::Wrapped<'a>; type IntoIter = ArrayIter<'a, P>; diff --git a/rust/src/string.rs b/rust/src/string.rs index 52458c03d..c660aa301 100644 --- a/rust/src/string.rs +++ b/rust/src/string.rs @@ -277,7 +277,7 @@ unsafe impl BnStrCompatible for &Path { type Result = Vec; fn into_bytes_with_nul(self) -> Self::Result { - self.to_string_lossy().into_bytes_with_nul() + self.as_os_str().as_encoded_bytes().to_vec() } } diff --git a/rust/src/typecontainer.rs b/rust/src/typecontainer.rs new file mode 100644 index 000000000..bd71b177f --- /dev/null +++ b/rust/src/typecontainer.rs @@ -0,0 +1,448 @@ +// 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::rc::{Array, Ref}; +use crate::string::{raw_to_string, BnStrCompatible, BnString}; +use crate::typeparser::{TypeParserError, TypeParserResult}; +use crate::types::{QualifiedName, QualifiedNameAndType, Type}; +use binaryninjacore_sys::*; +use std::collections::HashMap; +use std::ffi::{c_char, c_void}; +use std::ptr::NonNull; + +pub type TypeContainerType = BNTypeContainerType; + +/// A `TypeContainer` is a generic interface to access various Binary Ninja models +/// that contain types. Types are stored with both a unique id and a unique name. +#[repr(transparent)] +pub struct TypeContainer { + pub handle: NonNull, +} + +impl TypeContainer { + pub(crate) unsafe fn from_raw(handle: NonNull) -> Self { + // NOTE: There does not seem to be any shared ref counting for type containers, it seems if the + // NOTE: binary view is freed the type container will be freed and cause this to become invalid + // NOTE: but this is how the C++ and Python bindings operate so i guess its fine? + // TODO: I really dont get how some of the usage of the TypeContainer doesnt free the underlying container. + // TODO: So for now we always duplicate the type container + // let cloned_ptr = NonNull::new(BNDuplicateTypeContainer(handle.as_ptr())); + // Self { + // handle: cloned_ptr.unwrap(), + // } + Self { handle } + } + + /// 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) -> BnString { + let result = unsafe { BNTypeContainerGetId(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + /// Get a user-friendly name for the Type Container. + pub fn name(&self) -> BnString { + let result = unsafe { BNTypeContainerGetName(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + /// Get the type of underlying model the Type Container is accessing. + pub fn container_type(&self) -> TypeContainerType { + unsafe { BNTypeContainerGetType(self.handle.as_ptr()) } + } + + /// If the Type Container supports mutable operations (add, rename, delete) + pub fn is_mutable(&self) -> bool { + unsafe { BNTypeContainerIsMutable(self.handle.as_ptr()) } + } + + /// Get the Platform object associated with this Type Container. All Type Containers + /// have exactly one associated Platform (as opposed to, e.g. Type Libraries). + pub fn platform(&self) -> Ref { + let result = unsafe { BNTypeContainerGetPlatform(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { Platform::ref_from_raw(result) } + } + + /// Add or update types to a Type Container. If the Type Container already contains + /// a type with the same name as a type being added, the existing type will be + /// replaced with the definition given to this function, and references will be + /// updated in the source model. + pub fn add_types(&self, types: I) -> bool + where + I: IntoIterator, + T: Into, + { + // TODO: I dislike how this iter unzip looks like... but its how to avoid allocating again... + let (raw_names, mut raw_types): (Vec, Vec<_>) = types + .into_iter() + .map(|t| { + let t = t.into(); + (BNQualifiedName::from(t.name), 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; + unsafe { + BNTypeContainerAddTypes( + self.handle.as_ptr(), + raw_names.as_ptr(), + raw_types.as_mut_ptr(), + raw_types.len(), + Some(cb_progress_nop), + std::ptr::null_mut(), + &mut result_names, + &mut result_ids, + &mut result_count, + ) + } + } + + pub fn add_types_with_progress(&self, types: I, mut progress: F) -> bool + where + I: IntoIterator, + T: Into, + F: FnMut(usize, usize) -> bool, + { + // TODO: I dislike how this iter unzip looks like... but its how to avoid allocating again... + let (raw_names, mut raw_types): (Vec, Vec<_>) = types + .into_iter() + .map(|t| { + let t = t.into(); + (BNQualifiedName::from(t.name), 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; + unsafe { + BNTypeContainerAddTypes( + self.handle.as_ptr(), + raw_names.as_ptr(), + raw_types.as_mut_ptr(), + raw_types.len(), + Some(cb_progress::), + &mut progress as *mut F as *mut c_void, + &mut result_names, + &mut result_ids, + &mut result_count, + ) + } + } + + /// 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, S: BnStrCompatible>( + &self, + name: T, + type_id: S, + ) -> bool { + let type_id = type_id.into_bytes_with_nul(); + let raw_name = BNQualifiedName::from(name.into()); + unsafe { + BNTypeContainerRenameType( + self.handle.as_ptr(), + type_id.as_ref().as_ptr() as *const c_char, + &raw_name, + ) + } + } + + /// 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: S) -> bool { + let type_id = type_id.into_bytes_with_nul(); + unsafe { + BNTypeContainerDeleteType( + self.handle.as_ptr(), + type_id.as_ref().as_ptr() as *const c_char, + ) + } + } + + /// Get the unique id of the type in the Type Container with the given name. + /// + /// If no type with that name exists, returns None. + pub fn type_id>(&self, name: T) -> Option { + let mut result = std::ptr::null_mut(); + let raw_name = BNQualifiedName::from(name.into()); + let success = + unsafe { BNTypeContainerGetTypeId(self.handle.as_ptr(), &raw_name, &mut result) }; + success.then(|| unsafe { BnString::from_raw(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: S) -> Option { + let type_id = type_id.into_bytes_with_nul(); + let mut result = BNQualifiedName::default(); + let success = unsafe { + BNTypeContainerGetTypeName( + self.handle.as_ptr(), + type_id.as_ref().as_ptr() as *const c_char, + &mut result, + ) + }; + success.then(|| QualifiedName::from(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: S) -> Option> { + let type_id = type_id.into_bytes_with_nul(); + let mut result = std::ptr::null_mut(); + let success = unsafe { + BNTypeContainerGetTypeById( + self.handle.as_ptr(), + type_id.as_ref().as_ptr() as *const c_char, + &mut result, + ) + }; + success.then(|| unsafe { Type::ref_from_raw(result) }) + } + + /// Get the definition of the type in the Type Container with the given name. + /// + /// If no type with that name exists, returns None. + pub fn type_by_name>(&self, name: T) -> Option> { + let mut result = std::ptr::null_mut(); + let raw_name = BNQualifiedName::from(name.into()); + let success = + unsafe { BNTypeContainerGetTypeByName(self.handle.as_ptr(), &raw_name, &mut result) }; + success.then(|| unsafe { Type::ref_from_raw(result) }) + } + + /// Get a mapping of all types in a Type Container. + // TODO: This needs to be redone... somehow all of these need to be merged into one array... + pub fn types(&self) -> Option)>> { + let mut type_ids = std::ptr::null_mut(); + let mut type_names = std::ptr::null_mut(); + let mut type_types = std::ptr::null_mut(); + let mut type_count = 0; + let success = unsafe { + BNTypeContainerGetTypes( + self.handle.as_ptr(), + &mut type_ids, + &mut type_names, + &mut type_types, + &mut type_count, + ) + }; + success.then(|| unsafe { + let raw_ids = std::slice::from_raw_parts(type_ids, type_count); + let raw_names = std::slice::from_raw_parts(type_names, type_count); + let raw_types = std::slice::from_raw_parts(type_types, type_count); + let mut map = HashMap::new(); + for (idx, raw_id) in raw_ids.iter().enumerate() { + let id = raw_to_string(*raw_id).expect("Valid string"); + // Take the qualified name as a ref as the name should not be freed. + let name = QualifiedName::from(&raw_names[idx]); + // Take the type as an owned ref, as the returned type was not already incremented. + let ty = Type::from_raw(raw_types[idx]).to_owned(); + map.insert(id, (name, ty)); + } + BNFreeStringList(type_ids, type_count); + BNFreeTypeNameList(type_names, type_count); + BNFreeTypeList(type_types, type_count); + map + }) + } + + /// Get all type ids in a Type Container. + pub fn type_ids(&self) -> Option> { + let mut type_ids = std::ptr::null_mut(); + let mut type_count = 0; + let success = unsafe { + BNTypeContainerGetTypeIds(self.handle.as_ptr(), &mut type_ids, &mut type_count) + }; + success.then(|| unsafe { Array::new(type_ids, type_count, ()) }) + } + + /// Get all type names in a Type Container. + pub fn type_names(&self) -> Option> { + let mut type_ids = std::ptr::null_mut(); + let mut type_count = 0; + let success = unsafe { + BNTypeContainerGetTypeNames(self.handle.as_ptr(), &mut type_ids, &mut type_count) + }; + success.then(|| unsafe { Array::new(type_ids, type_count, ()) }) + } + + /// Get a mapping of all type ids and type names in a Type Container. + pub fn type_names_and_ids(&self) -> Option<(Array, Array)> { + let mut type_ids = std::ptr::null_mut(); + let mut type_names = std::ptr::null_mut(); + let mut type_count = 0; + let success = unsafe { + BNTypeContainerGetTypeNamesAndIds( + self.handle.as_ptr(), + &mut type_ids, + &mut type_names, + &mut type_count, + ) + }; + success.then(|| unsafe { + let ids = Array::new(type_ids, type_count, ()); + let names = Array::new(type_names, type_count, ()); + (ids, names) + }) + } + + /// Parse a single type and name from a string containing their definition, with + /// knowledge of the types in the Type Container. + /// + /// * `source` - Source code to parse + /// * `import_dependencies` - If Type Library / Type Archive types should be imported during parsing + pub fn parse_type_string( + &self, + source: S, + import_dependencies: bool, + ) -> Result> { + let source = source.into_bytes_with_nul(); + 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_ref().as_ptr() as *const c_char, + import_dependencies, + &mut result, + &mut errors, + &mut error_count, + ) + }; + if success { + Ok(QualifiedNameAndType::from(result)) + } else { + assert!(!errors.is_null()); + Err(unsafe { Array::new(errors, error_count, ()) }) + } + } + + /// Parse an entire block of source into types, variables, and functions, with + /// knowledge of the types in the Type Container. + + /// * `source` - Source code to parse + /// * `file_name` - Name of the file containing the source (optional: exists on disk) + /// * `options` - String arguments to pass as options, e.g. command line arguments + /// * `include_dirs` - List of directories to include in the header search path + /// * `auto_type_source` - Source of types if used for automatically generated types + /// * `import_dependencies` - If Type Library / Type Archive types should be imported during parsing + pub fn parse_types_from_source( + &self, + source: S, + filename: F, + options: O, + include_directories: D, + auto_type_source: A, + import_dependencies: bool, + ) -> Result> + where + S: BnStrCompatible, + F: BnStrCompatible, + O: IntoIterator, + O::Item: BnStrCompatible, + D: IntoIterator, + D::Item: BnStrCompatible, + A: BnStrCompatible, + { + let source = source.into_bytes_with_nul(); + let filename = filename.into_bytes_with_nul(); + let options: Vec<_> = options + .into_iter() + .map(|o| o.into_bytes_with_nul()) + .collect(); + let options_raw: Vec<*const c_char> = options + .iter() + .map(|o| o.as_ref().as_ptr() as *const c_char) + .collect(); + let include_directories: Vec<_> = include_directories + .into_iter() + .map(|d| d.into_bytes_with_nul()) + .collect(); + let include_directories_raw: Vec<*const c_char> = include_directories + .iter() + .map(|d| d.as_ref().as_ptr() as *const c_char) + .collect(); + let auto_type_source = auto_type_source.into_bytes_with_nul(); + 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_ref().as_ptr() as *const c_char, + filename.as_ref().as_ptr() as *const c_char, + options_raw.as_ptr(), + options_raw.len(), + include_directories_raw.as_ptr(), + include_directories_raw.len(), + auto_type_source.as_ref().as_ptr() as *const c_char, + import_dependencies, + &mut raw_result, + &mut errors, + &mut error_count, + ) + }; + if success { + Ok(raw_result.into()) + } else { + assert!(!errors.is_null()); + Err(unsafe { Array::new(errors, error_count, ()) }) + } + } +} + +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(), + } + } + } +} + +unsafe extern "C" fn cb_progress_nop( + _ctxt: *mut ::std::os::raw::c_void, + _progress: usize, + _total: usize, +) -> bool { + true +} + +unsafe extern "C" fn cb_progress bool>( + ctxt: *mut ::std::os::raw::c_void, + progress: usize, + total: usize, +) -> bool { + let ctxt = &mut *(ctxt as *mut F); + ctxt(progress, total) +} diff --git a/rust/src/typeparser.rs b/rust/src/typeparser.rs index d95c94d58..111ad9df8 100644 --- a/rust/src/typeparser.rs +++ b/rust/src/typeparser.rs @@ -1,39 +1,53 @@ -use core::{ffi, mem, ptr}; - use binaryninjacore_sys::*; +use std::ffi::{c_char, c_void}; +use std::fmt::Debug; +use std::ptr::NonNull; -use crate::binaryview::BinaryView; -use crate::disassembly::InstructionTextToken; use crate::platform::Platform; -use crate::rc::{Array, ArrayGuard, CoreArrayProvider, CoreArrayProviderInner, Ref}; -use crate::string::{BnStrCompatible, BnString}; -use crate::types::{NamedTypeReference, QualifiedName, QualifiedNameAndType, Type}; +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref}; +use crate::string::{raw_to_string, BnStrCompatible, BnString}; +use crate::typecontainer::TypeContainer; +use crate::types::{QualifiedName, QualifiedNameAndType, Type}; pub type TypeParserErrorSeverity = BNTypeParserErrorSeverity; pub type TypeParserOption = BNTypeParserOption; -pub type TokenEscapingType = BNTokenEscapingType; -pub type TypeDefinitionLineType = BNTypeDefinitionLineType; -pub type TypeContainerType = BNTypeContainerType; + +/// Register a custom parser with the API +pub fn register_type_parser( + name: S, + parser: T, +) -> (&'static mut T, CoreTypeParser) { + let parser = Box::leak(Box::new(parser)); + let mut callback = BNTypeParserCallbacks { + context: parser as *mut _ as *mut c_void, + getOptionText: Some(cb_get_option_text::), + preprocessSource: Some(cb_preprocess_source::), + parseTypesFromSource: Some(cb_parse_types_from_source::), + parseTypeString: Some(cb_parse_type_string::), + freeString: Some(cb_free_string), + freeResult: Some(cb_free_result), + freeErrorList: Some(cb_free_error_list), + }; + let result = unsafe { + BNRegisterTypeParser( + name.into_bytes_with_nul().as_ref().as_ptr() as *const _, + &mut callback, + ) + }; + let core = unsafe { CoreTypeParser::from_raw(NonNull::new(result).unwrap()) }; + (parser, core) +} #[repr(transparent)] pub struct CoreTypeParser { - handle: ptr::NonNull, + handle: NonNull, } impl CoreTypeParser { - #[allow(clippy::mut_from_ref)] - fn as_raw(&self) -> &mut BNTypeParser { - unsafe { &mut *self.handle.as_ptr() } - } - - pub(crate) unsafe fn from_raw(handle: ptr::NonNull) -> Self { + pub(crate) unsafe fn from_raw(handle: NonNull) -> Self { Self { handle } } - pub(crate) unsafe fn ref_from_raw(handle: &*mut BNTypeParser) -> &Self { - mem::transmute(handle) - } - pub fn parsers() -> Array { let mut count = 0; let result = unsafe { BNGetTypeParserList(&mut count) }; @@ -42,53 +56,59 @@ impl CoreTypeParser { pub fn parser_by_name(name: S) -> Option { let name_raw = name.into_bytes_with_nul(); - let result = - unsafe { BNGetTypeParserByName(name_raw.as_ref().as_ptr() as *const ffi::c_char) }; - ptr::NonNull::new(result).map(|x| unsafe { Self::from_raw(x) }) + let result = unsafe { BNGetTypeParserByName(name_raw.as_ref().as_ptr() as *const c_char) }; + NonNull::new(result).map(|x| unsafe { Self::from_raw(x) }) } pub fn name(&self) -> BnString { - let result = unsafe { BNGetTypeParserName(self.as_raw()) }; + let result = unsafe { BNGetTypeParserName(self.handle.as_ptr()) }; assert!(!result.is_null()); unsafe { BnString::from_raw(result) } } +} - pub fn get_option_text(&self, option: TypeParserOption, value: &str) -> Option { - let mut output = ptr::null_mut(); +impl TypeParser for CoreTypeParser { + fn get_option_text(&self, option: TypeParserOption, value: &str) -> Option { + let mut output = std::ptr::null_mut(); let value_cstr = BnString::new(value); let result = unsafe { - BNGetTypeParserOptionText(self.as_raw(), option, value_cstr.as_ptr(), &mut output) + BNGetTypeParserOptionText( + self.handle.as_ptr(), + option, + value_cstr.as_ptr(), + &mut output, + ) }; result.then(|| { assert!(!output.is_null()); - unsafe { BnString::from_raw(output) } + value_cstr.to_string() }) } - pub fn preprocess_source( + fn preprocess_source( &self, source: &str, file_name: &str, platform: &Platform, existing_types: &TypeContainer, - options: &[BnString], - include_dirs: &[BnString], - ) -> Result> { + options: &[String], + include_dirs: &[String], + ) -> Result> { let source_cstr = BnString::new(source); let file_name_cstr = BnString::new(file_name); - let mut result = ptr::null_mut(); - let mut errors = ptr::null_mut(); + let mut result = std::ptr::null_mut(); + let mut errors = std::ptr::null_mut(); let mut error_count = 0; let success = unsafe { BNTypeParserPreprocessSource( - self.as_raw(), + self.handle.as_ptr(), source_cstr.as_ptr(), file_name_cstr.as_ptr(), platform.handle, - existing_types.as_raw(), - options.as_ptr() as *const *const ffi::c_char, + existing_types.handle.as_ptr(), + options.as_ptr() as *const *const c_char, options.len(), - include_dirs.as_ptr() as *const *const ffi::c_char, + include_dirs.as_ptr() as *const *const c_char, include_dirs.len(), &mut result, &mut errors, @@ -97,87 +117,93 @@ impl CoreTypeParser { }; if success { assert!(!result.is_null()); - Ok(unsafe { BnString::from_raw(result) }) + let bn_result = unsafe { BnString::from_raw(result) }; + Ok(bn_result.to_string()) } else { - Err(unsafe { Array::new(errors, error_count, ()) }) + let errors: Array = unsafe { Array::new(errors, error_count, ()) }; + Err(errors.to_vec()) } } - pub fn parse_types_from_source( + fn parse_types_from_source( &self, source: &str, file_name: &str, platform: &Platform, existing_types: &TypeContainer, - options: &[BnString], - include_dirs: &[BnString], + options: &[String], + include_dirs: &[String], auto_type_source: &str, - ) -> Result> { + ) -> Result> { let source_cstr = BnString::new(source); let file_name_cstr = BnString::new(file_name); let auto_type_source = BnString::new(auto_type_source); - let mut result = BNTypeParserResult::default(); - let mut errors = ptr::null_mut(); + let mut raw_result = BNTypeParserResult::default(); + let mut errors = std::ptr::null_mut(); let mut error_count = 0; let success = unsafe { BNTypeParserParseTypesFromSource( - self.as_raw(), + self.handle.as_ptr(), source_cstr.as_ptr(), file_name_cstr.as_ptr(), platform.handle, - existing_types.as_raw(), - options.as_ptr() as *const *const ffi::c_char, + existing_types.handle.as_ptr(), + options.as_ptr() as *const *const c_char, options.len(), - include_dirs.as_ptr() as *const *const ffi::c_char, + include_dirs.as_ptr() as *const *const c_char, include_dirs.len(), auto_type_source.as_ptr(), - &mut result, + &mut raw_result, &mut errors, &mut error_count, ) }; if success { - Ok(unsafe { TypeParserResult::from_raw(result) }) + Ok(raw_result.into()) } else { - Err(unsafe { Array::new(errors, error_count, ()) }) + let errors: Array = unsafe { Array::new(errors, error_count, ()) }; + Err(errors.to_vec()) } } - pub fn parse_type_string( + fn parse_type_string( &self, source: &str, platform: &Platform, existing_types: &TypeContainer, - ) -> Result> { + ) -> Result> { let source_cstr = BnString::new(source); let mut output = BNQualifiedNameAndType::default(); - let mut errors = ptr::null_mut(); + let mut errors = std::ptr::null_mut(); let mut error_count = 0; let result = unsafe { BNTypeParserParseTypeString( - self.as_raw(), + self.handle.as_ptr(), source_cstr.as_ptr(), platform.handle, - existing_types.as_raw(), + existing_types.handle.as_ptr(), &mut output, &mut errors, &mut error_count, ) }; if result { - Ok(QualifiedNameAndType(output)) + Ok(QualifiedNameAndType::from(output)) } else { - Err(unsafe { Array::new(errors, error_count, ()) }) + let errors: Array = unsafe { Array::new(errors, error_count, ()) }; + Err(errors.to_vec()) } } } impl Default for CoreTypeParser { fn default() -> Self { - unsafe { Self::from_raw(ptr::NonNull::new(BNGetDefaultTypeParser()).unwrap()) } + // 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, @@ -185,7 +211,7 @@ pub trait TypeParser { /// /// * `option` - Option type /// * `value` - Option value - fn get_option_text(&self, option: TypeParserOption, value: &str) -> Option; + fn get_option_text(&self, option: TypeParserOption, value: &str) -> Option; /// Preprocess a block of source, returning the source that would be parsed /// @@ -201,9 +227,9 @@ pub trait TypeParser { file_name: &str, platform: &Platform, existing_types: &TypeContainer, - options: &[BnString], - include_dirs: &[BnString], - ) -> Result>; + options: &[String], + include_dirs: &[String], + ) -> Result>; /// Parse an entire block of source into types, variables, and functions /// @@ -220,10 +246,10 @@ pub trait TypeParser { file_name: &str, platform: &Platform, existing_types: &TypeContainer, - options: &[BnString], - include_dirs: &[BnString], + options: &[String], + include_dirs: &[String], auto_type_source: &str, - ) -> Result>; + ) -> Result>; /// Parse a single type and name from a string containing their definition. /// @@ -238,36 +264,10 @@ pub trait TypeParser { ) -> Result>; } -/// Register a custom parser with the API -pub fn register_type_parser( - name: S, - parser: T, -) -> (&'static mut T, CoreTypeParser) { - let parser = Box::leak(Box::new(parser)); - let mut callback = BNTypeParserCallbacks { - context: parser as *mut _ as *mut ffi::c_void, - getOptionText: Some(cb_get_option_text::), - preprocessSource: Some(cb_preprocess_source::), - parseTypesFromSource: Some(cb_parse_types_from_source::), - parseTypeString: Some(cb_parse_type_string::), - freeString: Some(cb_free_string), - freeResult: Some(cb_free_result), - freeErrorList: Some(cb_free_error_list), - }; - let result = unsafe { - BNRegisterTypeParser( - name.into_bytes_with_nul().as_ref().as_ptr() as *const ffi::c_char, - &mut callback, - ) - }; - let core = unsafe { CoreTypeParser::from_raw(ptr::NonNull::new(result).unwrap()) }; - (parser, core) -} - impl CoreArrayProvider for CoreTypeParser { type Raw = *mut BNTypeParser; type Context = (); - type Wrapped<'a> = &'a Self; + type Wrapped<'a> = Self; } unsafe impl CoreArrayProviderInner for CoreTypeParser { @@ -276,1366 +276,336 @@ unsafe impl CoreArrayProviderInner for CoreTypeParser { } unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { - CoreTypeParser::ref_from_raw(raw) + // 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) } } -/// 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. -// /// -// /// 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] -#[repr(transparent)] -pub struct TypeContainer { - handle: ptr::NonNull, +#[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 TypeContainer { - pub(crate) unsafe fn from_raw(handle: ptr::NonNull) -> Self { - Self { handle } - } - - pub(crate) unsafe fn ref_from_raw(handle: &*mut BNTypeContainer) -> &Self { - assert!(!handle.is_null()); - mem::transmute(handle) - } - - #[allow(clippy::ref_as_ptr)] - pub(crate) unsafe fn as_raw(&self) -> &mut BNTypeContainer { - &mut *self.handle.as_ptr() - } - - /// 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) -> BnString { - let result = unsafe { BNTypeContainerGetId(self.as_raw()) }; - assert!(!result.is_null()); - unsafe { BnString::from_raw(result) } - } - - /// Get a user-friendly name for the Type Container. - pub fn name(&self) -> BnString { - let result = unsafe { BNTypeContainerGetName(self.as_raw()) }; - assert!(!result.is_null()); - unsafe { BnString::from_raw(result) } - } - - /// Get the type of underlying model the Type Container is accessing. - pub fn container_type(&self) -> TypeContainerType { - unsafe { BNTypeContainerGetType(self.as_raw()) } - } - - /// Test if the Type Container supports mutable operations (add, rename, delete) - pub fn is_mutable(&self) -> bool { - unsafe { BNTypeContainerIsMutable(self.as_raw()) } - } - - /// Get the Platform object associated with this Type Container. All Type Containers - /// have exactly one associated Platform (as opposed to, e.g. Type Libraries). - pub fn platform(&self) -> Ref { - let result = unsafe { BNTypeContainerGetPlatform(self.as_raw()) }; - assert!(!result.is_null()); - unsafe { Platform::ref_from_raw(result) } - } - - /// Add or update types to a Type Container. If the Type Container already contains - /// a type with the same name as a type being added, the existing type will be - /// replaced with the definition given to this function, and references will be - /// updated in the source model. - pub fn add_types(&self, types: I) -> bool - where - I: IntoIterator)>, - { - let (names, types): (Vec, Vec>) = types.into_iter().unzip(); - - // SAFETY QualifiedName is transparent with BNQualifiedName - let names_raw: &[BNQualifiedName] = unsafe { mem::transmute(names.as_slice()) }; - - let mut types_raw: Vec<*mut BNType> = types.iter().map(|t| t.handle).collect(); - - let mut result_names = ptr::null_mut(); - let mut result_ids = ptr::null_mut(); - let mut result_count = 0; - unsafe { - BNTypeContainerAddTypes( - self.as_raw(), - names_raw.as_ptr(), - types_raw.as_mut_ptr(), - types.len(), - Some(cb_progress_nop), - ptr::null_mut(), - &mut result_names, - &mut result_ids, - &mut result_count, - ) +impl TypeParserError { + pub fn new( + severity: TypeParserErrorSeverity, + message: String, + file_name: String, + line: u64, + column: u64, + ) -> Self { + Self { + severity, + message, + file_name, + line, + column, } } +} - pub fn add_types_with_progress(&self, types: I, mut progress: F) -> bool - where - I: IntoIterator)>, - F: FnMut(usize, usize) -> bool, - { - let (names, types): (Vec, Vec>) = types.into_iter().unzip(); - - // SAFETY QualifiedName is transparent with BNQualifiedName - let names_raw: &[BNQualifiedName] = unsafe { mem::transmute(names.as_slice()) }; - - let mut types_raw: Vec<*mut BNType> = types.iter().map(|t| t.handle).collect(); - - let mut result_names = ptr::null_mut(); - let mut result_ids = ptr::null_mut(); - let mut result_count = 0; - unsafe { - BNTypeContainerAddTypes( - self.as_raw(), - names_raw.as_ptr(), - types_raw.as_mut_ptr(), - types.len(), - Some(cb_progress::), - &mut progress as *mut F as *mut ffi::c_void, - &mut result_names, - &mut result_ids, - &mut result_count, - ) +impl From for TypeParserError { + fn from(value: BNTypeParserError) -> Self { + Self { + severity: value.severity, + message: unsafe { BnString::from_raw(value.message).to_string() }, + file_name: unsafe { BnString::from_raw(value.fileName).to_string() }, + line: value.line, + column: value.column, } } +} - /// Rename a type in the Type Container. All references to this type will be updated - /// (by id) to use the new name. - pub fn rename_type(&self, name: &QualifiedName, type_id: S) -> bool { - let type_id = type_id.into_bytes_with_nul(); - unsafe { - BNTypeContainerRenameType( - self.as_raw(), - type_id.as_ref().as_ptr() as *const ffi::c_char, - &name.0, - ) +impl From<&BNTypeParserError> for TypeParserError { + fn from(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, } } +} - /// 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. - pub fn delete_type(&self, type_id: S) -> bool { - let type_id = type_id.into_bytes_with_nul(); - unsafe { - BNTypeContainerDeleteType( - self.as_raw(), - type_id.as_ref().as_ptr() as *const ffi::c_char, - ) +impl From for BNTypeParserError { + fn from(value: TypeParserError) -> Self { + Self { + severity: value.severity, + message: BnString::new(value.message).into_raw(), + fileName: BnString::new(value.file_name).into_raw(), + line: value.line, + column: value.column, } } +} - /// Get the unique id of the type in the Type Container with the given name. - /// If no type with that name exists, returns None. - pub fn type_id(&self, type_name: &QualifiedName) -> Option { - let mut result = ptr::null_mut(); - let success = unsafe { BNTypeContainerGetTypeId(self.as_raw(), &type_name.0, &mut result) }; - success.then(|| unsafe { BnString::from_raw(result) }) - } +impl CoreArrayProvider for TypeParserError { + type Raw = BNTypeParserError; + type Context = (); + type Wrapped<'a> = Self; +} - /// 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: S) -> Option { - let type_id = type_id.into_bytes_with_nul(); - let mut result = BNQualifiedName::default(); - let success = unsafe { - BNTypeContainerGetTypeName( - self.as_raw(), - type_id.as_ref().as_ptr() as *const ffi::c_char, - &mut result, - ) - }; - success.then(|| QualifiedName(result)) +unsafe impl CoreArrayProviderInner for TypeParserError { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + unsafe { BNFreeTypeParserErrors(raw, count) } } - /// 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: S) -> Option> { - let type_id = type_id.into_bytes_with_nul(); - let mut result = ptr::null_mut(); - let success = unsafe { - BNTypeContainerGetTypeById( - self.as_raw(), - type_id.as_ref().as_ptr() as *const ffi::c_char, - &mut result, - ) - }; - success.then(|| unsafe { Type::ref_from_raw(result) }) + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + Self::from(raw) } +} - /// Get the definition of the type in the Type Container with the given name. - /// If no type with that name exists, returns None. - pub fn type_by_name(&self, type_name: &QualifiedName) -> Option> { - let mut result = ptr::null_mut(); - let success = - unsafe { BNTypeContainerGetTypeByName(self.as_raw(), &type_name.0, &mut result) }; - success.then(|| unsafe { Type::ref_from_raw(result) }) - } +#[derive(Debug, Eq, PartialEq, Default)] +pub struct TypeParserResult { + pub types: Vec, + pub variables: Vec, + pub functions: Vec, +} - /// Get a mapping of all types in a Type Container. - pub fn types(&self) -> Option<(Array, Array, Array)> { - let mut type_ids = ptr::null_mut(); - let mut type_names = ptr::null_mut(); - let mut type_types = ptr::null_mut(); - let mut type_count = 0; - let success = unsafe { - BNTypeContainerGetTypes( - self.as_raw(), - &mut type_ids, - &mut type_names, - &mut type_types, - &mut type_count, - ) +impl From for TypeParserResult { + fn from(mut value: BNTypeParserResult) -> Self { + let raw_types = unsafe { std::slice::from_raw_parts(value.types, value.typeCount) }; + let raw_variables = + unsafe { std::slice::from_raw_parts(value.variables, value.variableCount) }; + let raw_functions = + unsafe { std::slice::from_raw_parts(value.functions, value.functionCount) }; + let result = TypeParserResult { + types: raw_types.iter().map(|t| ParsedType::from(t)).collect(), + variables: raw_variables.iter().map(|t| ParsedType::from(t)).collect(), + functions: raw_functions.iter().map(|t| ParsedType::from(t)).collect(), }; - success.then(|| unsafe { - let ids = Array::new(type_ids, type_count, ()); - let names = Array::new(type_names, type_count, ()); - let types = Array::new(type_types, type_count, ()); - (types, names, ids) - }) - } - - /// Get all type ids in a Type Container. - pub fn type_ids(&self) -> Option> { - let mut type_ids = ptr::null_mut(); - let mut type_count = 0; - let success = - unsafe { BNTypeContainerGetTypeIds(self.as_raw(), &mut type_ids, &mut type_count) }; - success.then(|| unsafe { Array::new(type_ids, type_count, ()) }) - } - - /// Get all type names in a Type Container. - pub fn type_names(&self) -> Option> { - let mut type_ids = ptr::null_mut(); - let mut type_count = 0; - let success = - unsafe { BNTypeContainerGetTypeNames(self.as_raw(), &mut type_ids, &mut type_count) }; - success.then(|| unsafe { Array::new(type_ids, type_count, ()) }) + // SAFETY: `value` must be a properly initialized BNTypeParserResult. + unsafe { BNFreeTypeParserResult(&mut value) }; + result } +} - /// Get a mapping of all type ids and type names in a Type Container. - pub fn type_names_and_ids(&self) -> Option<(Array, Array)> { - let mut type_ids = ptr::null_mut(); - let mut type_names = ptr::null_mut(); - let mut type_count = 0; - let success = unsafe { - BNTypeContainerGetTypeNamesAndIds( - self.as_raw(), - &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) - }) - } +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct ParsedType { + name: QualifiedName, + ty: Ref, + user: bool, +} - /// 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: S, - import_dependencies: bool, - ) -> Result> { - let source = source.into_bytes_with_nul(); - let mut result = BNQualifiedNameAndType::default(); - let mut errors = ptr::null_mut(); - let mut error_count = 0; - let success = unsafe { - BNTypeContainerParseTypeString( - self.as_raw(), - source.as_ref().as_ptr() as *const ffi::c_char, - import_dependencies, - &mut result, - &mut errors, - &mut error_count, - ) - }; - if success { - Ok(QualifiedNameAndType(result)) - } else { - assert!(!errors.is_null()); - Err(unsafe { Array::new(errors, error_count, ()) }) - } +impl ParsedType { + pub fn new(name: QualifiedName, ty: Ref, user: bool) -> Self { + Self { name, ty, user } } +} - /// Parse an entire block of source into types, variables, and functions, with - /// knowledge of the types in the Type Container. - - /// * `source` - Source code to parse - /// * `file_name` - Name of the file containing the source (optional: exists on disk) - /// * `options` - String arguments to pass as options, e.g. command line arguments - /// * `include_dirs` - List of directories to include in the header search path - /// * `auto_type_source` - Source of types if used for automatically generated types - /// * `import_dependencies` - If Type Library / Type Archive types should be imported during parsing - pub fn parse_types_from_source( - &self, - source: S, - filename: F, - options: O, - include_directories: D, - auto_type_source: A, - import_dependencies: bool, - ) -> Result> - where - S: BnStrCompatible, - F: BnStrCompatible, - O: IntoIterator, - O::Item: BnStrCompatible, - D: IntoIterator, - D::Item: BnStrCompatible, - A: BnStrCompatible, - { - let source = source.into_bytes_with_nul(); - let filename = filename.into_bytes_with_nul(); - let options: Vec<_> = options - .into_iter() - .map(|o| o.into_bytes_with_nul()) - .collect(); - let options_raw: Vec<*const ffi::c_char> = options - .iter() - .map(|o| o.as_ref().as_ptr() as *const ffi::c_char) - .collect(); - let include_directories: Vec<_> = include_directories - .into_iter() - .map(|d| d.into_bytes_with_nul()) - .collect(); - let include_directories_raw: Vec<*const ffi::c_char> = include_directories - .iter() - .map(|d| d.as_ref().as_ptr() as *const ffi::c_char) - .collect(); - let auto_type_source = auto_type_source.into_bytes_with_nul(); - let mut result = BNTypeParserResult::default(); - let mut errors = ptr::null_mut(); - let mut error_count = 0; - let success = unsafe { - BNTypeContainerParseTypesFromSource( - self.as_raw(), - source.as_ref().as_ptr() as *const ffi::c_char, - filename.as_ref().as_ptr() as *const ffi::c_char, - options_raw.as_ptr(), - options_raw.len(), - include_directories_raw.as_ptr(), - include_directories_raw.len(), - auto_type_source.as_ref().as_ptr() as *const ffi::c_char, - import_dependencies, - &mut result, - &mut errors, - &mut error_count, - ) - }; - if success { - Ok(unsafe { TypeParserResult::from_raw(result) }) - } else { - assert!(!errors.is_null()); - Err(unsafe { Array::new(errors, error_count, ()) }) +impl From for ParsedType { + fn from(value: BNParsedType) -> Self { + Self { + name: value.name.into(), + ty: unsafe { Type::ref_from_raw(value.type_) }, + user: value.isUser, } } } -impl Drop for TypeContainer { - fn drop(&mut self) { - unsafe { BNFreeTypeContainer(self.as_raw()) } +impl From<&BNParsedType> for ParsedType { + fn from(value: &BNParsedType) -> Self { + Self { + name: QualifiedName::from(&value.name), + ty: unsafe { Type::from_raw(value.type_).to_owned() }, + user: value.isUser, + } } } -impl Clone for TypeContainer { - fn clone(&self) -> Self { - unsafe { - Self::from_raw(ptr::NonNull::new(BNDuplicateTypeContainer(self.as_raw())).unwrap()) +impl From for BNParsedType { + fn from(value: ParsedType) -> Self { + Self { + name: value.name.into(), + type_: value.ty.handle, + isUser: value.user, } } } -#[repr(transparent)] -pub struct CoreTypePrinter { - handle: ptr::NonNull, +impl CoreArrayProvider for ParsedType { + type Raw = BNParsedType; + type Context = (); + type Wrapped<'b> = Self; } -impl CoreTypePrinter { - pub(crate) unsafe fn from_raw(handle: ptr::NonNull) -> CoreTypePrinter { - Self { handle } +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 } - pub(crate) unsafe fn ref_from_raw(handle: &*mut BNTypePrinter) -> &Self { - mem::transmute(handle) + unsafe fn wrap_raw<'b>(raw: &'b Self::Raw, _context: &'b Self::Context) -> Self::Wrapped<'b> { + ParsedType::from(raw) } +} - #[allow(clippy::mut_from_ref)] - pub(crate) unsafe fn as_raw(&self) -> &mut BNTypePrinter { - &mut *self.handle.as_ptr() +unsafe extern "C" fn cb_get_option_text( + ctxt: *mut ::std::os::raw::c_void, + option: BNTypeParserOption, + value: *const c_char, + result: *mut *mut c_char, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + if let Some(inner_result) = ctxt.get_option_text(option, &raw_to_string(value).unwrap()) { + let bn_inner_result = BnString::new(inner_result); + // NOTE: Dropped by `cb_free_string` + *result = bn_inner_result.into_raw(); + true + } else { + *result = std::ptr::null_mut(); + false } +} - pub fn printers() -> Array { - let mut count = 0; - let result = unsafe { BNGetTypePrinterList(&mut count) }; - assert!(!result.is_null()); - unsafe { Array::new(result, count, ()) } +unsafe extern "C" fn cb_preprocess_source( + ctxt: *mut c_void, + source: *const c_char, + file_name: *const c_char, + platform: *mut BNPlatform, + existing_types: *mut BNTypeContainer, + options: *const *const c_char, + option_count: usize, + include_dirs: *const *const c_char, + include_dir_count: usize, + result: *mut *mut c_char, + errors: *mut *mut BNTypeParserError, + error_count: *mut usize, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let platform = Platform { handle: platform }; + let existing_types_ptr = NonNull::new(existing_types).unwrap(); + let existing_types = TypeContainer::from_raw(existing_types_ptr); + let options_raw = unsafe { std::slice::from_raw_parts(options, option_count) }; + let options: Vec<_> = options_raw + .iter() + .filter_map(|&r| raw_to_string(r)) + .collect(); + let includes_raw = unsafe { std::slice::from_raw_parts(include_dirs, include_dir_count) }; + let includes: Vec<_> = includes_raw + .iter() + .filter_map(|&r| raw_to_string(r)) + .collect(); + match ctxt.preprocess_source( + &raw_to_string(source).unwrap(), + &raw_to_string(file_name).unwrap(), + &platform, + &existing_types, + &options, + &includes, + ) { + Ok(inner_result) => { + let bn_inner_result = BnString::new(inner_result); + // NOTE: Dropped by `cb_free_string` + *result = bn_inner_result.into_raw(); + *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(Into::into).collect(); + *result = std::ptr::null_mut(); + // NOTE: Dropped by `cb_free_error_list` + *errors = Box::leak(inner_errors).as_mut_ptr(); + false + } } +} - pub fn printer_by_name(name: S) -> Option { - let name_raw = name.into_bytes_with_nul(); - let result = - unsafe { BNGetTypePrinterByName(name_raw.as_ref().as_ptr() as *const ffi::c_char) }; - ptr::NonNull::new(result).map(|x| unsafe { Self::from_raw(x) }) +unsafe extern "C" fn cb_parse_types_from_source( + ctxt: *mut c_void, + source: *const c_char, + file_name: *const c_char, + platform: *mut BNPlatform, + existing_types: *mut BNTypeContainer, + options: *const *const c_char, + option_count: usize, + include_dirs: *const *const c_char, + include_dir_count: usize, + auto_type_source: *const c_char, + result: *mut BNTypeParserResult, + errors: *mut *mut BNTypeParserError, + error_count: *mut usize, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let platform = Platform { handle: platform }; + let existing_types_ptr = NonNull::new(existing_types).unwrap(); + let existing_types = TypeContainer::from_raw(existing_types_ptr); + let options_raw = unsafe { std::slice::from_raw_parts(options, option_count) }; + let options: Vec<_> = options_raw + .iter() + .filter_map(|&r| raw_to_string(r)) + .collect(); + let includes_raw = unsafe { std::slice::from_raw_parts(include_dirs, include_dir_count) }; + let includes: Vec<_> = includes_raw + .iter() + .filter_map(|&r| raw_to_string(r)) + .collect(); + match ctxt.parse_types_from_source( + &raw_to_string(source).unwrap(), + &raw_to_string(file_name).unwrap(), + &platform, + &existing_types, + &options, + &includes, + &raw_to_string(auto_type_source).unwrap(), + ) { + Ok(type_parser_result) => { + let boxed_raw_types: Box<[BNParsedType]> = type_parser_result + .types + .into_iter() + .map(Into::into) + .collect(); + let boxed_raw_variables: Box<[BNParsedType]> = type_parser_result + .variables + .into_iter() + .map(Into::into) + .collect(); + let boxed_raw_functions: Box<[BNParsedType]> = type_parser_result + .functions + .into_iter() + .map(Into::into) + .collect(); + let type_count = boxed_raw_types.len(); + let variable_count = boxed_raw_variables.len(); + let function_count = boxed_raw_functions.len(); + let raw_result = BNTypeParserResult { + // NOTE: Freed with `cb_free_result`. + types: Box::leak(boxed_raw_types).as_mut_ptr(), + // NOTE: Freed with `cb_free_result`. + variables: Box::leak(boxed_raw_variables).as_mut_ptr(), + // NOTE: Freed with `cb_free_result`. + functions: Box::leak(boxed_raw_functions).as_mut_ptr(), + typeCount: type_count, + variableCount: variable_count, + functionCount: function_count, + }; + *result = raw_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(Into::into).collect(); + *result = Default::default(); + // NOTE: Dropped by cb_free_error_list + *errors = Box::leak(inner_errors).as_mut_ptr(); + false + } } - - pub fn name(&self) -> BnString { - let result = unsafe { BNGetTypePrinterName(self.as_raw()) }; - assert!(!result.is_null()); - unsafe { BnString::from_raw(result) } - } - - pub fn get_type_tokens( - &self, - type_: &Type, - platform: &Platform, - name: &QualifiedName, - base_confidence: u8, - escaping: TokenEscapingType, - ) -> Option> { - let mut result_count = 0; - let mut result = ptr::null_mut(); - let success = unsafe { - BNGetTypePrinterTypeTokens( - self.as_raw(), - type_.handle, - platform.handle, - &name.0 as *const _ as *mut _, - base_confidence, - escaping, - &mut result, - &mut result_count, - ) - }; - success.then(|| { - assert!(!result.is_null()); - unsafe { Array::new(result, result_count, ()) } - }) - } - - pub fn get_type_tokens_before_name( - &self, - type_: &Type, - platform: &Platform, - base_confidence: u8, - parent_type: &Type, - escaping: TokenEscapingType, - ) -> Option> { - let mut result_count = 0; - let mut result = ptr::null_mut(); - let success = unsafe { - BNGetTypePrinterTypeTokensBeforeName( - self.as_raw(), - type_.handle, - platform.handle, - base_confidence, - parent_type.handle, - escaping, - &mut result, - &mut result_count, - ) - }; - success.then(|| { - assert!(!result.is_null()); - unsafe { Array::new(result, result_count, ()) } - }) - } - - pub fn get_type_tokens_after_name( - &self, - type_: &Type, - platform: &Platform, - base_confidence: u8, - parent_type: &Type, - escaping: TokenEscapingType, - ) -> Option> { - let mut result_count = 0; - let mut result = ptr::null_mut(); - let success = unsafe { - BNGetTypePrinterTypeTokensAfterName( - self.as_raw(), - type_.handle, - platform.handle, - base_confidence, - parent_type.handle, - escaping, - &mut result, - &mut result_count, - ) - }; - success.then(|| { - assert!(!result.is_null()); - unsafe { Array::new(result, result_count, ()) } - }) - } - - pub fn get_type_string( - &self, - type_: &Type, - platform: &Platform, - name: &QualifiedName, - escaping: TokenEscapingType, - ) -> Option { - let mut result = ptr::null_mut(); - let success = unsafe { - BNGetTypePrinterTypeString( - self.as_raw(), - type_.handle, - platform.handle, - &name.0 as *const _ as *mut _, - escaping, - &mut result, - ) - }; - success.then(|| unsafe { - assert!(!result.is_null()); - BnString::from_raw(result) - }) - } - - pub fn get_type_string_before_name( - &self, - type_: &Type, - platform: &Platform, - escaping: BNTokenEscapingType, - ) -> Option { - let mut result = ptr::null_mut(); - let success = unsafe { - BNGetTypePrinterTypeStringAfterName( - self.as_raw(), - type_.handle, - platform.handle, - escaping, - &mut result, - ) - }; - success.then(|| unsafe { - assert!(!result.is_null()); - BnString::from_raw(result) - }) - } - - pub fn get_type_string_after_name( - &self, - type_: &Type, - platform: &Platform, - escaping: TokenEscapingType, - ) -> Option { - let mut result = ptr::null_mut(); - let success = unsafe { - BNGetTypePrinterTypeStringBeforeName( - self.as_raw(), - type_.handle, - platform.handle, - escaping, - &mut result, - ) - }; - success.then(|| unsafe { - assert!(!result.is_null()); - BnString::from_raw(result) - }) - } - - pub fn get_type_lines( - &self, - type_: &Type, - types: &TypeContainer, - name: &QualifiedName, - padding_cols: ffi::c_int, - collapsed: bool, - escaping: TokenEscapingType, - ) -> Option> { - let mut result_count = 0; - let mut result = ptr::null_mut(); - let success = unsafe { - BNGetTypePrinterTypeLines( - self.as_raw(), - type_.handle, - types.as_raw(), - &name.0 as *const BNQualifiedName as *mut BNQualifiedName, - padding_cols, - collapsed, - escaping, - &mut result, - &mut result_count, - ) - }; - success.then(|| { - assert!(!result.is_null()); - unsafe { Array::::new(result, result_count, ()) } - }) - } - - /// Print all types to a single big string, including headers, sections, etc - /// - /// * `types` - All types to print - /// * `data` - Binary View in which all the types are defined - /// * `padding_cols` - Maximum number of bytes represented by each padding line - /// * `escaping` - Style of escaping literals which may not be parsable - pub fn default_print_all_types( - &self, - types: &[QualifiedNameAndType], - data: &BinaryView, - padding_cols: ffi::c_int, - escaping: TokenEscapingType, - ) -> Option { - let mut result = ptr::null_mut(); - let mut types_raw: Vec<*mut BNType> = types - .iter() - .map(|t| t.type_object().as_ref().handle) - .collect(); - let mut names_raw: Vec = types.iter().map(|t| t.name().0).collect(); - let success = unsafe { - BNTypePrinterDefaultPrintAllTypes( - self.as_raw(), - names_raw.as_mut_ptr(), - types_raw.as_mut_ptr(), - types.len(), - data.handle, - padding_cols, - escaping, - &mut result, - ) - }; - success.then(|| unsafe { - assert!(!result.is_null()); - BnString::from_raw(result) - }) - } - - pub fn print_all_types( - &self, - types: &[QualifiedNameAndType], - data: &BinaryView, - padding_cols: ffi::c_int, - escaping: TokenEscapingType, - ) -> Option { - let mut result = ptr::null_mut(); - let mut types_raw: Vec<*mut BNType> = types - .iter() - .map(|t| t.type_object().as_ref().handle) - .collect(); - let mut names_raw: Vec = types.iter().map(|t| t.name().0).collect(); - let success = unsafe { - BNTypePrinterPrintAllTypes( - self.as_raw(), - names_raw.as_mut_ptr(), - types_raw.as_mut_ptr(), - types.len(), - data.handle, - padding_cols, - escaping, - &mut result, - ) - }; - success.then(|| unsafe { - assert!(!result.is_null()); - BnString::from_raw(result) - }) - } -} - -impl Default for CoreTypePrinter { - fn default() -> Self { - let default_settings = crate::settings::Settings::default(); - let name = default_settings.get_string( - ffi::CStr::from_bytes_with_nul(b"analysis.types.printerName\x00").unwrap(), - None, - None, - ); - Self::printer_by_name(name).unwrap() - } -} - -impl CoreArrayProvider for CoreTypePrinter { - type Raw = *mut BNTypePrinter; - type Context = (); - type Wrapped<'a> = &'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> { - CoreTypePrinter::ref_from_raw(raw) - } -} - -pub trait TypePrinter { - /// Generate a single-line text representation of a type, Returns a List - /// of text tokens representing the type. - /// - /// * `type_` - Type to print - /// * `platform` - Platform responsible for this type - /// * `name` - Name of the type - /// * `base_confidence` - Confidence to use for tokens created for this type - /// * `escaping` - Style of escaping literals which may not be parsable - fn get_type_tokens( - &self, - type_: &Type, - platform: &Platform, - name: &QualifiedName, - base_confidence: u8, - escaping: TokenEscapingType, - ) -> Option>; - - /// In a single-line text representation of a type, generate the tokens that - /// should be printed before the type's name. Returns a list of text tokens - /// representing the type - /// - /// * `type_` - Type to print - /// * `platform` - Platform responsible for this type - /// * `base_confidence` - Confidence to use for tokens created for this type - /// * `parent_type` - Type of the parent of this type, or None - /// * `escaping` - Style of escaping literals which may not be parsable - fn get_type_tokens_before_name( - &self, - type_: &Type, - platform: &Platform, - base_confidence: u8, - parent_type: &Type, - escaping: TokenEscapingType, - ) -> Option>; - - /// In a single-line text representation of a type, generate the tokens - /// that should be printed after the type's name. Returns a ist 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_: &Type, - platform: &Platform, - base_confidence: u8, - parent_type: &Type, - escaping: TokenEscapingType, - ) -> Option>; - - /// Generate a single-line text representation of a type. Returns a string - /// representing the type - /// - /// * `type_` - Type to print - /// * `platform` - Platform responsible for this type - /// * `name` - Name of the type - /// * `escaping` - Style of escaping literals which may not be parsable - fn get_type_string( - &self, - type_: &Type, - platform: &Platform, - name: &QualifiedName, - escaping: TokenEscapingType, - ) -> Option; - - /// In a single-line text representation of a type, generate the string that - /// should be printed before the type's name. Returns a string representing - /// the type - /// - /// * `type_` - Type to print - /// * `platform` - Platform responsible for this type - /// * `escaping` - Style of escaping literals which may not be parsable - fn get_type_string_before_name( - &self, - type_: &Type, - platform: &Platform, - escaping: TokenEscapingType, - ) -> Option; - - /// In a single-line text representation of a type, generate the string that - /// should be printed after the type's name. Returns a string representing - /// the type - /// - /// * `type_` - Type to print - /// * `platform` - Platform responsible for this type - /// * `escaping` - Style of escaping literals which may not be parsable - fn get_type_string_after_name( - &self, - type_: &Type, - platform: &Platform, - escaping: TokenEscapingType, - ) -> Option; - - /// Generate a multi-line representation of a type. Returns a list of type - /// definition lines - /// - /// * `type_` - Type to print - /// * `types` - Type Container containing the type and dependencies - /// * `name` - Name of the type - /// * `padding_cols` - Maximum number of bytes represented by each padding line - /// * `collapsed` - Whether to collapse structure/enum blocks - /// * `escaping` - Style of escaping literals which may not be parsable - fn get_type_lines( - &self, - type_: &Type, - types: &TypeContainer, - name: &QualifiedName, - padding_cols: ffi::c_int, - collapsed: bool, - escaping: TokenEscapingType, - ) -> Option>; - - /// Print all types to a single big string, including headers, sections, - /// etc. - /// - /// * `types` - All types to print - /// * `data` - Binary View in which all the types are defined - /// * `padding_cols` - Maximum number of bytes represented by each padding line - /// * `escaping` - Style of escaping literals which may not be parsable - fn print_all_types( - &self, - names: &QualifiedName, - types: &[Type], - data: &BinaryView, - padding_cols: ffi::c_int, - escaping: TokenEscapingType, - ) -> Option; -} - -/// Register a custom parser with the API -pub fn register_type_printer( - name: S, - parser: T, -) -> (&'static mut T, CoreTypePrinter) { - let parser = Box::leak(Box::new(parser)); - let mut callback = BNTypePrinterCallbacks { - context: parser as *mut _ as *mut ffi::c_void, - getTypeTokens: Some(cb_get_type_tokens::), - getTypeTokensBeforeName: Some(cb_get_type_tokens_before_name::), - getTypeTokensAfterName: Some(cb_get_type_tokens_after_name::), - getTypeString: Some(cb_get_type_string::), - getTypeStringBeforeName: Some(cb_get_type_string_before_name::), - getTypeStringAfterName: Some(cb_get_type_string_after_name::), - getTypeLines: Some(cb_get_type_lines::), - printAllTypes: Some(cb_print_all_types::), - freeTokens: Some(cb_free_tokens), - freeString: Some(cb_free_string), - freeLines: Some(cb_free_lines), - }; - let result = unsafe { - BNRegisterTypePrinter( - name.into_bytes_with_nul().as_ref().as_ptr() as *const ffi::c_char, - &mut callback, - ) - }; - let core = unsafe { CoreTypePrinter::from_raw(ptr::NonNull::new(result).unwrap()) }; - (parser, core) -} - -#[repr(C)] -#[derive(Clone)] -pub struct TypeParserError { - pub severity: TypeParserErrorSeverity, - pub message: BnString, - pub file_name: BnString, - pub line: u64, - pub column: u64, -} - -impl TypeParserError { - pub fn new( - severity: TypeParserErrorSeverity, - message: M, - file_name: F, - line: u64, - column: u64, - ) -> Self { - Self { - severity, - message: BnString::new(message), - file_name: BnString::new(file_name), - line, - column, - } - } - - pub(crate) unsafe fn ref_from_raw(handle: &BNTypeParserError) -> &Self { - assert!(!handle.message.is_null()); - assert!(!handle.fileName.is_null()); - mem::transmute(handle) - } -} - -impl core::fmt::Debug for TypeParserError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let severity = match self.severity { - BNTypeParserErrorSeverity::IgnoredSeverity => "ignored", - BNTypeParserErrorSeverity::NoteSeverity => "note", - BNTypeParserErrorSeverity::RemarkSeverity => "remark", - BNTypeParserErrorSeverity::WarningSeverity => "warning", - BNTypeParserErrorSeverity::FatalSeverity | BNTypeParserErrorSeverity::ErrorSeverity => { - "error" - } - }; - let message = if self.file_name.as_str() == "" { - self.message.as_str().to_owned() - } else { - format!( - "{}: {}:{} {}", - self.file_name.as_str(), - self.line, - self.column, - self.message - ) - }; - f.debug_struct("TypeParserError") - .field("severity", &severity) - .field("message", &message) - .finish() - } -} - -impl CoreArrayProvider for TypeParserError { - type Raw = BNTypeParserError; - type Context = (); - type Wrapped<'a> = &'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::ref_from_raw(raw) - } -} - -/// The user created version of TypeParserResult -#[derive(Debug, Clone, Default)] -pub struct TypeParserResultUser { - pub types: Vec, - pub variables: Vec, - pub functions: Vec, -} - -impl TypeParserResultUser { - fn into_user_raw(self) -> BNTypeParserResult { - let Self { - types, - variables, - functions, - } = self; - let types = Box::leak(types.into_iter().map(|t| unsafe { t.into_raw() }).collect()); - let variables = Box::leak( - variables - .into_iter() - .map(|t| unsafe { t.into_raw() }) - .collect(), - ); - let functions = Box::leak( - functions - .into_iter() - .map(|t| unsafe { t.into_raw() }) - .collect(), - ); - BNTypeParserResult { - types: types.as_mut_ptr(), - typeCount: types.len(), - variables: variables.as_mut_ptr(), - variableCount: variables.len(), - functions: functions.as_mut_ptr(), - functionCount: functions.len(), - } - } - - /// SAFETY only can be used with products from [TypeParserResultUser::into_user_raw] - unsafe fn from_user_raw(value: BNTypeParserResult) -> Self { - let from_raw = |raw, count| { - Box::from_raw(ptr::slice_from_raw_parts_mut(raw, count)) - .into_iter() - .map(|t| ParsedType::from_raw(t)) - .collect() - }; - Self { - types: from_raw(value.types, value.typeCount), - variables: from_raw(value.variables, value.variableCount), - functions: from_raw(value.functions, value.functionCount), - } - } -} - -pub struct TypeParserResult(BNTypeParserResult); - -impl TypeParserResult { - pub(crate) unsafe fn from_raw(value: BNTypeParserResult) -> Self { - Self(value) - } - - fn as_raw(&mut self) -> &mut BNTypeParserResult { - &mut self.0 - } - - pub fn types(&self) -> ArrayGuard<&ParsedType> { - unsafe { ArrayGuard::new(self.0.types, self.0.typeCount, &()) } - } - - pub fn variables(&self) -> ArrayGuard<&ParsedType> { - unsafe { ArrayGuard::new(self.0.variables, self.0.variableCount, &()) } - } - - pub fn functions(&self) -> ArrayGuard<&ParsedType> { - unsafe { ArrayGuard::new(self.0.functions, self.0.functionCount, &()) } - } -} - -impl Drop for TypeParserResult { - fn drop(&mut self) { - unsafe { BNFreeTypeParserResult(self.as_raw()) } - } -} - -#[derive(Debug, Clone)] -pub struct ParsedType { - name: QualifiedName, - type_: Ref, - is_user: bool, -} - -impl ParsedType { - pub fn new(name: QualifiedName, type_: Ref, is_user: bool) -> Self { - Self { - name, - type_, - is_user, - } - } - - pub(crate) unsafe fn from_raw(parsed: &BNParsedType) -> Self { - Self { - name: QualifiedName(parsed.name), - type_: Type::ref_from_raw(parsed.type_), - is_user: parsed.isUser, - } - } - - pub(crate) unsafe fn clone_from_raw(parsed: &BNParsedType) -> Self { - let name = mem::ManuallyDrop::new(QualifiedName(parsed.name)); - let type_ = Type { - handle: parsed.type_, - } - .to_owned(); - let user = parsed.isUser; - ParsedType::new((&*name).clone(), type_, user) - } - - pub(crate) unsafe fn into_raw(self) -> BNParsedType { - let Self { - name, - type_, - is_user, - } = self; - BNParsedType { - name: mem::ManuallyDrop::new(name).0, - type_: Ref::into_raw(type_).handle, - isUser: is_user, - } - } - - pub fn name(&self) -> &QualifiedName { - &self.name - } - - pub fn ty(&self) -> &Type { - &self.type_ - } - - pub fn is_user(&self) -> bool { - self.is_user - } -} - -impl<'a> CoreArrayProvider for &'a ParsedType { - type Raw = BNParsedType; - type Context = &'a (); - type Wrapped<'b> = ParsedType where 'a: 'b; -} - -unsafe impl<'a> CoreArrayProviderInner for &'a ParsedType { - // ParsedType can only by used in a ArrayGuard - unsafe fn free(_raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { - unreachable!() - } - - unsafe fn wrap_raw<'b>(raw: &'b Self::Raw, _context: &'b Self::Context) -> Self::Wrapped<'b> { - ParsedType::clone_from_raw(raw) - } -} - -#[derive(Clone)] -pub struct TypeDefinitionLine { - pub line_type: TypeDefinitionLineType, - pub tokens: Vec, - pub type_: Ref, - pub parent_type: Ref, - pub root_type: Ref, - pub root_type_name: BnString, - pub base_type: Ref, - pub base_offset: u64, - pub offset: u64, - pub field_index: usize, -} - -impl TypeDefinitionLine { - pub(crate) unsafe fn clone_from_raw(value: &BNTypeDefinitionLine) -> Self { - Self { - line_type: value.lineType, - tokens: unsafe { core::slice::from_raw_parts(value.tokens, value.count) } - .iter() - .map(|i| InstructionTextToken::from_raw(i).to_owned()) - .collect(), - type_: Type { - handle: value.type_, - } - .to_owned(), - parent_type: Type { - handle: value.parentType, - } - .to_owned(), - root_type: Type { - handle: value.rootType, - } - .to_owned(), - root_type_name: unsafe { BnString::new(ffi::CStr::from_ptr(value.rootTypeName)) }, - base_type: NamedTypeReference::from_raw(value.baseType).to_owned(), - base_offset: value.baseOffset, - offset: value.offset, - field_index: value.fieldIndex, - } - } - - fn into_raw(self) -> TypeDefinitionLineRaw { - let TypeDefinitionLine { - line_type, - tokens, - type_, - parent_type, - root_type, - root_type_name, - base_type, - base_offset, - offset, - field_index, - } = self; - let tokens = Box::leak(tokens.into_boxed_slice()); - // SAFETY BNInstructionTextToken and InstructionTextToken are transparent - let tokens_ptr = tokens.as_mut_ptr() as *mut BNInstructionTextToken; - TypeDefinitionLineRaw(BNTypeDefinitionLine { - lineType: line_type, - tokens: tokens_ptr, - count: tokens.len(), - type_: unsafe { Ref::into_raw(type_) }.handle, - parentType: unsafe { Ref::into_raw(parent_type) }.handle, - rootType: unsafe { Ref::into_raw(root_type) }.handle, - rootTypeName: root_type_name.into_raw(), - baseType: unsafe { Ref::into_raw(base_type) }.handle, - baseOffset: base_offset, - offset: offset, - fieldIndex: field_index, - }) - } -} - -#[repr(transparent)] -struct TypeDefinitionLineRaw(BNTypeDefinitionLine); - -impl Drop for TypeDefinitionLineRaw { - fn drop(&mut self) { - let _tokens = unsafe { - Box::from_raw(ptr::slice_from_raw_parts_mut( - self.0.tokens as *mut InstructionTextToken, - self.0.count, - )) - }; - let _type = unsafe { Type::ref_from_raw(self.0.type_) }; - let _parent_type = unsafe { Type::ref_from_raw(self.0.parentType) }; - let _root_type = unsafe { Type::ref_from_raw(self.0.rootType) }; - let _root_type_name = unsafe { BnString::from_raw(self.0.rootTypeName) }; - let _base_type = unsafe { NamedTypeReference::ref_from_raw(self.0.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::clone_from_raw(raw) - } -} - -pub fn options_text(options: O) -> Array { - let options = options.into_bytes_with_nul(); - let mut count = 0; - let result = unsafe { - BNParseTypeParserOptionsText(options.as_ref().as_ptr() as *const ffi::c_char, &mut count) - }; - assert!(!result.is_null()); - unsafe { Array::new(result, count, ()) } -} - -pub fn parser_errors(errors: &[TypeParserError]) -> BnString { - // SAFETY TypeParserError and BNTypeParserError are transparent - let errors: &[BNTypeParserError] = unsafe { mem::transmute(errors) }; - let result = unsafe { BNFormatTypeParserParseErrors(errors.as_ptr() as *mut _, errors.len()) }; - assert!(!result.is_null()); - unsafe { BnString::from_raw(result) } -} - -unsafe extern "C" fn cb_get_option_text( - ctxt: *mut ::std::os::raw::c_void, - option: BNTypeParserOption, - value: *const ffi::c_char, - result: *mut *mut ffi::c_char, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - let value = ffi::CStr::from_ptr(value); - let value = value.to_string_lossy(); - if let Some(inner_result) = ctxt.get_option_text(option, &value) { - // SAFETY dropped by cb_free_string - *result = inner_result.into_raw(); - true - } else { - *result = ptr::null_mut(); - false - } -} - -unsafe extern "C" fn cb_preprocess_source( - ctxt: *mut ffi::c_void, - source: *const ffi::c_char, - file_name: *const ffi::c_char, - platform: *mut BNPlatform, - existing_types: *mut BNTypeContainer, - options: *const *const ffi::c_char, - option_count: usize, - include_dirs: *const *const ffi::c_char, - include_dir_count: usize, - result: *mut *mut ffi::c_char, - errors: *mut *mut BNTypeParserError, - error_count: *mut usize, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - let source = ffi::CStr::from_ptr(source); - let file_name = ffi::CStr::from_ptr(file_name); - let platform = Platform { handle: platform }; - let existing_types = TypeContainer::ref_from_raw(&existing_types); - // SAFETY BnString and *ffi::c_char are transparent - let options: &[BnString] = - core::slice::from_raw_parts(options as *const BnString, option_count); - let include_dirs: &[BnString] = - core::slice::from_raw_parts(include_dirs as *const BnString, include_dir_count); - match ctxt.preprocess_source( - &source.to_string_lossy(), - &file_name.to_string_lossy(), - &platform, - existing_types, - options, - include_dirs, - ) { - Ok(inner_result) => { - // SAFETY drop by the function cb_free_string - *result = inner_result.into_raw(); - *errors = ptr::null_mut(); - *error_count = 0; - true - } - Err(inner_erros) => { - // SAFETY drop by the function cb_free_error_list - let inner_errors = Box::leak(inner_erros.into_boxed_slice()); - // SAFETY: TypeParserError and BNTypeParserError are transparent - *result = ptr::null_mut(); - *errors = inner_errors.as_ptr() as *mut BNTypeParserError; - *error_count = inner_errors.len(); - false - } - } -} - -unsafe extern "C" fn cb_parse_types_from_source( - ctxt: *mut ffi::c_void, - source: *const ffi::c_char, - file_name: *const ffi::c_char, - platform: *mut BNPlatform, - existing_types: *mut BNTypeContainer, - options: *const *const ffi::c_char, - option_count: usize, - include_dirs: *const *const ffi::c_char, - include_dir_count: usize, - auto_type_source: *const ffi::c_char, - result: *mut BNTypeParserResult, - errors: *mut *mut BNTypeParserError, - error_count: *mut usize, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - let source = ffi::CStr::from_ptr(source); - let file_name = ffi::CStr::from_ptr(file_name); - let auto_type_source = ffi::CStr::from_ptr(auto_type_source); - let platform = Platform { handle: platform }; - let existing_types = TypeContainer::ref_from_raw(&existing_types); - // SAFETY BnString and *ffi::c_char are transparent - let options: &[BnString] = - core::slice::from_raw_parts(options as *const BnString, option_count); - let include_dirs: &[BnString] = - core::slice::from_raw_parts(include_dirs as *const BnString, include_dir_count); - match ctxt.parse_types_from_source( - &source.to_string_lossy(), - &file_name.to_string_lossy(), - &platform, - existing_types, - options, - include_dirs, - &auto_type_source.to_string_lossy(), - ) { - Ok(inner_result) => { - let inner_result_ffi = inner_result.into_user_raw(); - // SAFETY drop by the function cb_free_result - *result = inner_result_ffi; - *errors = ptr::null_mut(); - *error_count = 0; - true - } - Err(inner_erros) => { - // SAFETY drop by the function cb_free_error_list - let inner_errors = Box::leak(inner_erros.into_boxed_slice()); - // SAFETY: TypeParserError and BNTypeParserError are transparent - *result = Default::default(); - *errors = inner_errors.as_ptr() as *mut BNTypeParserError; - *error_count = inner_errors.len(); - false - } - } -} +} unsafe extern "C" fn cb_parse_type_string( - ctxt: *mut ffi::c_void, - source: *const ffi::c_char, + ctxt: *mut c_void, + source: *const c_char, platform: *mut BNPlatform, existing_types: *mut BNTypeContainer, result: *mut BNQualifiedNameAndType, @@ -1643,327 +613,41 @@ unsafe extern "C" fn cb_parse_type_string( error_count: *mut usize, ) -> bool { let ctxt: &mut T = &mut *(ctxt as *mut T); - let source = ffi::CStr::from_ptr(source); let platform = Platform { handle: platform }; - let existing_types = TypeContainer::ref_from_raw(&existing_types); - match ctxt.parse_type_string(&source.to_string_lossy(), &platform, existing_types) { + 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) => { - // TODO: SAFETY: dropped by the function ?BNFreeQualifiedNameAndType? - *result = mem::ManuallyDrop::new(inner_result).0; - *errors = ptr::null_mut(); + *result = inner_result.into(); + *errors = std::ptr::null_mut(); *error_count = 0; true } Err(inner_errors) => { - // SAFETY drop by the function cb_free_error_list - let inner_errors = Box::leak(inner_errors.into_boxed_slice()); - // SAFETY: TypeParserError and BNTypeParserError are transparent - *result = Default::default(); - *errors = inner_errors.as_ptr() as *mut BNTypeParserError; *error_count = inner_errors.len(); + let inner_errors: Box<[_]> = inner_errors.into_iter().map(Into::into).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 ffi::c_void, string: *mut ffi::c_char) { - // SAFETY the returned string is just BnString - drop(BnString::from_raw(string)) +unsafe extern "C" fn cb_free_string(_ctxt: *mut c_void, string: *mut c_char) { + // SAFETY: The returned string is just BnString + let _ = BnString::from_raw(string); } -unsafe extern "C" fn cb_free_result(_ctxt: *mut ffi::c_void, result: *mut BNTypeParserResult) { - drop(TypeParserResultUser::from_user_raw(*result)) +unsafe extern "C" fn cb_free_result(_ctxt: *mut c_void, result: *mut BNTypeParserResult) { + let _ = Box::from_raw(result); } unsafe extern "C" fn cb_free_error_list( - _ctxt: *mut ffi::c_void, + _ctxt: *mut c_void, errors: *mut BNTypeParserError, error_count: usize, ) { - // SAFETY TypeParserError and BNTypeParserError are transparent, and error - // originally was an TypeParserError transmuted into BNTypeParserError - let errors = - core::ptr::slice_from_raw_parts_mut(errors, error_count) as *mut [TypeParserError]; - let errors: Box<[TypeParserError]> = Box::from_raw(errors); - drop(errors) -} - -unsafe extern "C" fn cb_get_type_tokens( - ctxt: *mut ::std::os::raw::c_void, - type_: *mut BNType, - platform: *mut BNPlatform, - name: *mut BNQualifiedName, - base_confidence: u8, - escaping: BNTokenEscapingType, - result: *mut *mut BNInstructionTextToken, - result_count: *mut usize, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - let inner_result = ctxt.get_type_tokens( - &Type { handle: type_ }, - &Platform { handle: platform }, - &*mem::ManuallyDrop::new(QualifiedName(*name)), - base_confidence, - escaping, - ); - if let Some(inner_result) = inner_result { - // SAFETY dropped by the cb_free_tokens - let inner_result = Box::leak(inner_result.into_boxed_slice()); - *result_count = inner_result.len(); - // SAFETY InstructionTextToken and BNInstructionTextToken are transparent - let inner_result_ptr = - inner_result.as_ptr() as *mut InstructionTextToken as *mut BNInstructionTextToken; - *result = inner_result_ptr; - true - } else { - *result = ptr::null_mut(); - *result_count = 0; - false - } -} - -unsafe extern "C" fn cb_get_type_tokens_before_name( - ctxt: *mut ::std::os::raw::c_void, - type_: *mut BNType, - platform: *mut BNPlatform, - base_confidence: u8, - parent_type: *mut BNType, - escaping: BNTokenEscapingType, - result: *mut *mut BNInstructionTextToken, - result_count: *mut usize, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - let inner_result = ctxt.get_type_tokens_before_name( - &Type { handle: type_ }, - &Platform { handle: platform }, - base_confidence, - &Type { - handle: parent_type, - }, - escaping, - ); - if let Some(inner_result) = inner_result { - // SAFETY dropped by the cb_free_tokens - let inner_result = Box::leak(inner_result.into_boxed_slice()); - *result_count = inner_result.len(); - // SAFETY InstructionTextToken and BNInstructionTextToken are transparent - let inner_result_ptr = - inner_result.as_ptr() as *mut InstructionTextToken as *mut BNInstructionTextToken; - *result = inner_result_ptr; - true - } else { - *result = ptr::null_mut(); - *result_count = 0; - false - } -} - -unsafe extern "C" fn cb_get_type_tokens_after_name( - ctxt: *mut ::std::os::raw::c_void, - type_: *mut BNType, - platform: *mut BNPlatform, - base_confidence: u8, - parent_type: *mut BNType, - escaping: BNTokenEscapingType, - result: *mut *mut BNInstructionTextToken, - result_count: *mut usize, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - let inner_result = ctxt.get_type_tokens_after_name( - &Type { handle: type_ }, - &Platform { handle: platform }, - base_confidence, - &Type { - handle: parent_type, - }, - escaping, - ); - if let Some(inner_result) = inner_result { - // SAFETY dropped by the cb_free_tokens - let inner_result = Box::leak(inner_result.into_boxed_slice()); - *result_count = inner_result.len(); - // SAFETY InstructionTextToken and BNInstructionTextToken are transparent - let inner_result_ptr = - inner_result.as_ptr() as *mut InstructionTextToken as *mut BNInstructionTextToken; - *result = inner_result_ptr; - true - } else { - *result = ptr::null_mut(); - *result_count = 0; - false - } -} - -unsafe extern "C" fn cb_get_type_string( - ctxt: *mut ::std::os::raw::c_void, - type_: *mut BNType, - platform: *mut BNPlatform, - name: *mut BNQualifiedName, - escaping: BNTokenEscapingType, - result: *mut *mut ::std::os::raw::c_char, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - let inner_result = ctxt.get_type_string( - &Type { handle: type_ }, - &Platform { handle: platform }, - &*mem::ManuallyDrop::new(QualifiedName(*name)), - escaping, - ); - if let Some(inner_result) = inner_result { - // SAFETY dropped by the cb_free_string - *result = inner_result.into_raw(); - true - } else { - *result = ptr::null_mut(); - false - } -} - -unsafe extern "C" fn cb_get_type_string_before_name( - ctxt: *mut ::std::os::raw::c_void, - type_: *mut BNType, - platform: *mut BNPlatform, - escaping: BNTokenEscapingType, - result: *mut *mut ::std::os::raw::c_char, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - let inner_result = ctxt.get_type_string_before_name( - &Type { handle: type_ }, - &Platform { handle: platform }, - escaping, - ); - if let Some(inner_result) = inner_result { - // SAFETY dropped by the cb_free_string - *result = inner_result.into_raw(); - true - } else { - *result = ptr::null_mut(); - false - } -} - -unsafe extern "C" fn cb_get_type_string_after_name( - ctxt: *mut ::std::os::raw::c_void, - type_: *mut BNType, - platform: *mut BNPlatform, - escaping: BNTokenEscapingType, - result: *mut *mut ::std::os::raw::c_char, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - let inner_result = ctxt.get_type_string_after_name( - &Type { handle: type_ }, - &Platform { handle: platform }, - escaping, - ); - if let Some(inner_result) = inner_result { - // SAFETY dropped by the cb_free_string - *result = inner_result.into_raw(); - true - } else { - *result = ptr::null_mut(); - false - } -} - -unsafe extern "C" fn cb_get_type_lines( - ctxt: *mut ::std::os::raw::c_void, - type_: *mut BNType, - types: *mut BNTypeContainer, - name: *mut BNQualifiedName, - padding_cols: ::std::os::raw::c_int, - collapsed: bool, - escaping: BNTokenEscapingType, - result: *mut *mut BNTypeDefinitionLine, - result_count: *mut usize, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - let inner_result = ctxt.get_type_lines( - &Type { handle: type_ }, - TypeContainer::ref_from_raw(&types), - &*mem::ManuallyDrop::new(QualifiedName(*name)), - padding_cols, - collapsed, - escaping, - ); - if let Some(inner_result) = inner_result { - // SAFETY dropped by the cb_free_lines - let inner_result = Box::leak(inner_result.into_iter().map(|x| x.into_raw()).collect()); - // SAFETY TypeDefinitionLineRaw and BNTypeDefinitionLine are transparent - *result_count = inner_result.len(); - let inner_result_ptr = inner_result.as_ptr() as *const BNTypeDefinitionLine; - *result = inner_result_ptr as *mut BNTypeDefinitionLine; - true - } else { - *result = ptr::null_mut(); - *result_count = 0; - false - } -} - -unsafe extern "C" fn cb_print_all_types( - ctxt: *mut ::std::os::raw::c_void, - names: *mut BNQualifiedName, - types: *mut *mut BNType, - type_count: usize, - data: *mut BNBinaryView, - padding_cols: ::std::os::raw::c_int, - escaping: BNTokenEscapingType, - result: *mut *mut ::std::os::raw::c_char, -) -> bool { - let ctxt: &mut T = &mut *(ctxt as *mut T); - let inner_result = ctxt.print_all_types( - &*mem::ManuallyDrop::new(QualifiedName(*names)), - //SAFETY *mut BNType and Type are transparent - core::slice::from_raw_parts(types as *mut Type, type_count), - &BinaryView { handle: data }, - padding_cols, - escaping, - ); - if let Some(inner_result) = inner_result { - // SAFETY dropped by the cb_free_string - *result = inner_result.into_raw(); - true - } else { - *result = ptr::null_mut(); - false - } -} - -unsafe extern "C" fn cb_progress_nop( - _ctxt: *mut ::std::os::raw::c_void, - _progress: usize, - _total: usize, -) -> bool { - true -} - -unsafe extern "C" fn cb_progress bool>( - ctxt: *mut ::std::os::raw::c_void, - progress: usize, - total: usize, -) -> bool { - let ctxt = &mut *(ctxt as *mut F); - ctxt(progress, total) -} - -unsafe extern "C" fn cb_free_tokens( - _ctxt: *mut ::std::os::raw::c_void, - tokens: *mut BNInstructionTextToken, - count: usize, -) { - drop(Box::from_raw(core::ptr::slice_from_raw_parts_mut( - tokens as *mut InstructionTextToken, - count, - ))); -} - -unsafe extern "C" fn cb_free_lines( - _ctxt: *mut ::std::os::raw::c_void, - lines: *mut BNTypeDefinitionLine, - count: usize, -) { - drop(Box::from_raw(core::ptr::slice_from_raw_parts_mut( - lines as *mut TypeDefinitionLineRaw, - count, - ))); + let errors = std::ptr::slice_from_raw_parts_mut(errors, error_count); + let _ = Box::from_raw(errors); } diff --git a/rust/src/typeprinter.rs b/rust/src/typeprinter.rs new file mode 100644 index 000000000..b9145b430 --- /dev/null +++ b/rust/src/typeprinter.rs @@ -0,0 +1,921 @@ +use crate::binaryview::BinaryView; +use crate::disassembly::InstructionTextToken; +use crate::platform::Platform; +use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref}; +use crate::string::{raw_to_string, BnStrCompatible, BnString}; +use crate::typecontainer::TypeContainer; +use crate::types::{NamedTypeReference, QualifiedName, QualifiedNameAndType, Type}; +use binaryninjacore_sys::*; +use std::ffi::{c_char, c_int, c_void}; +use std::ptr::NonNull; + +pub type TokenEscapingType = BNTokenEscapingType; +pub type TypeDefinitionLineType = BNTypeDefinitionLineType; + +/// Register a custom parser with the API +pub fn register_type_printer( + name: S, + parser: T, +) -> (&'static mut T, CoreTypePrinter) { + let parser = Box::leak(Box::new(parser)); + let mut callback = BNTypePrinterCallbacks { + context: parser as *mut _ as *mut c_void, + getTypeTokens: Some(cb_get_type_tokens::), + getTypeTokensBeforeName: Some(cb_get_type_tokens_before_name::), + getTypeTokensAfterName: Some(cb_get_type_tokens_after_name::), + getTypeString: Some(cb_get_type_string::), + getTypeStringBeforeName: Some(cb_get_type_string_before_name::), + getTypeStringAfterName: Some(cb_get_type_string_after_name::), + getTypeLines: Some(cb_get_type_lines::), + printAllTypes: Some(cb_print_all_types::), + freeTokens: Some(cb_free_tokens), + freeString: Some(cb_free_string), + freeLines: Some(cb_free_lines), + }; + let result = unsafe { + BNRegisterTypePrinter( + name.into_bytes_with_nul().as_ref().as_ptr() as *const c_char, + &mut callback, + ) + }; + let core = unsafe { CoreTypePrinter::from_raw(NonNull::new(result).unwrap()) }; + (parser, core) +} + +#[repr(transparent)] +pub struct CoreTypePrinter { + handle: NonNull, +} + +impl CoreTypePrinter { + pub(crate) unsafe fn from_raw(handle: NonNull) -> CoreTypePrinter { + Self { handle } + } + + pub fn printers() -> Array { + let mut count = 0; + let result = unsafe { BNGetTypePrinterList(&mut count) }; + assert!(!result.is_null()); + unsafe { Array::new(result, count, ()) } + } + + pub fn printer_by_name(name: S) -> Option { + let name_raw = name.into_bytes_with_nul(); + let result = unsafe { BNGetTypePrinterByName(name_raw.as_ref().as_ptr() as *const c_char) }; + NonNull::new(result).map(|x| unsafe { Self::from_raw(x) }) + } + + pub fn name(&self) -> BnString { + let result = unsafe { BNGetTypePrinterName(self.handle.as_ptr()) }; + assert!(!result.is_null()); + unsafe { BnString::from_raw(result) } + } + + pub fn get_type_tokens>( + &self, + type_: &Type, + platform: &Platform, + name: T, + base_confidence: u8, + escaping: TokenEscapingType, + ) -> Option> { + let mut result_count = 0; + let mut result = std::ptr::null_mut(); + let mut raw_name = BNQualifiedName::from(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, + ) + }; + success.then(|| { + assert!(!result.is_null()); + unsafe { Array::new(result, result_count, ()) } + }) + } + + pub fn get_type_tokens_before_name( + &self, + type_: &Type, + platform: &Platform, + base_confidence: u8, + parent_type: &Type, + escaping: TokenEscapingType, + ) -> Option> { + let mut result_count = 0; + let mut result = std::ptr::null_mut(); + let success = unsafe { + BNGetTypePrinterTypeTokensBeforeName( + self.handle.as_ptr(), + type_.handle, + platform.handle, + base_confidence, + parent_type.handle, + escaping, + &mut result, + &mut result_count, + ) + }; + success.then(|| { + assert!(!result.is_null()); + unsafe { Array::new(result, result_count, ()) } + }) + } + + pub fn get_type_tokens_after_name( + &self, + type_: &Type, + platform: &Platform, + base_confidence: u8, + parent_type: &Type, + escaping: TokenEscapingType, + ) -> Option> { + let mut result_count = 0; + let mut result = std::ptr::null_mut(); + let success = unsafe { + BNGetTypePrinterTypeTokensAfterName( + self.handle.as_ptr(), + type_.handle, + platform.handle, + base_confidence, + parent_type.handle, + escaping, + &mut result, + &mut result_count, + ) + }; + success.then(|| { + assert!(!result.is_null()); + unsafe { Array::new(result, result_count, ()) } + }) + } + + pub fn get_type_string>( + &self, + type_: &Type, + platform: &Platform, + name: T, + escaping: TokenEscapingType, + ) -> Option { + let mut result = std::ptr::null_mut(); + let mut raw_name = BNQualifiedName::from(name.into()); + let success = unsafe { + BNGetTypePrinterTypeString( + self.handle.as_ptr(), + type_.handle, + platform.handle, + &mut raw_name, + escaping, + &mut result, + ) + }; + success.then(|| unsafe { + assert!(!result.is_null()); + BnString::from_raw(result) + }) + } + + pub fn get_type_string_before_name( + &self, + type_: &Type, + platform: &Platform, + escaping: BNTokenEscapingType, + ) -> Option { + let mut result = std::ptr::null_mut(); + let success = unsafe { + BNGetTypePrinterTypeStringAfterName( + self.handle.as_ptr(), + type_.handle, + platform.handle, + escaping, + &mut result, + ) + }; + success.then(|| unsafe { + assert!(!result.is_null()); + BnString::from_raw(result) + }) + } + + pub fn get_type_string_after_name( + &self, + type_: &Type, + platform: &Platform, + escaping: TokenEscapingType, + ) -> Option { + let mut result = std::ptr::null_mut(); + let success = unsafe { + BNGetTypePrinterTypeStringBeforeName( + self.handle.as_ptr(), + type_.handle, + platform.handle, + escaping, + &mut result, + ) + }; + success.then(|| unsafe { + assert!(!result.is_null()); + BnString::from_raw(result) + }) + } + + pub fn get_type_lines>( + &self, + type_: &Type, + types: &TypeContainer, + name: T, + padding_cols: isize, + collapsed: bool, + escaping: TokenEscapingType, + ) -> Option> { + let mut result_count = 0; + let mut result = std::ptr::null_mut(); + let mut raw_name = BNQualifiedName::from(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, + ) + }; + success.then(|| { + assert!(!result.is_null()); + unsafe { Array::::new(result, result_count, ()) } + }) + } + + /// Print all types to a single big string, including headers, sections, etc + /// + /// * `types` - All types to print + /// * `data` - Binary View in which all the types are defined + /// * `padding_cols` - Maximum number of bytes represented by each padding line + /// * `escaping` - Style of escaping literals which may not be parsable + pub fn default_print_all_types( + &self, + types: T, + data: &BinaryView, + padding_cols: isize, + escaping: TokenEscapingType, + ) -> Option + where + T: Iterator, + I: Into, + { + let mut result = std::ptr::null_mut(); + let (mut raw_names, mut raw_types): (Vec, Vec<_>) = types + .map(|t| { + let t = t.into(); + (BNQualifiedName::from(t.name), 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, + ) + }; + success.then(|| unsafe { + assert!(!result.is_null()); + BnString::from_raw(result) + }) + } + + pub fn print_all_types( + &self, + types: T, + data: &BinaryView, + padding_cols: isize, + escaping: TokenEscapingType, + ) -> Option + where + T: Iterator, + I: Into, + { + let mut result = std::ptr::null_mut(); + // TODO: I dislike how this iter unzip looks like... but its how to avoid allocating again... + let (mut raw_names, mut raw_types): (Vec, Vec<_>) = types + .map(|t| { + let t = t.into(); + (BNQualifiedName::from(t.name), 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, + ) + }; + 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::default(); + let name = default_settings.get_string( + std::ffi::CStr::from_bytes_with_nul(b"analysis.types.printerName\x00").unwrap(), + None, + None, + ); + Self::printer_by_name(name).unwrap() + } +} + +impl CoreArrayProvider for CoreTypePrinter { + type Raw = *mut BNTypePrinter; + type Context = (); + type Wrapped<'a> = Self; +} + +unsafe impl CoreArrayProviderInner for CoreTypePrinter { + unsafe fn free(raw: *mut Self::Raw, _count: usize, _context: &Self::Context) { + BNFreeTypePrinterList(raw) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + // TODO: Because handle is a NonNull we should prob make Self::Raw that as well... + let handle = NonNull::new(*raw).unwrap(); + CoreTypePrinter::from_raw(handle) + } +} + +pub trait TypePrinter { + /// Generate a single-line text representation of a type, Returns a List + /// of text tokens representing the type. + /// + /// * `type_` - Type to print + /// * `platform` - Platform responsible for this type + /// * `name` - Name of the type + /// * `base_confidence` - Confidence to use for tokens created for this type + /// * `escaping` - Style of escaping literals which may not be parsable + fn get_type_tokens>( + &self, + type_: Ref, + platform: Option>, + name: T, + base_confidence: u8, + escaping: TokenEscapingType, + ) -> Option>; + + /// In a single-line text representation of a type, generate the tokens that + /// should be printed before the type's name. Returns a list of text tokens + /// representing the type + /// + /// * `type_` - Type to print + /// * `platform` - Platform responsible for this type + /// * `base_confidence` - Confidence to use for tokens created for this type + /// * `parent_type` - Type of the parent of this type, or None + /// * `escaping` - Style of escaping literals which may not be parsable + fn get_type_tokens_before_name( + &self, + type_: Ref, + platform: Option>, + base_confidence: u8, + parent_type: Option>, + escaping: TokenEscapingType, + ) -> Option>; + + /// In a single-line text representation of a type, generate the tokens + /// that should be printed after the type's name. Returns a ist of text + /// tokens representing the type + /// + /// * `type_` - Type to print + /// * `platform` - Platform responsible for this type + /// * `base_confidence` - Confidence to use for tokens created for this type + /// * `parent_type` - Type of the parent of this type, or None + /// * `escaping` - Style of escaping literals which may not be parsable + fn get_type_tokens_after_name( + &self, + type_: Ref, + platform: Option>, + base_confidence: u8, + parent_type: Option>, + escaping: TokenEscapingType, + ) -> Option>; + + /// Generate a single-line text representation of a type. Returns a string + /// representing the type + /// + /// * `type_` - Type to print + /// * `platform` - Platform responsible for this type + /// * `name` - Name of the type + /// * `escaping` - Style of escaping literals which may not be parsable + fn get_type_string>( + &self, + type_: Ref, + platform: Option>, + name: T, + escaping: TokenEscapingType, + ) -> Option; + + /// In a single-line text representation of a type, generate the string that + /// should be printed before the type's name. Returns a string representing + /// the type + /// + /// * `type_` - Type to print + /// * `platform` - Platform responsible for this type + /// * `escaping` - Style of escaping literals which may not be parsable + fn get_type_string_before_name( + &self, + type_: Ref, + platform: Option>, + escaping: TokenEscapingType, + ) -> Option; + + /// In a single-line text representation of a type, generate the string that + /// should be printed after the type's name. Returns a string representing + /// the type + /// + /// * `type_` - Type to print + /// * `platform` - Platform responsible for this type + /// * `escaping` - Style of escaping literals which may not be parsable + fn get_type_string_after_name( + &self, + type_: Ref, + platform: Option>, + escaping: TokenEscapingType, + ) -> Option; + + /// Generate a multi-line representation of a type. Returns a list of type + /// definition lines + /// + /// * `type_` - Type to print + /// * `types` - Type Container containing the type and dependencies + /// * `name` - Name of the type + /// * `padding_cols` - Maximum number of bytes represented by each padding line + /// * `collapsed` - Whether to collapse structure/enum blocks + /// * `escaping` - Style of escaping literals which may not be parsable + fn get_type_lines>( + &self, + type_: Ref, + types: &TypeContainer, + name: T, + padding_cols: isize, + collapsed: bool, + escaping: TokenEscapingType, + ) -> Option>; + + /// Print all types to a single big string, including headers, sections, + /// etc. + /// + /// * `types` - All types to print + /// * `data` - Binary View in which all the types are defined + /// * `padding_cols` - Maximum number of bytes represented by each padding line + /// * `escaping` - Style of escaping literals which may not be parsable + fn print_all_types( + &self, + names: Vec, + types: Vec>, + data: Ref, + padding_cols: isize, + escaping: TokenEscapingType, + ) -> Option; +} + +// TODO: This needs an extreme amount of documentation... +#[derive(Clone)] +pub struct TypeDefinitionLine { + pub line_type: TypeDefinitionLineType, + pub tokens: Vec, + pub ty: Ref, + pub parent_type: Option>, + // TODO: Document what the root type is. + pub root_type: Option>, + pub root_type_name: Option, + // TODO: Document the base type, and why its a ntr instead of type + name like root type + pub base_type: Option>, + // TODO: These can also be optional? + pub base_offset: u64, + pub offset: u64, + pub field_index: usize, +} + +impl From for TypeDefinitionLine { + fn from(value: BNTypeDefinitionLine) -> Self { + Self { + line_type: value.lineType, + // TODO: Tokens are busted. + tokens: vec![], + ty: unsafe { Type::ref_from_raw(value.type_) }, + parent_type: match value.parentType.is_null() { + false => Some(unsafe { Type::ref_from_raw(value.parentType) }), + true => None, + }, + root_type: match value.rootType.is_null() { + false => Some(unsafe { Type::ref_from_raw(value.rootType) }), + true => None, + }, + root_type_name: match value.rootTypeName.is_null() { + false => Some(unsafe { BnString::from_raw(value.rootTypeName).to_string() }), + true => None, + }, + base_type: match value.baseType.is_null() { + false => Some(unsafe { NamedTypeReference::ref_from_raw(value.baseType) }), + true => None, + }, + base_offset: value.baseOffset, + offset: value.offset, + field_index: value.fieldIndex, + } + } +} + +impl From<&BNTypeDefinitionLine> for TypeDefinitionLine { + fn from(value: &BNTypeDefinitionLine) -> Self { + Self { + line_type: value.lineType, + // TODO: Tokens are busted. + tokens: vec![], + 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, + } + } +} + +impl From for BNTypeDefinitionLine { + fn from(value: TypeDefinitionLine) -> Self { + Self { + lineType: value.line_type, + tokens: todo!("this is busted!"), + count: todo!("see above"), + type_: value.ty.handle, + parentType: value + .parent_type + .map(|t| t.handle) + .unwrap_or(std::ptr::null_mut()), + rootType: value + .root_type + .map(|t| t.handle) + .unwrap_or(std::ptr::null_mut()), + rootTypeName: value + .root_type_name + .map(|s| BnString::new(s).into_raw()) + .unwrap_or(std::ptr::null_mut()), + baseType: value + .base_type + .map(|t| t.handle) + .unwrap_or(std::ptr::null_mut()), + baseOffset: value.base_offset, + offset: value.offset, + fieldIndex: value.field_index, + } + } +} + +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) + } +} + +unsafe extern "C" fn cb_get_type_tokens( + ctxt: *mut ::std::os::raw::c_void, + type_: *mut BNType, + platform: *mut BNPlatform, + name: *mut BNQualifiedName, + base_confidence: u8, + escaping: BNTokenEscapingType, + result: *mut *mut BNInstructionTextToken, + result_count: *mut usize, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let qualified_name = QualifiedName::from(*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 { + // SAFETY dropped by the cb_free_tokens + let inner_result = Box::leak(inner_result.into_boxed_slice()); + *result_count = inner_result.len(); + // TODO: At some point this will not be the case! + // SAFETY InstructionTextToken and BNInstructionTextToken are transparent + let inner_result_ptr = + inner_result.as_ptr() as *mut InstructionTextToken as *mut BNInstructionTextToken; + *result = inner_result_ptr; + true + } else { + *result = std::ptr::null_mut(); + *result_count = 0; + false + } +} + +unsafe extern "C" fn cb_get_type_tokens_before_name( + ctxt: *mut ::std::os::raw::c_void, + type_: *mut BNType, + platform: *mut BNPlatform, + base_confidence: u8, + parent_type: *mut BNType, + escaping: BNTokenEscapingType, + result: *mut *mut BNInstructionTextToken, + result_count: *mut usize, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let inner_result = ctxt.get_type_tokens_before_name( + Type::ref_from_raw(type_), + match platform.is_null() { + false => Some(Platform::ref_from_raw(platform)), + true => None, + }, + base_confidence, + match parent_type.is_null() { + false => Some(Type::ref_from_raw(parent_type)), + true => None, + }, + escaping, + ); + if let Some(inner_result) = inner_result { + // SAFETY dropped by the cb_free_tokens + let inner_result = Box::leak(inner_result.into_boxed_slice()); + *result_count = inner_result.len(); + // SAFETY InstructionTextToken and BNInstructionTextToken are transparent + let inner_result_ptr = + inner_result.as_ptr() as *mut InstructionTextToken as *mut BNInstructionTextToken; + *result = inner_result_ptr; + true + } else { + *result = std::ptr::null_mut(); + *result_count = 0; + false + } +} + +unsafe extern "C" fn cb_get_type_tokens_after_name( + ctxt: *mut ::std::os::raw::c_void, + type_: *mut BNType, + platform: *mut BNPlatform, + base_confidence: u8, + parent_type: *mut BNType, + escaping: BNTokenEscapingType, + result: *mut *mut BNInstructionTextToken, + result_count: *mut usize, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let inner_result = ctxt.get_type_tokens_after_name( + Type::ref_from_raw(type_), + match platform.is_null() { + false => Some(Platform::ref_from_raw(platform)), + true => None, + }, + base_confidence, + match parent_type.is_null() { + false => Some(Type::ref_from_raw(parent_type)), + true => None, + }, + escaping, + ); + if let Some(inner_result) = inner_result { + // NOTE: Dropped by `cb_free_tokens` + let inner_result = Box::leak(inner_result.into_boxed_slice()); + *result_count = inner_result.len(); + // TODO: At some point this will not be the case! + // SAFETY InstructionTextToken and BNInstructionTextToken are transparent + let inner_result_ptr = + inner_result.as_ptr() as *mut InstructionTextToken as *mut BNInstructionTextToken; + *result = inner_result_ptr; + true + } else { + *result = std::ptr::null_mut(); + *result_count = 0; + false + } +} + +unsafe extern "C" fn cb_get_type_string( + ctxt: *mut ::std::os::raw::c_void, + type_: *mut BNType, + platform: *mut BNPlatform, + name: *mut BNQualifiedName, + escaping: BNTokenEscapingType, + result: *mut *mut ::std::os::raw::c_char, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let qualified_name = QualifiedName::from(*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 { + // NOTE: Dropped by `cb_free_string` + let raw_string = BnString::new(inner_result); + *result = raw_string.into_raw(); + true + } else { + *result = std::ptr::null_mut(); + false + } +} + +unsafe extern "C" fn cb_get_type_string_before_name( + ctxt: *mut ::std::os::raw::c_void, + type_: *mut BNType, + platform: *mut BNPlatform, + escaping: BNTokenEscapingType, + result: *mut *mut ::std::os::raw::c_char, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let inner_result = ctxt.get_type_string_before_name( + Type::ref_from_raw(type_), + match platform.is_null() { + false => Some(Platform::ref_from_raw(platform)), + true => None, + }, + escaping, + ); + if let Some(inner_result) = inner_result { + // NOTE: Dropped by `cb_free_string` + let raw_string = BnString::new(inner_result); + *result = raw_string.into_raw(); + true + } else { + *result = std::ptr::null_mut(); + false + } +} + +unsafe extern "C" fn cb_get_type_string_after_name( + ctxt: *mut ::std::os::raw::c_void, + type_: *mut BNType, + platform: *mut BNPlatform, + escaping: BNTokenEscapingType, + result: *mut *mut ::std::os::raw::c_char, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let inner_result = ctxt.get_type_string_after_name( + Type::ref_from_raw(type_), + match platform.is_null() { + false => Some(Platform::ref_from_raw(platform)), + true => None, + }, + escaping, + ); + if let Some(inner_result) = inner_result { + // NOTE: Dropped by `cb_free_string` + let raw_string = BnString::new(inner_result); + *result = raw_string.into_raw(); + true + } else { + *result = std::ptr::null_mut(); + false + } +} + +unsafe extern "C" fn cb_get_type_lines( + ctxt: *mut ::std::os::raw::c_void, + type_: *mut BNType, + types: *mut BNTypeContainer, + name: *mut BNQualifiedName, + padding_cols: ::std::os::raw::c_int, + collapsed: bool, + escaping: BNTokenEscapingType, + result: *mut *mut BNTypeDefinitionLine, + result_count: *mut usize, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let qualified_name = QualifiedName::from(*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 { + *result_count = inner_result.len(); + let boxed_raw_lines: Box<[_]> = inner_result.into_iter().map(Into::into).collect(); + // NOTE: Dropped by `cb_free_lines` + *result = Box::leak(boxed_raw_lines).as_mut_ptr(); + true + } else { + *result = std::ptr::null_mut(); + *result_count = 0; + false + } +} + +unsafe extern "C" fn cb_print_all_types( + ctxt: *mut ::std::os::raw::c_void, + names: *mut BNQualifiedName, + types: *mut *mut BNType, + type_count: usize, + data: *mut BNBinaryView, + padding_cols: ::std::os::raw::c_int, + escaping: BNTokenEscapingType, + result: *mut *mut ::std::os::raw::c_char, +) -> bool { + let ctxt: &mut T = &mut *(ctxt as *mut T); + let raw_names = std::slice::from_raw_parts(names, type_count); + let names: Vec<_> = raw_names.iter().map(Into::into).collect(); + let raw_types = std::slice::from_raw_parts(types, type_count); + 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 { + // NOTE: Dropped by `cb_free_string` + let raw_string = BnString::new(inner_result); + *result = raw_string.into_raw(); + 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 + let _ = BnString::from_raw(string); +} + +unsafe extern "C" fn cb_free_tokens( + _ctxt: *mut ::std::os::raw::c_void, + tokens: *mut BNInstructionTextToken, + count: usize, +) { + let errors = std::ptr::slice_from_raw_parts_mut(tokens, count); + let _ = Box::from_raw(errors); +} + +unsafe extern "C" fn cb_free_lines( + _ctxt: *mut ::std::os::raw::c_void, + lines: *mut BNTypeDefinitionLine, + count: usize, +) { + let errors = std::ptr::slice_from_raw_parts_mut(lines, count); + let _ = Box::from_raw(errors); +} diff --git a/rust/src/types.rs b/rust/src/types.rs index f986a5d68..e27ddf73f 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -960,6 +960,23 @@ impl ToOwned for Type { } } +impl CoreArrayProvider for Type { + type Raw = *mut BNType; + type Context = (); + type Wrapped<'a> = &'a Self; +} + +unsafe impl CoreArrayProviderInner for Type { + unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) { + BNFreeTypeList(raw, count) + } + + unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, _context: &'a Self::Context) -> Self::Wrapped<'a> { + // TODO: This is assuming &'a Type is &*mut BNType + std::mem::transmute(raw) + } +} + // TODO: Remove this struct, or make it not a ZST with a terrible array provider. /// ZST used only for `Array`. pub struct ComponentReferencedType; @@ -1726,7 +1743,7 @@ impl From for BNStructureMember { impl CoreArrayProvider for StructureMember { type Raw = BNStructureMember; type Context = (); - type Wrapped<'a> = StructureMember; + type Wrapped<'a> = Self; } unsafe impl CoreArrayProviderInner for StructureMember { @@ -1815,6 +1832,11 @@ pub struct NamedTypeReference { } impl NamedTypeReference { + pub(crate) unsafe fn from_raw(handle: *mut BNNamedTypeReference) -> Self { + debug_assert!(!handle.is_null()); + Self { handle } + } + pub(crate) unsafe fn ref_from_raw(handle: *mut BNNamedTypeReference) -> Ref { debug_assert!(!handle.is_null()); Ref::new(Self { handle }) @@ -1825,12 +1847,12 @@ impl NamedTypeReference { /// You should not assign type ids yourself, that is the responsibility of the BinaryView /// implementation after your types have been added. Just make sure the names match up and /// the core will do the id stuff for you. - pub fn new(type_class: NamedTypeReferenceClass, name: QualifiedName) -> Ref { - let mut raw_name = BNQualifiedName::from(name); + pub fn new>(type_class: NamedTypeReferenceClass, name: T) -> Ref { + let mut raw_name = BNQualifiedName::from(name.into()); unsafe { Self::ref_from_raw(BNCreateNamedType( type_class, - std::ptr::null() as *const _, + std::ptr::null(), &mut raw_name, )) } @@ -1841,13 +1863,13 @@ impl NamedTypeReference { /// You should not assign type ids yourself: if you use this to reference a type you are going /// to create but have not yet created, you may run into problems when giving your types to /// a BinaryView. - pub fn new_with_id( + pub fn new_with_id, S: BnStrCompatible>( type_class: NamedTypeReferenceClass, type_id: S, - name: QualifiedName, + name: T, ) -> Ref { let type_id = type_id.into_bytes_with_nul(); - let mut raw_name = BNQualifiedName::from(name); + let mut raw_name = BNQualifiedName::from(name.into()); unsafe { Self::ref_from_raw(BNCreateNamedType( @@ -1923,9 +1945,9 @@ impl Debug for NamedTypeReference { // TODO: Document usage, specifically how to make a qualified name and why it exists. #[derive(Default, Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] pub struct QualifiedName { - pub items: Vec, // TODO: Make this Option where default is "::". pub seperator: String, + pub items: Vec, } impl QualifiedName { diff --git a/rust/tests/demangler.rs b/rust/tests/demangler.rs index acaa0a87b..c2f47bf6e 100644 --- a/rust/tests/demangler.rs +++ b/rust/tests/demangler.rs @@ -20,13 +20,13 @@ fn test_demangler_simple(_session: &Session) { // Example LLVM-style mangled name let llvm_mangled = "_Z3fooi"; // "foo(int)" in LLVM mangling let llvm_demangled = demangle_llvm(llvm_mangled, true).unwrap(); - assert_eq!(llvm_demangled, vec!["foo(int)"]); + assert_eq!(llvm_demangled, "foo(int)".into()); // Example GNU-style mangled name let gnu_mangled = "_Z3bari"; // "bar(int)" in GNU mangling let (gnu_demangled_name, gnu_demangled_type) = demangle_gnu3(&placeholder_arch, gnu_mangled, true).unwrap(); - assert_eq!(gnu_demangled_name, vec!["bar"]); + assert_eq!(gnu_demangled_name, "bar".into()); // TODO: We check the type display because other means include things such as confidence which is hard to get 1:1 assert_eq!( gnu_demangled_type.unwrap().to_string(), @@ -37,7 +37,7 @@ fn test_demangler_simple(_session: &Session) { let msvc_mangled = "?baz@@YAHH@Z"; // "int __cdecl baz(int)" in MSVC mangling let (msvc_demangled_name, msvc_demangled_type) = demangle_ms(&placeholder_arch, msvc_mangled, true).unwrap(); - assert_eq!(msvc_demangled_name, vec!["baz"]); + assert_eq!(msvc_demangled_name, "baz".into()); // TODO: We check the type display because other means include things such as confidence which is hard to get 1:1 assert_eq!( msvc_demangled_type.unwrap().to_string(), diff --git a/rust/tests/typearchive.rs b/rust/tests/typearchive.rs index 2a179f108..0b0164955 100644 --- a/rust/tests/typearchive.rs +++ b/rust/tests/typearchive.rs @@ -19,7 +19,7 @@ fn empty_view() -> Ref { } #[rstest] -fn test_main_thread_different(_session: &Session) { +fn test_create_archive(_session: &Session) { let placeholder_platform = Platform::by_name("x86_64").expect("Failed to get platform"); let temp_dir = tempfile::tempdir().unwrap(); diff --git a/rust/tests/typecontainer.rs b/rust/tests/typecontainer.rs new file mode 100644 index 000000000..0457fe09f --- /dev/null +++ b/rust/tests/typecontainer.rs @@ -0,0 +1,127 @@ +use binaryninja::binaryview::{BinaryView, BinaryViewExt}; +use binaryninja::filemetadata::FileMetadata; +use binaryninja::headless::Session; +use binaryninja::platform::Platform; +use binaryninja::rc::Ref; +use binaryninja::types::Type; +use rstest::*; + +#[fixture] +#[once] +fn session() -> Session { + Session::new() +} + +#[fixture] +#[once] +fn platform() -> Ref { + // TODO: Because some behavior might be platform specific we might need to move this back into each test. + // TODO: See test_parse_type + Platform::by_name("windows-x86_64").expect("windows-x86_64 exists") +} + +#[fixture] +#[once] +fn empty_view() -> Ref { + BinaryView::from_data(&FileMetadata::new(), &[]).expect("Failed to create view") +} + +#[rstest] +fn test_types(_session: &Session, platform: &Platform) { + let type_container = platform.type_container(); + let types = type_container.types().unwrap(); + let types_len = types.len(); + // windows-x86_64 has a few thousand, not zero. + assert_ne!(types_len, 0); +} + +#[rstest] +fn test_type_id(_session: &Session, platform: &Platform) { + let type_container = platform.type_container(); + let type_ids = type_container.type_ids().unwrap(); + let first_type_id = type_ids.iter().next().unwrap(); + let found_type = type_container + .type_by_id(first_type_id) + .expect("Type ID not valid!"); + let found_type_name = type_container + .type_name(first_type_id) + .expect("Type name not found for Type ID"); + let found_type_for_type_name = type_container + .type_by_name(found_type_name) + .expect("Found type name not valid!"); + // These _should_ be the same type. + assert_eq!(found_type, found_type_for_type_name); +} + +#[rstest] +fn test_add_delete_type(_session: &Session, empty_view: &BinaryView) { + let view_type_container = empty_view.type_container(); + let test_type = Type::int(4, true); + assert!( + view_type_container.add_types([("mytype", test_type)]), + "Failed to add types!" + ); + let my_type_id = view_type_container + .type_id("mytype") + .expect("mytype not found"); + assert!( + view_type_container.delete_type(my_type_id), + "Type was deleted!" + ); + // There should be no type ids if the type was actually deleted + assert_eq!(view_type_container.type_ids().unwrap().len(), 0) +} + +#[rstest] +fn test_immutable_container(_session: &Session, platform: &Platform) { + // Platform type containers are immutable, so we shouldn't be able to delete/add/rename types. + let plat_type_container = platform.type_container(); + assert!( + !plat_type_container.is_mutable(), + "Platform should NOT be mutable!" + ); + let type_ids = plat_type_container.type_ids().unwrap(); + let first_type_id = type_ids.iter().next().unwrap(); + // Platform type containers are immutable so these should be false! + assert!( + !plat_type_container.delete_type(first_type_id), + "Type was deleted!" + ); + assert!( + !plat_type_container.add_types([("new_type", Type::int(4, true))]), + "Type was added!" + ); + assert!( + !plat_type_container.rename_type(first_type_id, "renamed_type"), + "Type was renamed!" + ); +} + +#[rstest] +fn test_parse_type(_session: &Session, platform: &Platform) { + let type_container = platform.type_container(); + // HANDLE will be pulled in from the platform, which is `windows-x86_64`. + let parsed_type = type_container + .parse_type_string("typedef HANDLE test;", false) + .map_err(|e| e.to_vec()) + .expect("Failed to parse type"); + assert_eq!(parsed_type.name, "test".into()); + assert_eq!(parsed_type.ty.to_string(), "HANDLE"); +} + +#[rstest] +fn test_container_lifetime(_session: &Session, platform: &Platform, empty_view: &BinaryView) { + let plat_type_container_0 = platform.type_container(); + let view_type_container_0 = empty_view.type_container(); + let test_type = Type::int(4, true); + view_type_container_0.add_types([("mytype", test_type)]); + let plat_types_0 = plat_type_container_0.types(); + let view_types_0 = view_type_container_0.types(); + let plat_type_container_1 = platform.type_container(); + let view_type_container_1 = empty_view.type_container(); + let plat_types_1 = plat_type_container_1.types(); + let view_types_1 = view_type_container_1.types(); + // If the types do not equal the container is being freed from the first set of calls. + assert_eq!(plat_types_0, plat_types_1); + assert_eq!(view_types_0, view_types_1); +} diff --git a/rust/tests/typeparser.rs b/rust/tests/typeparser.rs new file mode 100644 index 000000000..b86bb6808 --- /dev/null +++ b/rust/tests/typeparser.rs @@ -0,0 +1,85 @@ +use binaryninja::headless::Session; +use binaryninja::platform::Platform; +use binaryninja::typeparser::{CoreTypeParser, TypeParser, TypeParserError}; +use binaryninja::types::Type; +use binaryninjacore_sys::BNTypeParserErrorSeverity::ErrorSeverity; +use rstest::*; + +const TEST_TYPES: &str = r#" +typedef int int32_t; +typedef unsigned int uint32_t; +typedef float float_t; +typedef double double_t; +typedef char char_t; +typedef unsigned char uchar_t; +typedef short short_t; +typedef unsigned short ushort_t; +typedef long long_t; +typedef unsigned long ulong_t; +typedef long long longlong_t; +typedef unsigned long long ulonglong_t; +typedef void* void_ptr_t; +typedef int (*function_type)(int arg1, float arg2); +// This should be 2 types +typedef struct { + int a; + float b; +} struct_type; +"#; + +#[fixture] +#[once] +fn session() -> Session { + Session::new() +} + +#[rstest] +fn test_string_to_type(_session: &Session) { + let platform = Platform::by_name("windows-x86_64").expect("windows-x86_64 exists"); + let plat_type_container = platform.type_container(); + let parser = CoreTypeParser::default(); + let parsed_type = parser + .parse_type_string("int32_t", &platform, &plat_type_container) + .expect("Parsed int32_t"); + let test_type = Type::int(4, true); + assert_eq!(test_type, parsed_type.ty); +} + +#[rstest] +fn test_string_to_types(_session: &Session) { + let platform = Platform::by_name("windows-x86_64").expect("windows-x86_64 exists"); + let plat_type_container = platform.type_container(); + let parser = CoreTypeParser::default(); + let parsed_type = parser + .parse_types_from_source( + TEST_TYPES, + "test_file.h", + &platform, + &plat_type_container, + &[], + &[], + "", + ) + .expect("Parsed types"); + assert_eq!(14, parsed_type.types.len()); +} + +#[rstest] +fn test_parse_error(_session: &Session) { + let platform = Platform::by_name("windows-x86_64").expect("windows-x86_64 exists"); + let plat_type_container = platform.type_container(); + let parser = CoreTypeParser::default(); + let parser_error = parser + .parse_type_string("AAAAA", &platform, &plat_type_container) + .expect_err("Parsing should fail!"); + assert_eq!( + parser_error, + vec![TypeParserError { + severity: ErrorSeverity, + message: "a type specifier is required for all declarations".to_string(), + file_name: "string.hpp".to_string(), + line: 1, + column: 1 + }] + ); +}