use idakit_sys as sys;
use crate::Database;
use crate::address::Address;
impl Database {
#[must_use]
#[doc(alias("build_strlist"))]
pub fn strings(&self) -> Strings<'_> {
self.strlist_build();
Strings::new(self)
}
}
#[derive(Clone, Copy)]
#[doc(alias("string_info_t"))]
pub struct StringLiteral<'db> {
address: Address,
length: usize,
raw_type: i32,
db: &'db Database,
}
impl<'db> StringLiteral<'db> {
#[inline]
pub(crate) fn new(address: Address, length: usize, raw_type: i32, db: &'db Database) -> Self {
Self {
address,
length,
raw_type,
db,
}
}
#[inline]
#[must_use]
pub const fn address(&self) -> Address {
self.address
}
#[inline]
#[must_use]
pub const fn len(&self) -> usize {
self.length
}
#[inline]
#[must_use]
pub const fn is_empty(&self) -> bool {
self.length == 0
}
#[inline]
#[must_use]
pub fn char_width(&self) -> u8 {
char_width_of(self.raw_type)
}
#[inline]
#[must_use]
pub fn is_pascal(&self) -> bool {
is_pascal_of(self.raw_type)
}
#[must_use]
#[doc(alias("get_strlit_contents"))]
pub fn text(&self) -> Option<String> {
self.db
.strlit_contents(self.address, self.length, self.raw_type)
}
}
impl std::fmt::Debug for StringLiteral<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StringLiteral")
.field("address", &self.address)
.field("len", &self.length)
.field("char_width", &self.char_width())
.field("text", &self.text())
.finish()
}
}
impl PartialEq for StringLiteral<'_> {
fn eq(&self, o: &Self) -> bool {
self.address == o.address
}
}
impl Eq for StringLiteral<'_> {}
impl std::hash::Hash for StringLiteral<'_> {
fn hash<H: std::hash::Hasher>(&self, s: &mut H) {
self.address.hash(s);
}
}
fn char_width_of(raw_type: i32) -> u8 {
1u8 << ((raw_type & sys::STRWIDTH_MASK) as u8)
}
fn is_pascal_of(raw_type: i32) -> bool {
let layout = (raw_type & sys::STRLYT_MASK) >> sys::STRLYT_SHIFT;
(1..=3).contains(&layout)
}
#[doc(alias("get_strlist_qty", "get_strlist_item"))]
pub struct Strings<'db> {
db: &'db Database,
next: usize,
count: usize,
}
impl<'db> Strings<'db> {
#[inline]
pub(crate) fn new(db: &'db Database) -> Self {
Self {
db,
next: 0,
count: db.strlist_qty(),
}
}
fn item(&self, n: usize) -> Option<StringLiteral<'db>> {
let item = self.db.strlist_item(n)?;
let address = Address::try_new(item.ea)?;
Some(StringLiteral::new(
address,
item.length.max(0) as usize,
item.type_,
self.db,
))
}
}
impl<'db> Iterator for Strings<'db> {
type Item = StringLiteral<'db>;
fn next(&mut self) -> Option<Self::Item> {
while self.next < self.count {
let n = self.next;
self.next += 1;
if let Some(string) = self.item(n) {
return Some(string);
}
}
None
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(0, Some(self.count - self.next))
}
}
#[cfg(test)]
mod tests {
use assert2::assert;
use rstest::rstest;
use super::*;
const STRTYPE_C: i32 = 0x00; const STRTYPE_C_16: i32 = 0x01; const STRTYPE_C_32: i32 = 0x02; const STRTYPE_PASCAL: i32 = 0x04; const STRTYPE_PASCAL_16: i32 = 0x05; const STRTYPE_LEN2: i32 = 0x08;
#[rstest]
#[case(STRTYPE_C, 1)]
#[case(STRTYPE_C_16, 2)]
#[case(STRTYPE_C_32, 4)]
#[case(STRTYPE_PASCAL, 1)]
#[case(STRTYPE_PASCAL_16, 2)]
#[case(STRTYPE_C | 0x5500_0000, 1)] fn char_width_reads_the_strwidth_field(#[case] raw: i32, #[case] width: u8) {
assert!(char_width_of(raw) == width);
}
#[rstest]
#[case(STRTYPE_C, false)]
#[case(STRTYPE_C_16, false)]
#[case(STRTYPE_PASCAL, true)]
#[case(STRTYPE_PASCAL_16, true)]
#[case(STRTYPE_LEN2, true)]
fn is_pascal_reads_the_strlyt_field(#[case] raw: i32, #[case] pascal: bool) {
assert!(is_pascal_of(raw) == pascal);
}
}