use idakit::prelude::*;
use idakit_runner_macros::kernel_test;
#[kernel_test(read_only)]
fn search() {
crate::common::with_canonical_db(run);
}
fn run(idb: &mut Database) {
let first = idb.functions().next().expect("a function");
let address = first.address();
let bytes = idb.bytes(address, 8);
assert!(bytes.len() == 8, "need 8 readable bytes at the entry");
exact_forms_all_find_entry(idb, address, &bytes);
wildcards_still_match(idb, address, &bytes);
range_excludes_start(idb, address, &bytes);
rejections_trip(idb);
debug_impls_render_real_fields(idb, address, &bytes);
println!("search OK: all four constructor forms match; rejections trip typed errors");
}
fn exact_forms_all_find_entry(idb: &Database, address: Address, bytes: &[u8]) {
let hex_str = bytes
.iter()
.map(|b| format!("{b:02X}"))
.collect::<Vec<_>>()
.join(" ");
let hex = Pattern::hex(idb, &hex_str).expect("hex compiles");
assert!(
idb.search(&hex).any(|m| m == address),
"hex should match entry"
);
let raw = Pattern::bytes(idb, bytes).call().expect("bytes compiles");
assert!(
idb.search(&raw).any(|m| m == address),
"bytes should match entry"
);
let full_mask = "x".repeat(bytes.len());
let cm = Pattern::code_mask(idb, bytes, &full_mask).expect("code_mask compiles");
assert!(
idb.search(&cm).any(|m| m == address),
"code_mask should match entry"
);
let ida = Pattern::ida(idb, &hex_str).call().expect("ida compiles");
assert!(
idb.search(&ida).any(|m| m == address),
"ida should match entry"
);
}
fn wildcards_still_match(idb: &Database, address: Address, bytes: &[u8]) {
let mut wild: Vec<String> = bytes.iter().map(|b| format!("{b:02X}")).collect();
"?".clone_into(&mut wild[1]);
let byte_wild = Pattern::hex(idb, wild.join(" ")).expect("byte-wildcard compiles");
assert!(
idb.search(&byte_wild).any(|m| m == address),
"byte wildcard should still match entry"
);
wild[1] = format!("{:X}?", bytes[1] >> 4);
let nib_wild = Pattern::hex(idb, wild.join(" ")).expect("nibble-wildcard compiles");
assert!(
idb.search(&nib_wild).any(|m| m == address),
"nibble wildcard should still match entry"
);
}
fn range_excludes_start(idb: &Database, address: Address, bytes: &[u8]) {
let pat = Pattern::bytes(idb, bytes).call().expect("bytes compiles");
let bounds = idb
.address_range()
.expect("open database has an address range");
assert!(
!idb.search_in((address + 1)..bounds.end, &pat)
.any(|m| m == address),
"range after {address:#x} should exclude it"
);
}
fn rejections_trip(idb: &Database) {
let_no_anchor(&Pattern::hex(idb, "? ?"), 2);
let r = Pattern::bytes(idb, &[0x90, 0x90]).mask(&[0xFF]).call();
assert!(
matches!(
r,
Err(Error::PatternRejected {
kind: PatternRejection::MaskMismatch { bytes: 2, mask: 1 },
..
})
),
"short mask should be MaskMismatch, got {r:?}"
);
let r = Pattern::ida(idb, "").call();
assert!(
matches!(
r,
Err(Error::PatternRejected {
kind: PatternRejection::Unparseable { .. },
..
})
),
"empty ida pattern should be Unparseable, got {r:?}"
);
}
fn debug_impls_render_real_fields(idb: &Database, address: Address, bytes: &[u8]) {
let pat = Pattern::bytes(idb, bytes).call().expect("bytes compiles");
let pat_dbg = format!("{pat:?}");
assert!(
pat_dbg.contains("BITMASK"),
"Pattern's Debug should render its real match flags, got {pat_dbg:?}"
);
let bounds = idb
.address_range()
.expect("open database has an address range");
let matches = idb.search_in(address..bounds.end, &pat);
let matches_dbg = format!("{matches:?}");
assert!(
matches_dbg.contains("Matches") && matches_dbg.contains(&format!("{address:#x}")),
"Matches' Debug should render its real cursor state, got {matches_dbg:?}"
);
}
fn let_no_anchor(r: &Result<Pattern<'_>>, total: usize) {
assert!(
matches!(
r,
Err(Error::PatternRejected {
kind: PatternRejection::NoAnchor { total: t },
..
}) if *t == total
),
"expected NoAnchor {{ total: {total} }}, got {r:?}"
);
}