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);
}
}