diff options
| author | Mason Reed <mason@vector35.com> | 2025-12-13 16:32:17 -0500 |
|---|---|---|
| committer | Mason Reed <35282038+emesare@users.noreply.github.com> | 2025-12-15 19:29:56 -0500 |
| commit | 8e409a0854292eb7e61420b7bfc904a1156b4a68 (patch) | |
| tree | b0f1fe381812b8ec21205d7c1cb609ec141c1dc0 /rust | |
| parent | 3ec3f3e384edb78b33b5f0c179ca8a89731b95e5 (diff) | |
[Rust] Misc `QualifiedName` cleanup
- Added some unit tests
- Improved documentation
- Made `QualifiedName::new` and `QualifiedName::new_with_separator` generic over `Into<String>`
Diffstat (limited to 'rust')
| -rw-r--r-- | rust/src/qualified_name.rs | 61 | ||||
| -rw-r--r-- | rust/tests/qualified_name.rs | 187 |
2 files changed, 239 insertions, 9 deletions
diff --git a/rust/src/qualified_name.rs b/rust/src/qualified_name.rs index c1d943f8..fc362cf6 100644 --- a/rust/src/qualified_name.rs +++ b/rust/src/qualified_name.rs @@ -1,3 +1,5 @@ +//! The [`QualifiedName`] is the canonical way to represent structured names in Binary Ninja. + use crate::rc::{CoreArrayProvider, CoreArrayProviderInner}; use crate::string::{raw_to_string, strings_to_string_list, BnString}; use binaryninjacore_sys::*; @@ -5,7 +7,34 @@ use std::borrow::Cow; use std::fmt::{Display, Formatter}; use std::ops::{Index, IndexMut}; -// TODO: Document usage, specifically how to make a qualified name and why it exists. +/// A [`QualifiedName`] represents a name composed of multiple components, typically used for symbols +/// and type names within namespaces, classes, or modules. +/// +/// # Creating a Qualified Name +/// +/// ``` +/// use binaryninja::qualified_name::QualifiedName; +/// +/// // Uses the default separator "::" +/// let qn_vec = QualifiedName::new(vec!["my", "namespace", "func"]); +/// assert_eq!(qn_vec.to_string(), "my::namespace::func"); +/// +/// // Using `QualifiedName::from` will not split on the default separator "::". +/// let qn_from = QualifiedName::from("std::string"); +/// assert_eq!(qn_from.len(), 1); +/// assert_eq!(qn_from.to_string(), "std::string"); +/// ``` +/// +/// # Using a Custom Separator +/// +/// While `::` is the default, you can specify a custom separator: +/// +/// ``` +/// use binaryninja::qualified_name::QualifiedName; +/// +/// let qn = QualifiedName::new_with_separator(["a", "b", "c"], "."); +/// assert_eq!(qn.to_string(), "a.b.c"); +/// ``` #[derive(Default, Debug, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)] pub struct QualifiedName { // TODO: Make this Option<String> where default is "::". @@ -14,7 +43,7 @@ pub struct QualifiedName { } impl QualifiedName { - pub(crate) fn from_raw(value: &BNQualifiedName) -> Self { + pub fn from_raw(value: &BNQualifiedName) -> Self { // TODO: This could be improved... let raw_names = unsafe { std::slice::from_raw_parts(value.name, value.nameCount) }; let items = raw_names @@ -25,7 +54,7 @@ impl QualifiedName { Self { items, separator } } - pub(crate) fn from_owned_raw(value: BNQualifiedName) -> Self { + pub fn from_owned_raw(value: BNQualifiedName) -> Self { let result = Self::from_raw(&value); Self::free_raw(value); result @@ -42,17 +71,31 @@ impl QualifiedName { } } - pub(crate) fn free_raw(value: BNQualifiedName) { + pub fn free_raw(value: BNQualifiedName) { unsafe { BnString::free_raw(value.join) }; unsafe { BNFreeStringList(value.name, value.nameCount) }; } - pub fn new(items: Vec<String>) -> Self { - Self::new_with_separator(items, "::".to_string()) + /// Creates a new [`QualifiedName`] with the default separator `::`. + pub fn new<I, S>(items: I) -> Self + where + I: IntoIterator<Item = S>, + S: Into<String>, + { + Self::new_with_separator(items, "::") } - pub fn new_with_separator(items: Vec<String>, separator: String) -> Self { - Self { items, separator } + /// Creates a new `QualifiedName` with a custom separator. + pub fn new_with_separator<I, S>(items: I, separator: impl Into<String>) -> Self + where + I: IntoIterator<Item = S>, + S: Into<String>, + { + let items = items.into_iter().map(Into::into).collect::<Vec<String>>(); + Self { + items, + separator: separator.into(), + } } pub fn with_item(&self, item: impl Into<String>) -> Self { @@ -90,7 +133,7 @@ impl QualifiedName { /// # Example /// /// ``` - /// use binaryninja::types::QualifiedName; + /// use binaryninja::qualified_name::QualifiedName; /// /// let qualified_name = /// QualifiedName::new(vec!["my::namespace".to_string(), "mytype".to_string()]); diff --git a/rust/tests/qualified_name.rs b/rust/tests/qualified_name.rs new file mode 100644 index 00000000..c0d16a75 --- /dev/null +++ b/rust/tests/qualified_name.rs @@ -0,0 +1,187 @@ +use binaryninja::qualified_name::QualifiedName; + +#[test] +fn test_new_from_vec_string() { + let items = vec!["std".to_string(), "vector".to_string(), "int".to_string()]; + let qn = QualifiedName::new(items); + assert_eq!(qn.len(), 3); + assert_eq!(qn.to_string(), "std::vector::int"); + assert_eq!(qn.separator, "::"); +} + +#[test] +fn test_new_from_array() { + let items = ["root", "data"]; + let qn = QualifiedName::new(items); + assert_eq!(qn.len(), 2); + assert_eq!(qn.to_string(), "root::data"); +} + +#[test] +fn test_new_empty() { + let items: Vec<String> = Vec::new(); + let qn = QualifiedName::new(items); + assert!(qn.is_empty()); + assert_eq!(qn.to_string(), ""); +} + +#[test] +fn test_new_with_custom_separator() { + let items = vec!["a".to_string(), "b".to_string(), "c".to_string()]; + let separator = ".".to_string(); + let qn = QualifiedName::new_with_separator(items, separator); + assert_eq!(qn.to_string(), "a.b.c"); + assert_eq!(qn.separator, "."); +} + +#[test] +fn test_from_string() { + let qn: QualifiedName = "SingleName".to_string().into(); + assert_eq!(qn.len(), 1); + assert_eq!(qn[0], "SingleName"); + assert_eq!(qn.to_string(), "SingleName"); +} + +#[test] +fn test_from_str_literal() { + let qn: QualifiedName = "AnotherName".into(); + assert_eq!(qn.len(), 1); + assert_eq!(qn[0], "AnotherName"); +} + +#[test] +fn test_into_string() { + let qn = QualifiedName::new(vec!["A", "B", "C"]); + let s: String = qn.into(); + assert_eq!(s, "A::B::C"); +} + +#[test] +fn test_push_pop_and_last() { + let mut qn = QualifiedName::new(vec!["ns1"]); + + qn.push("TypeA".to_string()); + assert_eq!(qn.len(), 2); + assert_eq!(qn.to_string(), "ns1::TypeA"); + + assert_eq!(qn.last().unwrap(), "TypeA"); + *qn.last_mut().unwrap() = "TypeB".to_string(); + assert_eq!(qn.to_string(), "ns1::TypeB"); + + assert_eq!(qn.pop().unwrap(), "TypeB"); + assert_eq!(qn.len(), 1); + assert_eq!(qn.to_string(), "ns1"); + + assert_eq!(qn.pop().unwrap(), "ns1"); + assert!(qn.is_empty()); + assert_eq!(qn.pop(), None); +} + +#[test] +fn test_with_item() { + let qn_a = QualifiedName::from("ns1"); + let qn_b = qn_a.with_item("ns2"); + let qn_c = qn_b.with_item("symbol"); + + assert_eq!(qn_a.to_string(), "ns1"); + assert_eq!(qn_b.to_string(), "ns1::ns2"); + assert_eq!(qn_c.to_string(), "ns1::ns2::symbol"); + + // Ensure the original is unchanged + assert_eq!(qn_a.len(), 1); +} + +#[test] +fn test_split_last() { + let qn = QualifiedName::new(vec!["A", "B", "C"]); + + let (last, prefix) = qn.split_last().unwrap(); + assert_eq!(last, "C"); + assert_eq!(prefix.to_string(), "A::B"); + + let (last2, prefix2) = prefix.split_last().unwrap(); + assert_eq!(last2, "B"); + assert_eq!(prefix2.to_string(), "A"); + + let (last3, prefix3) = prefix2.split_last().unwrap(); + assert_eq!(last3, "A"); + assert!(prefix3.is_empty()); + + // Split on an empty name + assert_eq!(prefix3.split_last(), None); +} + +#[test] +fn test_replace() { + let qualified_name = + QualifiedName::new(vec!["my_prefix::ns".to_string(), "my_Type".to_string()]); + + let replaced = qualified_name.replace("my", "your"); + assert_eq!( + replaced.items, + vec!["your_prefix::ns".to_string(), "your_Type".to_string()] + ); + assert_eq!(replaced.to_string(), "your_prefix::ns::your_Type"); + + let qualified_name_dot = QualifiedName::new_with_separator(vec!["foo", "bar"], ".".to_string()); + let replaced_dot = qualified_name_dot.replace("foo", "baz"); + assert_eq!(replaced_dot.to_string(), "baz.bar"); +} + +#[test] +fn test_index_and_index_mut() { + let mut qn = QualifiedName::new(vec!["a", "b", "c"]); + assert_eq!(qn[0], "a"); + assert_eq!(qn[2], "c"); + qn[1] = "NEW_ITEM".to_string(); + assert_eq!(qn.to_string(), "a::NEW_ITEM::c"); +} + +#[test] +#[should_panic] +fn test_index_out_of_bounds() { + let qn = QualifiedName::new(vec!["a"]); + // This should panic + let _ = qn[1]; +} + +#[test] +fn test_insert() { + let mut qn = QualifiedName::new(vec!["start", "end"]); + + qn.insert(1, "middle".to_string()); + assert_eq!(qn.to_string(), "start::middle::end"); + + qn.insert(0, "HEAD".to_string()); + assert_eq!(qn.to_string(), "HEAD::start::middle::end"); + + qn.insert(4, "TAIL".to_string()); + assert_eq!(qn.to_string(), "HEAD::start::middle::end::TAIL"); + + let initial_len = qn.len(); + qn.insert(initial_len + 5, "NOPE".to_string()); + assert_eq!(qn.len(), initial_len); +} + +#[test] +fn test_into_and_from_raw() { + let original_qn = QualifiedName::new(["std", "vector"]); + assert_eq!(original_qn.to_string(), "std::vector"); + + let raw_qn = QualifiedName::into_raw(original_qn); + assert_eq!(raw_qn.nameCount, 2); + assert!(!raw_qn.join.is_null()); + + let restored_qn = QualifiedName::from_owned_raw(raw_qn); + assert_eq!(restored_qn.len(), 2); + assert_eq!(restored_qn.to_string(), "std::vector"); +} + +#[test] +fn test_raw_freeing() { + let qn = QualifiedName::new(["std", "vector"]); + let raw_qn = QualifiedName::into_raw(qn); + assert!(!raw_qn.join.is_null()); + assert!(!raw_qn.name.is_null()); + QualifiedName::free_raw(raw_qn); +} |
