crates/nornir-introspect/src/binscan.rs
//! `binscan` — analyse ONE produced artifact end-to-end into warehouse-ready
//! facts. Slice 1 fills the size + hardening surface; the link graph, binary
//! SBOM and artifact diff are later slices (they extend [`BinaryReport`]).
//!
//! Pure Rust, gatling-parallel over the symbol table (see [`crate::size`]),
//! never shells out. `source_root` (optional) enables DWARF crate + file/line
//! attribution for the per-symbol rows.
use std::path::Path;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use crate::hardening::{self, Hardening};
use crate::size::{self, CrateSize, SectionSize, SymbolSize};
/// The full analysis of one artifact — the header (format/size + checksec), the
/// section + symbol + crate size breakdown.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BinaryReport {
/// Format / arch / class / total-bytes + the checksec hardening verdict.
pub header: Hardening,
/// Per-section sizes (Bloaty view).
pub sections: Vec<SectionSize>,
/// Per-symbol sizes (cargo-bloat / twiggy view).
pub symbols: Vec<SymbolSize>,
/// Per-crate size roll-up (descending by size).
pub crates: Vec<CrateSize>,
}
/// Analyse the artifact at `path`. When `source_root` is `Some`, DWARF symbols
/// (scoped to that root) are joined so every symbol lands on its crate + source
/// location.
pub fn analyze(path: &Path, source_root: Option<&Path>) -> Result<BinaryReport> {
let header = hardening::analyze_hardening(path)?;
let sizes = size::analyze_sizes(path, source_root)?;
Ok(BinaryReport {
header,
sections: sizes.sections,
symbols: sizes.symbols,
crates: sizes.crates,
})
}
#[cfg(test)]
mod tests {
use super::*;
/// End-to-end red-when-broken guard, reusing the self-exe pattern from
/// `artifact.rs`: analyse the test binary itself (always present, always
/// carries a symtab + DWARF under the `test` profile) and assert the whole
/// report is coherent — non-empty section + symbol sizes, a per-crate
/// roll-up, a DWARF-attributed source location, and a decoded hardening
/// verdict. Any regression in the section/symbol walk, the DWARF join, or the
/// checksec decode turns this red.
#[test]
fn analyze_selfexe_report() {
let bin = std::env::current_exe().expect("current test exe");
let rep = analyze(&bin, Some(Path::new("/"))).expect("analyze");
// Header / hardening verdict decoded.
assert!(rep.header.total_bytes > 0, "total bytes");
assert!(
matches!(rep.header.relro.as_str(), "none" | "partial" | "full"),
"relro verdict decoded"
);
assert!(!rep.header.arch.is_empty(), "arch decoded");
// Sizes non-empty.
assert!(
rep.sections
.iter()
.any(|s| s.name == ".text" && s.size_bytes > 0),
"non-empty .text section"
);
assert!(
rep.symbols.iter().any(|s| s.size_bytes > 0),
"sized symbols"
);
assert!(!rep.crates.is_empty(), "per-crate roll-up");
// DWARF join proven: at least one symbol carries a source file.
assert!(
rep.symbols.iter().any(|s| s.source_file.is_some()),
"≥1 symbol attributed to a DWARF source file"
);
}
}