use std::fmt::Write as _;
use idakit::prelude::*;
const OVERVIEW: usize = 25;
const CLOSE_UP: usize = 6;
const MAX_LINES: usize = 12;
const BUDGET: usize = 84;
const NAME_COL: usize = 36;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut args = std::env::args().skip(1);
let a = args.next().expect("usage: type_diff <a.i64> <b.i64>");
let b = args.next().expect("usage: type_diff <a.i64> <b.i64>");
Ida::run(move |ida| -> Result<()> {
ida.call(move |idb| -> Result<()> {
let ca = idb.with_open(&a, |idb| Ok(idb.type_catalog()))?;
let cb = idb.with_open(&b, |idb| Ok(idb.type_catalog()))?;
report(&label(&a), &ca, &label(&b), &cb);
Ok(())
})?
})??;
println!("\nTYPE_DIFF OK");
Ok(())
}
fn report(la: &str, a: &TypeCatalog, lb: &str, b: &TypeCatalog) {
println!("Comparing named types across two databases");
println!(" A {la:<24} {} types", a.len());
println!(" B {lb:<24} {} types", b.len());
let d = a.diff(b);
println!();
println!(
" shared {} identical {} drifted {} A-only {} B-only {}",
d.shared(),
d.identical().len(),
d.drifted().len(),
d.only_left().len(),
d.only_right().len(),
);
if d.drifted().is_empty() {
return;
}
let mut drifted: Vec<&(String, TypeDiff)> = d.drifted().iter().collect();
drifted.sort_by_key(|(_, td)| td.len());
println!("\ndrifted, fewest changes first (+ added, - removed, ~ changed):");
for (name, td) in drifted.iter().take(OVERVIEW) {
println!(" {:<NAME_COL$} {}", clip(name, NAME_COL), tally(td));
}
if drifted.len() > OVERVIEW {
println!(" ... and {} more", drifted.len() - OVERVIEW);
}
println!("\ndetail ({} fewest-changed):", drifted.len().min(CLOSE_UP));
for (name, d) in drifted.iter().take(CLOSE_UP) {
println!(" {name}");
let text = format!("{d:BUDGET$}");
let lines: Vec<&str> = text.lines().collect();
for line in lines.iter().take(MAX_LINES) {
println!(" {line}");
}
if lines.len() > MAX_LINES {
println!(" ... and {} more lines", lines.len() - MAX_LINES);
}
}
}
fn clip(s: &str, max: usize) -> String {
if s.chars().count() <= max {
s.to_owned()
} else {
s.chars().take(max - 1).chain(['…']).collect()
}
}
fn tally(d: &TypeDiff) -> String {
let mut parts: Vec<String> = Vec::new();
if d.added() > 0 {
parts.push(format!("+{}", d.added()));
}
if d.removed() > 0 {
parts.push(format!("-{}", d.removed()));
}
if d.changed() > 0 {
parts.push(format!("~{}", d.changed()));
}
let mut out = format!("{:<12}", parts.join(" "));
if let Some((l, r)) = d.size_change() {
let _ = write!(out, "size {} -> {}", hex(l), hex(r));
}
out.trim_end().to_owned()
}
fn hex(size: Option<u64>) -> String {
size.map_or_else(|| "?".to_owned(), |v| format!("{v:#x}"))
}
fn label(path: &str) -> String {
std::path::Path::new(path)
.file_name()
.map_or_else(|| path.to_owned(), |s| s.to_string_lossy().into_owned())
}