Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

src/mimir_lens/zombie.rs

//! **Lens B — zombie cargo features.**
//!
//! Two ways a cargo feature is dead weight, both read from the code room
//! (mimir-schema 2 — feature nodes, `ENABLES`, `GATED_BY`):
//!
//! * **never built** — the manifest declares the feature and no crate of the
//!   workspace was COMPILED with it (no `ENABLES` edge from a crate node). The
//!   code it gates never reached rustc: it is in the room only as
//!   `cfg_gated_out` stubs from the driver's token scan. Exact.
//! * **unreached** — the feature IS built, an item exists only under it (its
//!   own `#[cfg]`, `GATED_BY` with `partial = false`), and nothing calls or
//!   names that item. Judged on compiled items only: a compiled-out stub has no
//!   inbound edge by construction, so a stub is evidence for never-built and
//!   NEVER for unreached.
//!
//! An item that merely CONTAINS code conditional on a feature (`partial =
//! true`: a `#[cfg]` on a statement, a `cfg!(…)`) is counted beside the
//! finding, never judged — the item is there either way.

use std::collections::{BTreeMap, BTreeSet};

use serde::{Deserialize, Serialize};

use super::sources::{Feed, evidence_note};

/// Which way a feature is a zombie.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Direction {
    Unreached,
    NeverBuilt,
    Both,
}

impl Direction {
    pub fn label(self) -> &'static str {
        match self {
            Direction::Unreached => "unreached",
            Direction::NeverBuilt => "never-built",
            Direction::Both => "never-built+unreached",
        }
    }

    pub fn severe(self) -> bool {
        matches!(self, Direction::NeverBuilt | Direction::Both)
    }
}

/// One zombie feature.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ZombieFeature {
    pub repo: String,
    pub feature: String,
    pub direction: Direction,
    /// Items that exist only under the feature (their own `cfg`).
    pub gated_items: u32,
    /// Of those, the ones the build compiled OUT (`cfg_gated_out` stubs).
    pub compiled_out_items: u32,
    /// Items that merely contain code conditional on the feature.
    pub partial_items: u32,
    /// Items that exist only when the feature is OFF (`not(feature = …)`).
    pub negated_items: u32,
    /// CALLS + USES_TYPE sites arriving at the compiled gated items.
    pub inbound_calls: u32,
    /// Bytes in the latest Build Thing artifact built from the repo; `None` =
    /// none filed yet.
    pub bytes_in_binary: Option<u64>,
    pub sites: Vec<String>,
}

/// The whole reading.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ZombieReport {
    pub features: Vec<ZombieFeature>,
    /// Zombies found, before the list was cut to the request's limit.
    pub zombies_total: usize,
    /// Of those, the never-built ones.
    pub severe_total: usize,
    pub features_seen: usize,
    pub repos_with_gates: usize,
    pub build_space_unknown: Vec<String>,
    pub feeds: Vec<Feed>,
    pub resolution: String,
    pub evidence_note: String,
}

impl ZombieReport {
    pub fn severe(&self) -> Vec<&ZombieFeature> {
        self.features
            .iter()
            .filter(|f| f.direction.severe())
            .collect()
    }

    pub fn state_json(&self) -> serde_json::Value {
        serde_json::json!({
            "kind": "zombie",
            "features_seen": self.features_seen,
            "repos_with_gates": self.repos_with_gates,
            "build_space_unknown": self.build_space_unknown,
            "zombies": self.zombies_total,
            "severe": self.severe_total,
            "zombies_shown": self.features.len(),
            "resolution": self.resolution,
            "evidence_note": self.evidence_note,
            "feeds": self.feeds,
            "worst": self.features.iter().map(|f| serde_json::json!({
                "repo": f.repo, "feature": f.feature, "direction": f.direction.label(),
                "gated_items": f.gated_items, "compiled_out_items": f.compiled_out_items,
                "partial_items": f.partial_items, "negated_items": f.negated_items,
                "inbound_calls": f.inbound_calls,
                "bytes_in_binary": f.bytes_in_binary, "sites": f.sites,
            })).collect::<Vec<_>>(),
        })
    }
}

/// **Extract the feature names a `cfg` expression tests.**
pub fn features_in_cfg(cfg_expr: &str) -> Vec<String> {
    let mut out = Vec::new();
    let mut rest = cfg_expr;
    while let Some(i) = rest.find("feature") {
        rest = &rest[i + "feature".len()..];
        let Some(eq) = rest.find('=') else { break };
        let after = &rest[eq + 1..];
        let after = after.trim_start();
        let Some(stripped) = after.strip_prefix('"') else {
            continue;
        };
        let Some(end) = stripped.find('"') else { break };
        let name = &stripped[..end];
        if !name.is_empty() {
            out.push(name.to_string());
        }
        rest = &stripped[end..];
    }
    out.sort();
    out.dedup();
    out
}

/// One `GATED_BY` edge as the room holds it: an item, and a feature that
/// gates it or a part of it.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Gate {
    pub repo: String,
    /// `crate/feature` — a feature belongs to ONE crate; two crates of a repo
    /// may each have a `testmatrix`.
    pub feature: String,
    /// The gated item's path.
    pub item: String,
    /// `file:line` of the item — what tells two items of one path apart (the
    /// kept `fn f` and its compiled-out twin under the other configuration).
    pub site: String,
    /// `partial = false`: the item's OWN `cfg` names the feature.
    pub own: bool,
    /// The build compiled the item (`false` = a `cfg_gated_out` stub).
    pub compiled: bool,
    /// Every mention of the feature sits under `not(…)`: the item is there
    /// when the feature is OFF.
    pub negated: bool,
    /// CALLS + USES_TYPE sites arriving at the item.
    pub inbound: u32,
    /// Bytes the item's symbols take in the latest Build Thing artifact built
    /// from its repo (builder 2's filings); `None` = no Build Thing run has
    /// filed an analysed artifact built from the repo yet.
    pub bytes: Option<u64>,
}

/// What the build says about a repo's features: the room's feature nodes
/// (`declared` = a manifest declares it) and its `ENABLES` edges from crate
/// nodes (`built` = some crate was compiled with it). Keys are `crate/feature`.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ManifestFacts {
    pub declared: BTreeSet<String>,
    pub built: BTreeSet<String>,
    pub build_space_known: bool,
}

/// What a repo's own files say turns its features on — beside what the
/// consumed build compiled (ENABLES). Labels are `package/feature`, `-` and
/// `_` alike.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Enablers {
    /// Named on by a manifest (a dependency's `features = [..]`, dev and
    /// optional ones included; a `[features]` line forwarding to it) or by a
    /// variant of `.nornir/build-variants.toml`.
    pub enabled: BTreeSet<String>,
    /// A `[[bin]]`'s `required-features`: an OPT-IN target — never a zombie,
    /// never "unreached".
    pub opt_in: BTreeSet<String>,
    /// Feature names the repo's build-variants offer or excuse (`offers`,
    /// `allow_unbuilt`): built on purpose by a variant, or unbuilt by decision.
    pub excused: BTreeSet<String>,
}

fn label(pkg: &str, feature: &str) -> String {
    format!("{}/{feature}", pkg.replace('_', "-"))
}

impl Enablers {
    /// Read one `Cargo.toml`'s text.
    pub fn add_manifest(&mut self, text: &str) {
        let Ok(t) = text.parse::<toml::Table>() else { return };
        let pkg = t.get("package").and_then(|p| p.get("name")).and_then(toml::Value::as_str).map(str::to_string);
        if let Some(pkg) = &pkg {
            for (_, items) in t.get("features").and_then(toml::Value::as_table).into_iter().flatten() {
                for item in items.as_array().into_iter().flatten().filter_map(toml::Value::as_str) {
                    match item.split_once('/') {
                        Some((dep, f)) => {
                            self.enabled.insert(label(dep.trim_end_matches('?'), f));
                        }
                        None if !item.starts_with("dep:") => {
                            self.enabled.insert(label(pkg, item));
                        }
                        None => {}
                    }
                }
            }
            for bin in t.get("bin").and_then(toml::Value::as_array).into_iter().flatten() {
                for f in bin.get("required-features").and_then(toml::Value::as_array).into_iter().flatten().filter_map(toml::Value::as_str) {
                    self.opt_in.insert(label(pkg, f));
                }
            }
        }
        let mut deps = |table: Option<&toml::Value>| {
            for (key, spec) in table.and_then(toml::Value::as_table).into_iter().flatten() {
                let name = spec.get("package").and_then(toml::Value::as_str).unwrap_or(key);
                for f in spec.get("features").and_then(toml::Value::as_array).into_iter().flatten().filter_map(toml::Value::as_str) {
                    self.enabled.insert(label(name, f));
                }
            }
        };
        for sec in ["dependencies", "dev-dependencies", "build-dependencies"] {
            deps(t.get(sec));
        }
        deps(t.get("workspace").and_then(|w| w.get("dependencies")));
        for (_, target) in t.get("target").and_then(toml::Value::as_table).into_iter().flatten() {
            for sec in ["dependencies", "dev-dependencies", "build-dependencies"] {
                deps(target.get(sec));
            }
        }
    }

    /// Read a `.nornir/build-variants.toml`'s text.
    pub fn add_build_variants(&mut self, text: &str) {
        let Ok(t) = text.parse::<toml::Table>() else { return };
        let Some(build) = t.get("build") else { return };
        for f in build.get("offers").and_then(toml::Value::as_array).into_iter().flatten().filter_map(toml::Value::as_str) {
            self.excused.insert(f.to_string());
        }
        for (f, _) in build.get("allow_unbuilt").and_then(toml::Value::as_table).into_iter().flatten() {
            self.excused.insert(f.clone());
        }
        for v in build.get("variant").and_then(toml::Value::as_array).into_iter().flatten() {
            let pkg = v.get("package").and_then(toml::Value::as_str).unwrap_or("");
            for f in v.get("features").and_then(toml::Value::as_array).into_iter().flatten().filter_map(toml::Value::as_str) {
                match f.split_once('/') {
                    Some((dep, f)) => self.enabled.insert(label(dep, f)),
                    None => self.enabled.insert(label(pkg, f)),
                };
            }
        }
    }

    /// Is the gate's feature (`crate/feature`) out of the zombie verdicts:
    /// an opt-in target's, or offered / excused by the build-variants?
    pub fn exempt(&self, feature: &str) -> bool {
        let norm = feature.replace('_', "-");
        let name = feature.rsplit('/').next().unwrap_or(feature);
        self.opt_in.contains(&norm) || self.excused.contains(name)
    }

    /// Does a file of the repo turn the feature on?
    pub fn enables(&self, feature: &str) -> bool {
        self.enabled.contains(&feature.replace('_', "-"))
    }
}

/// **Find the zombie features.** `resolution` says what the inbound counts
/// were resolved by; `feeds` is the record of what was read.
pub fn analyze(gates: &[Gate], manifests: &BTreeMap<String, ManifestFacts>, feeds: Vec<Feed>, resolution: &str, top_n: usize) -> ZombieReport {
    #[derive(Default)]
    struct Acc<'a> {
        own: BTreeSet<&'a str>,
        compiled_out: BTreeSet<&'a str>,
        partial: BTreeSet<&'a str>,
        negated: BTreeSet<&'a str>,
        /// the compiled, non-negated own gates: the only ones `unreached` is judged on
        judged: BTreeSet<&'a str>,
        inbound: u32,
        bytes: Option<u64>,
        sites: Vec<String>,
    }
    let mut by_feature: BTreeMap<(String, String), Acc<'_>> = BTreeMap::new();
    let mut seen_features: BTreeSet<(String, String)> = BTreeSet::new();
    let mut repos_with_gates: BTreeSet<String> = BTreeSet::new();
    for g in gates {
        repos_with_gates.insert(g.repo.clone());
        seen_features.insert((g.repo.clone(), g.feature.clone()));
        let e = by_feature.entry((g.repo.clone(), g.feature.clone())).or_default();
        if !g.own {
            e.partial.insert(&g.item);
            continue;
        }
        if g.negated {
            // there when the feature is OFF: no part of what the feature adds
            e.negated.insert(&g.site);
            continue;
        }
        e.own.insert(&g.site);
        if e.sites.len() < 5 {
            e.sites.push(format!("{} {}", g.site, g.item));
        }
        if !g.compiled {
            e.compiled_out.insert(&g.site);
        } else if e.judged.insert(&g.site) {
            e.inbound += g.inbound;
            if let Some(b) = g.bytes {
                *e.bytes.get_or_insert(0) += b;
            }
        }
    }

    let mut out: Vec<ZombieFeature> = Vec::new();
    for ((repo, feature), acc) in &by_feature {
        let mf = manifests.get(repo);
        let never_built = mf.map(|m| m.build_space_known && m.declared.contains(feature) && !m.built.contains(feature)).unwrap_or(false);
        // nothing compiled exists only under the feature: `unreached` cannot be judged
        let unreached = !acc.judged.is_empty() && acc.inbound == 0;
        let direction = match (unreached, never_built) {
            (true, true) => Direction::Both,
            (false, true) => Direction::NeverBuilt,
            (true, false) => Direction::Unreached,
            (false, false) => continue,
        };
        out.push(ZombieFeature {
            repo: repo.clone(),
            feature: feature.clone(),
            direction,
            gated_items: acc.own.len() as u32,
            compiled_out_items: acc.compiled_out.len() as u32,
            partial_items: acc.partial.len() as u32,
            negated_items: acc.negated.len() as u32,
            inbound_calls: acc.inbound,
            bytes_in_binary: acc.bytes,
            sites: acc.sites.clone(),
        });
    }

    for (repo, m) in manifests {
        for f in &m.declared {
            seen_features.insert((repo.clone(), f.clone()));
        }
        if !m.build_space_known {
            continue;
        }
        for f in m.declared.difference(&m.built) {
            if by_feature.contains_key(&(repo.clone(), f.clone())) {
                continue;
            }
            out.push(ZombieFeature {
                repo: repo.clone(),
                feature: f.clone(),
                direction: Direction::NeverBuilt,
                gated_items: 0,
                compiled_out_items: 0,
                partial_items: 0,
                negated_items: 0,
                inbound_calls: 0,
                bytes_in_binary: None,
                sites: vec!["declared in Cargo.toml; no crate was compiled with it, and no code is gated by it".into()],
            });
        }
    }

    out.sort_by(|a, b| {
        b.direction
            .severe()
            .cmp(&a.direction.severe())
            .then_with(|| b.gated_items.cmp(&a.gated_items))
            .then_with(|| a.repo.cmp(&b.repo))
            .then_with(|| a.feature.cmp(&b.feature))
    });
    let (zombies_total, severe_total) = (out.len(), out.iter().filter(|f| f.direction.severe()).count());
    out.truncate(top_n);

    let mut build_space_unknown: Vec<String> =
        repos_with_gates.iter().filter(|r| !manifests.get(*r).map(|m| m.build_space_known).unwrap_or(false)).cloned().collect();
    build_space_unknown.sort();

    let mut evidence_note = evidence_note(&feeds, "zombie features");
    if !build_space_unknown.is_empty() {
        evidence_note.push_str(&format!(
            " {} repo(s) declare NO build space ({}), so the NEVER-BUILT direction — the \
             one that caught korp's `spark41` — could not be evaluated for them at all. \
             That is UNKNOWN, not clean.",
            build_space_unknown.len(),
            build_space_unknown.join(", ")
        ));
    }
    ZombieReport {
        features: out,
        zombies_total,
        severe_total,
        features_seen: seen_features.len(),
        repos_with_gates: repos_with_gates.len(),
        build_space_unknown,
        feeds,
        resolution: resolution.to_string(),
        evidence_note,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn gate(feature: &str, item: &str, own: bool, compiled: bool, inbound: u32) -> Gate {
        Gate { repo: "korp".into(), feature: feature.into(), item: item.into(), site: format!("{item}.rs:10"), own, compiled, negated: false, inbound, bytes: None }
    }

    fn built(declared: &[&str], built: &[&str]) -> BTreeMap<String, ManifestFacts> {
        let set = |v: &[&str]| v.iter().map(|s| s.to_string()).collect();
        [("korp".to_string(), ManifestFacts { declared: set(declared), built: set(built), build_space_known: true })].into()
    }

    fn feeds() -> Vec<Feed> {
        vec![Feed { table: "code_edges:GATED_BY".into(), rows: 3, root: "code room".into(), missing: false }]
    }

    #[test]
    fn cfg_expressions_yield_the_features_they_mention() {
        assert_eq!(features_in_cfg(r#"feature = "spark41""#), vec!["spark41"]);
        assert_eq!(features_in_cfg(r#"not(feature = "std")"#), vec!["std"]);
        assert!(features_in_cfg("target_os = \"linux\"").is_empty());
    }

    /// RED before: only ENABLES of the consumed build counted, so a feature
    /// a dev-dependency, an optional dependency's features list, a forwarding
    /// line or a build variant turns on read "never built", and an opt-in
    /// `[[bin]]`'s required feature read as a zombie. The owner's audit
    /// examples: korp/client, draupnir/backend-oci, dwarves-gui/gpu, modgunn/rustsec.
    #[test]
    fn every_manifest_enabler_and_build_variant_counts_and_opt_in_bins_are_no_zombie() {
        let mut e = Enablers::default();
        e.add_manifest(r#"
[package]
name = "korp"
[features]
full = ["client", "draupnir/backend-oci"]
[[bin]]
name = "korp-cli"
required-features = ["client"]
[dev-dependencies]
modgunn = { version = "0.1", features = ["rustsec"] }
[dependencies]
draupnir = { version = "0.2", optional = true, features = ["backend-redfish"] }
"#);
        e.add_build_variants(r#"
[build]
offers = ["backend-youki"]
[build.allow_unbuilt]
backend-tunnr = "decided"
[[build.variant]]
package = "dwarves-gui"
features = ["gpu", "nornir/embed-ort"]
"#);
        for on in ["korp/client", "draupnir/backend-oci", "modgunn/rustsec", "draupnir/backend-redfish", "dwarves-gui/gpu", "nornir/embed-ort"] {
            assert!(e.enables(on), "{on}: {e:?}");
        }
        assert!(e.exempt("korp/client"), "an opt-in bin's required feature");
        assert!(e.exempt("draupnir/backend-youki") && e.exempt("draupnir/backend-tunnr"), "offered / excused by the build variants");
        assert!(!e.exempt("korp/server") && !e.enables("korp/server"));
    }

    #[test]
    fn unreached_gated_code_is_a_finding_and_reached_gated_code_is_not() {
        let g = [gate("korp/spark41", "korp::spark::spark_ingest", true, true, 0), gate("korp/live", "korp::live::live_ingest", true, true, 2)];
        let r = analyze(&g, &built(&["korp/spark41", "korp/live"], &["korp/spark41", "korp/live"]), feeds(), "code room", 20);
        assert_eq!(r.features_seen, 2);
        assert_eq!(r.features.len(), 1);
        assert_eq!(r.features[0].feature, "korp/spark41");
        assert_eq!(r.features[0].direction, Direction::Unreached);
    }

    #[test]
    fn a_compiled_out_stub_is_evidence_for_never_built_and_never_for_unreached() {
        // the stub has no inbound edge by construction
        let g = [gate("korp/spark41", "korp::spark::old", true, false, 0)];
        // built: the stub alone proves nothing
        let r = analyze(&g, &built(&["korp/spark41"], &["korp/spark41"]), feeds(), "code room", 20);
        assert!(r.features.is_empty(), "{:?}", r.features);
        // not built: never-built, and still not "unreached"
        let r = analyze(&g, &built(&["korp/spark41"], &[]), feeds(), "code room", 20);
        assert_eq!(r.features[0].direction, Direction::NeverBuilt);
        assert_eq!((r.features[0].gated_items, r.features[0].compiled_out_items), (1, 1));
    }

    #[test]
    fn an_item_that_only_contains_gated_code_is_counted_not_judged() {
        let g = [gate("korp/trace", "korp::audit", false, true, 0)];
        let r = analyze(&g, &built(&["korp/trace"], &["korp/trace"]), feeds(), "code room", 20);
        assert!(r.features.is_empty(), "an uncalled fn that merely holds a cfg!(…) is not a zombie feature");
        let r = analyze(&g, &built(&["korp/trace"], &[]), feeds(), "code room", 20);
        assert_eq!((r.features[0].direction, r.features[0].gated_items, r.features[0].partial_items), (Direction::NeverBuilt, 0, 1));
    }

    #[test]
    fn a_negated_gate_is_not_judged() {
        // `#[cfg(not(feature = "std"))] fn f` exists when the feature is OFF
        let mut g = gate("korp/std", "korp::no_std_shim", true, true, 0);
        g.negated = true;
        let r = analyze(&[g.clone()], &built(&["korp/std"], &["korp/std"]), feeds(), "code room", 20);
        assert!(r.features.is_empty());
        // and beside its compiled-out twin it is counted apart, not as gated code
        let twin = gate("korp/std", "korp::no_std_shim_std", true, false, 0);
        let r = analyze(&[g, twin], &built(&["korp/std"], &[]), feeds(), "code room", 20);
        assert_eq!((r.features[0].gated_items, r.features[0].compiled_out_items, r.features[0].negated_items), (1, 1, 1));
        assert_eq!(r.features[0].sites.len(), 1, "the negated item is no site of the feature: {:?}", r.features[0].sites);
    }

    #[test]
    fn the_totals_are_counted_before_the_list_is_cut() {
        let r = analyze(&[], &built(&["korp/a", "korp/b", "korp/c"], &[]), feeds(), "code room", 2);
        assert_eq!((r.features.len(), r.zombies_total, r.severe_total), (2, 3, 3));
        assert_eq!(r.state_json()["zombies"], 3);
    }

    #[test]
    fn a_declared_feature_nothing_was_compiled_with_is_never_built_even_without_gated_code() {
        let r = analyze(&[], &built(&["korp/gen", "korp/live"], &["korp/live"]), feeds(), "code room", 20);
        assert_eq!(r.features.len(), 1);
        assert_eq!((r.features[0].feature.as_str(), r.features[0].direction), ("korp/gen", Direction::NeverBuilt));
        assert_eq!(r.features_seen, 2);
    }

    #[test]
    fn a_repo_whose_build_space_is_unknown_is_named_not_called_clean() {
        let g = [gate("korp/live", "korp::live::live_ingest", true, true, 2)];
        let r = analyze(&g, &BTreeMap::new(), feeds(), "code room", 5);
        assert_eq!(r.build_space_unknown, vec!["korp".to_string()]);
        assert!(r.evidence_note.contains("UNKNOWN, not clean"), "{}", r.evidence_note);
    }
}