#![allow(clippy::panic)]
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use std::fs;
use std::path::{Path, PathBuf};
use syn::visit::Visit;
mod shared_checks {
include!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/build_support/shared_checks.rs"
));
}
fn fail(msg: &str) -> ! {
println!("cargo:warning=build.rs: {msg}");
panic!("build.rs invariant failed: {msg}")
}
#[derive(Debug, Deserialize)]
struct PubItemAllowlistEntry {
name: String,
justification: String,
#[serde(default)]
witness: Vec<PubItemAllowlistWitness>,
}
#[derive(Debug, Deserialize)]
struct PubItemAllowlistWitness {
path: String,
#[serde(default)]
lines: Vec<u32>,
}
fn main() {
let repo_invariants_available = repo_invariant_surface_available();
println!("cargo:rerun-if-env-changed=BATPAK_PLATFORM_PROFILE");
println!("cargo:rerun-if-changed=Cargo.toml");
println!("cargo:rerun-if-changed=src/");
println!("cargo:rerun-if-changed=examples/");
println!("cargo:rerun-if-changed=build_support/shared_checks.rs");
if repo_invariants_available {
println!("cargo:rerun-if-changed=../../Cargo.toml");
println!("cargo:rerun-if-changed=tests/");
println!("cargo:rerun-if-changed=benches/");
println!("cargo:rerun-if-changed=../macros/src/");
println!("cargo:rerun-if-changed=../macros-support/src/");
println!("cargo:rerun-if-changed=../../tools/xtask/src/");
println!("cargo:rerun-if-changed=../../tools/integrity/src/");
println!("cargo:rerun-if-changed=../../traceability/dead_code_silencer_allowlist.yaml");
println!("cargo:rerun-if-changed=../../traceability/pub_item_allowlist.yaml");
println!("cargo:rerun-if-changed=../../traceability/invariants.yaml");
println!("cargo:rerun-if-changed=../../../README.md");
println!("cargo:rerun-if-changed=../../../MODEL.md");
println!("cargo:rerun-if-changed=../../../INVARIANTS.md");
println!("cargo:rerun-if-changed=../../../CONFORMANCE.md");
println!(
"cargo:rerun-if-changed=../../../archive/decisions/100_ADR_0001_SYNC_ONLY_STORE.md"
);
}
check_no_tokio_in_deps();
check_no_banned_patterns();
check_store_config_field_usage();
if repo_invariants_available {
check_no_dead_code_silencers();
check_allow_justifications();
}
check_no_stubs_in_src();
check_store_surface_honesty();
check_no_fixed_temp_patterns();
if repo_invariants_available {
check_pub_items_have_tests();
}
check_platform_profile_env();
}
fn repo_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..")
}
fn core_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
fn repo_invariant_surface_available() -> bool {
let repo_root = repo_root();
[
repo_root.join("Cargo.toml"),
repo_root.join("traceability/dead_code_silencer_allowlist.yaml"),
repo_root.join("traceability/pub_item_allowlist.yaml"),
repo_root.join("traceability/invariants.yaml"),
repo_root.join("tools/xtask/src"),
repo_root.join("tools/integrity/src"),
]
.iter()
.all(|path| path.exists())
}
fn repo_relative_display(path: &Path) -> String {
path.display().to_string().replace('\\', "/")
}
#[derive(Debug, Serialize, Deserialize)]
struct BuildPlatformProfile {
schema_version: u16,
host: BuildPlatformProfileHost,
store_path: BuildStorePathProfile,
admission: BuildPlatformAdmissionProfile,
fingerprint_crc32: u32,
}
#[derive(Debug, Serialize, Deserialize)]
struct BuildPlatformProfileHost {
monotonic_clock: BuildClockEvidence,
}
#[derive(Debug, Serialize, Deserialize)]
struct BuildStorePathProfile {
path_status: BuildStorePathStatusEvidence,
parent_dir_sync: BuildParentDirSyncEvidence,
lock_leaf_symlink_protection: BuildLockLeafSymlinkProtection,
mmap_index: BuildMmapEvidence,
sealed_segment_mmap: BuildMmapEvidence,
active_segment_read: BuildActiveSegmentReadEvidence,
}
#[derive(Debug, Serialize, Deserialize)]
struct BuildPlatformAdmissionProfile {
store_lock: BuildStoreLockAdmissionSummary,
parent_dir_sync: BuildParentDirSyncAdmissionSummary,
mmap_index: BuildMmapAdmissionSummary,
sealed_segment_mmap: BuildMmapAdmissionSummary,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
enum BuildClockEvidence {
ProcessLocalInstantAnchor,
Unknown,
ProbeFailed,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
enum BuildStorePathStatusEvidence {
ObservedDirectory,
UnknownMissing,
ObservedUnsupportedNotDirectory,
ProbeFailed { reason: String },
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
enum BuildParentDirSyncEvidence {
UnixFsync,
RenameOnly,
Unknown,
ProbeFailed,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
enum BuildLockLeafSymlinkProtection {
AtomicNoFollow,
BestEffortCheckThenOpen,
Unknown,
ObservedUnsupported,
ProbeFailed,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
enum BuildMmapEvidence {
FileBacked,
Unknown,
ObservedUnsupported,
ProbeFailed,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
enum BuildActiveSegmentReadEvidence {
UnixReadAt,
LockedSeekRead,
Unknown,
ProbeFailed,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
enum BuildStoreLockAdmissionSummary {
AtomicNoFollow,
BestEffortCheckThenOpen,
Rejected,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
enum BuildParentDirSyncAdmissionSummary {
UnixFsync,
RenameOnly,
Rejected,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
enum BuildMmapAdmissionSummary {
FileBacked,
Rejected,
}
#[derive(Serialize)]
struct BuildPlatformProfileBody<'a> {
schema_version: u16,
host: &'a BuildPlatformProfileHost,
store_path: &'a BuildStorePathProfile,
admission: &'a BuildPlatformAdmissionProfile,
}
fn check_platform_profile_env() {
let Ok(path) = std::env::var("BATPAK_PLATFORM_PROFILE") else {
return;
};
println!("cargo:rerun-if-changed={path}");
let bytes = fs::read(&path).unwrap_or_else(|error| {
fail(&format!(
"cannot read BATPAK_PLATFORM_PROFILE={path}: {error}"
))
});
let profile: BuildPlatformProfile = serde_json::from_slice(&bytes).unwrap_or_else(|error| {
fail(&format!(
"cannot decode BATPAK_PLATFORM_PROFILE={path}: {error}"
))
});
if profile.schema_version != 1 {
fail(&format!(
"BATPAK_PLATFORM_PROFILE={path} has schema_version {}; expected 1",
profile.schema_version
));
}
validate_build_platform_profile_semantics(&profile, &path);
let body = BuildPlatformProfileBody {
schema_version: profile.schema_version,
host: &profile.host,
store_path: &profile.store_path,
admission: &profile.admission,
};
let body_bytes = serde_json::to_vec(&body).unwrap_or_else(|error| {
fail(&format!(
"cannot canonicalize BATPAK_PLATFORM_PROFILE={path}: {error}"
))
});
let computed = crc32fast::hash(&body_bytes);
if computed != profile.fingerprint_crc32 {
fail(&format!(
"BATPAK_PLATFORM_PROFILE={path} fingerprint_crc32 {} does not match computed {}",
profile.fingerprint_crc32, computed
));
}
println!("cargo:rustc-env=BATPAK_PLATFORM_PROFILE_PATH={path}");
println!("cargo:rustc-env=BATPAK_PLATFORM_PROFILE_FINGERPRINT_CRC32={computed}");
}
fn validate_build_platform_profile_semantics(profile: &BuildPlatformProfile, path: &str) {
let expected_store_lock = match profile.store_path.lock_leaf_symlink_protection {
BuildLockLeafSymlinkProtection::AtomicNoFollow => {
BuildStoreLockAdmissionSummary::AtomicNoFollow
}
BuildLockLeafSymlinkProtection::BestEffortCheckThenOpen => {
BuildStoreLockAdmissionSummary::BestEffortCheckThenOpen
}
BuildLockLeafSymlinkProtection::Unknown
| BuildLockLeafSymlinkProtection::ObservedUnsupported
| BuildLockLeafSymlinkProtection::ProbeFailed => BuildStoreLockAdmissionSummary::Rejected,
};
if profile.admission.store_lock != expected_store_lock {
fail(&format!(
"BATPAK_PLATFORM_PROFILE={path} has inconsistent store_lock admission {:?}; expected {:?} from lock evidence {:?}",
profile.admission.store_lock,
expected_store_lock,
profile.store_path.lock_leaf_symlink_protection
));
}
let expected_parent_dir_sync = match profile.store_path.parent_dir_sync {
BuildParentDirSyncEvidence::UnixFsync => BuildParentDirSyncAdmissionSummary::UnixFsync,
BuildParentDirSyncEvidence::RenameOnly => BuildParentDirSyncAdmissionSummary::RenameOnly,
BuildParentDirSyncEvidence::Unknown | BuildParentDirSyncEvidence::ProbeFailed => {
BuildParentDirSyncAdmissionSummary::Rejected
}
};
if profile.admission.parent_dir_sync != expected_parent_dir_sync {
fail(&format!(
"BATPAK_PLATFORM_PROFILE={path} has inconsistent parent_dir_sync admission {:?}; expected {:?} from parent-dir evidence {:?}",
profile.admission.parent_dir_sync,
expected_parent_dir_sync,
profile.store_path.parent_dir_sync
));
}
validate_build_path_mmap_consistency(path, "mmap_index", profile);
validate_build_path_mmap_consistency(path, "sealed_segment_mmap", profile);
validate_build_mmap_admission(
path,
"mmap_index",
&profile.store_path.mmap_index,
&profile.admission.mmap_index,
);
validate_build_mmap_admission(
path,
"sealed_segment_mmap",
&profile.store_path.sealed_segment_mmap,
&profile.admission.sealed_segment_mmap,
);
}
fn validate_build_path_mmap_consistency(path: &str, field: &str, profile: &BuildPlatformProfile) {
let evidence = match field {
"mmap_index" => &profile.store_path.mmap_index,
"sealed_segment_mmap" => &profile.store_path.sealed_segment_mmap,
_ => fail(&format!(
"internal build profile validation bug: unknown mmap field {field}"
)),
};
let required = match profile.store_path.path_status {
BuildStorePathStatusEvidence::ObservedDirectory => return,
BuildStorePathStatusEvidence::ObservedUnsupportedNotDirectory => {
BuildMmapEvidence::ObservedUnsupported
}
BuildStorePathStatusEvidence::UnknownMissing => BuildMmapEvidence::Unknown,
BuildStorePathStatusEvidence::ProbeFailed { .. } => BuildMmapEvidence::ProbeFailed,
};
if evidence != &required {
fail(&format!(
"BATPAK_PLATFORM_PROFILE={path} has inconsistent {field} evidence {evidence:?}; expected {required:?} from path_status {:?}",
profile.store_path.path_status
));
}
}
fn validate_build_mmap_admission(
path: &str,
field: &str,
evidence: &BuildMmapEvidence,
admission: &BuildMmapAdmissionSummary,
) {
let expected = match evidence {
BuildMmapEvidence::FileBacked => BuildMmapAdmissionSummary::FileBacked,
BuildMmapEvidence::Unknown
| BuildMmapEvidence::ObservedUnsupported
| BuildMmapEvidence::ProbeFailed => BuildMmapAdmissionSummary::Rejected,
};
if admission != &expected {
fail(&format!(
"BATPAK_PLATFORM_PROFILE={path} has inconsistent {field} admission {admission:?}; expected {expected:?} from mmap evidence {evidence:?}"
));
}
}
fn check_no_stubs_in_src() {
let stub_patterns = [
(
"\"placeholder\"",
"Placeholder string literal — replace with real implementation",
),
(
"\"not implemented\"",
"Stub string — implement the real behavior or return a typed error",
),
(
"\"not yet implemented\"",
"Stub string — implement the real behavior",
),
];
walk_rs_files(Path::new("src"), &mut |path, contents| {
let path_str = repo_relative_display(path);
for (line_no, line) in contents.lines().enumerate() {
let lower = line.to_lowercase();
for (pattern, msg) in &stub_patterns {
if lower.contains(pattern) {
panic!(
"STUB DETECTED in {path_str}:{}: {msg}\n\
Line: {line}\n\
LAW-001: No fake success responses. FM-009: No polite downgrades.",
line_no + 1
);
}
}
}
});
}
fn check_no_dead_code_silencers() {
let repo_root = repo_root();
let allowlisted = shared_checks::load_dead_code_silencer_allowlist(&repo_root)
.unwrap_or_else(|err| fail(&err));
walk_dead_code_checked_rs_files(&mut |path, contents| {
let rel = path
.strip_prefix(&repo_root)
.unwrap_or(path)
.to_string_lossy()
.replace('\\', "/");
let sites =
shared_checks::collect_dead_code_silencer_sites(contents).unwrap_or_else(|err| {
fail(&format!(
"cannot parse {} while checking dead_code silencers: {err}",
rel
))
});
for site in sites {
let allowlist_site = format!("{rel}:{}", site.line);
if allowlisted.contains(&allowlist_site) {
continue;
}
fail(&format!(
"dead_code silencers are not tolerated in this repo: {rel}:{}:{}\n\
Found `{}`.\n\
If code is test-only, use #[cfg(test)]. If it is unused, delete it.\n\
If it is shared infrastructure, restructure it so the compiler sees the real ownership surface.\n\
If this is the rare legitimate exception, add `{allowlist_site}` to traceability/dead_code_silencer_allowlist.yaml with `reason` and `adr`.",
site.line,
site.column,
site.rendered,
));
}
});
}
fn check_allow_justifications() {
let repo_root = repo_root();
let known_invariants =
shared_checks::load_known_invariants(&repo_root).unwrap_or_else(|err| fail(&err));
walk_allow_checked_rs_files(&mut |path, contents| {
let path_str = repo_relative_display(path);
let lines: Vec<&str> = contents.lines().collect();
for (line_no, line) in lines.iter().enumerate() {
let trimmed = line.trim();
if trimmed.starts_with("#![allow(") || trimmed.starts_with("#[allow(") {
let has_justification =
shared_checks::line_carries_justification(line, &repo_root, &known_invariants)
|| (line_no > 0
&& lines
.get(line_no - 1)
.map(|prev| {
shared_checks::line_carries_justification(
prev,
&repo_root,
&known_invariants,
)
})
.unwrap_or(false));
if !has_justification {
fail(&format!(
"ROGUE SILENCE in {path_str}:{}: `{trimmed}`\n\
Every #[allow(...)] must carry a `// justifies: <>=5 words + >=1 resolvable anchor>`\n\
comment on the same or preceding line. Anchors: INV-<NAME> from\n\
traceability/invariants.yaml, ADR-NNNN from root ADR files, or a concrete repo path\n\
(src/..., tests/..., examples/..., crates/macros/..., crates/macros-support/...,\n\
benches/..., tools/..., build.rs). See INV-ALLOW-IS-DESIGN.",
line_no + 1
));
}
}
}
});
}
fn check_no_tokio_in_deps() {
let cargo = fs::read_to_string("Cargo.toml").expect("read Cargo.toml");
if let Some(deps_section) = cargo.split("[dependencies]").nth(1) {
let deps_only = deps_section.split("\n[").next().unwrap_or("");
if deps_only.contains("tokio") {
panic!(
"INVARIANT 1 VIOLATED: tokio found in [dependencies].\n\
tokio belongs in [dev-dependencies] only.\n\
The library is runtime-agnostic. Fan-out uses Vec<flume::Sender>.\n\
See: INVARIANTS.md."
);
}
}
}
fn check_no_banned_patterns() {
walk_rs_files(Path::new("src"), &mut |path, contents| {
let path_str = repo_relative_display(path);
for banned in ["transmute", "mem::read", "pointer_cast"] {
if contents.contains(banned) {
panic!(
"RED FLAG VIOLATED in {path_str}: found `{banned}`.\n\
repr(C) is for field ordering, not a wire format.\n\
All serialization goes through rmp-serde. Always.\n\
See: INVARIANTS.md."
);
}
}
if path_str.contains("store") && contents.contains("async fn") {
panic!(
"INVARIANT 2 VIOLATED in {path_str}: found `async fn`.\n\
Store API is sync. Async callers use spawn_blocking()\n\
or flume's recv_async(). See: store/subscription.rs.\n\
See: INVARIANTS.md."
);
}
if contents.contains("std::thread::spawn(") {
panic!(
"BANNED PATTERN in {path_str}: `std::thread::spawn()` found.\n\
Use `std::thread::Builder::new().name(...).spawn()` instead.\n\
`thread::spawn` panics on failure; `Builder::spawn` returns Result.\n\
See: Bug 7 post-mortem (react_loop panic)."
);
}
if path_str.contains("store") && !path_str.ends_with("segment.rs") {
for (line_no, line) in contents.lines().enumerate() {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("///") {
continue;
}
if trimmed.contains(".sync()")
&& !trimmed.contains("sync_with_mode")
&& !trimmed.contains("self.sync()")
&& !trimmed.contains("force_sync()")
{
panic!(
"BANNED PATTERN in {path_str}:{}: bare `.sync()` call.\n\
Use `.sync_with_mode(&config.sync.mode)` instead.\n\
Bare .sync() hardcodes SyncAll, ignoring the user's config.\n\
See: Bug 9 post-mortem (segment rotation bypassed sync.mode).\n\
Line: {trimmed}",
line_no + 1
);
}
}
}
const INV3_ARTIFACT_ALLOWED_PATH: &str = "src/artifact.rs";
#[inline]
fn inv3_declaration_has_word(lower: &str, noun: &str) -> bool {
lower
.split(|c: char| !c.is_alphanumeric() && c != '_')
.any(|word| {
word == noun
|| word.starts_with(&format!("{noun}_"))
|| word.ends_with(&format!("_{noun}"))
|| word.contains(&format!("_{noun}_"))
})
}
for line in contents.lines() {
let trimmed = line.trim();
if trimmed.starts_with("//") || trimmed.starts_with("///") {
continue;
}
let is_decl = trimmed.starts_with("pub ")
|| trimmed.starts_with("fn ")
|| trimmed.starts_with("struct ")
|| trimmed.starts_with("enum ")
|| trimmed.starts_with("type ");
if !is_decl {
continue;
}
let lower = trimmed.to_lowercase();
for noun in ["trajectory"] {
if inv3_declaration_has_word(&lower, noun) {
panic!(
"INVARIANT 3 VIOLATED in {path_str}: \
product concept `{noun}` in declaration:\n {trimmed}\n\
Library vocabulary: coordinate, entity, event, outcome, \
gate, region, transition.\n\
See: INVARIANTS.md."
);
}
}
if inv3_declaration_has_word(&lower, "artifact") {
let artifact_decl_ok_in_lib =
path_str == "src/lib.rs" && trimmed == "pub mod artifact;";
let artifact_decl_ok_in_prelude =
path_str == "src/prelude.rs" && trimmed.starts_with("pub use crate::artifact");
if !(path_str == INV3_ARTIFACT_ALLOWED_PATH
|| artifact_decl_ok_in_lib
|| artifact_decl_ok_in_prelude)
{
panic!(
"INVARIANT 3 VIOLATED in {path_str}: \
product concept `artifact` in declaration:\n {trimmed}\n\
Lane-A exception: definitions only in `{INV3_ARTIFACT_ALLOWED_PATH}`; \
crate wiring may use `pub mod artifact;` in `src/lib.rs` and \
`pub use crate::artifact::{{...}}` in `src/prelude.rs`.\n\
See: INVARIANTS.md."
);
}
}
}
});
}
fn check_store_surface_honesty() {
let store_mod =
fs::read_to_string("src/store/mod.rs").expect("read src/store/mod.rs for surface check");
if store_mod.contains("pub fn subscribe(") {
panic!(
"PUBLIC API HONESTY VIOLATION: src/store/mod.rs still exports `pub fn subscribe(`.\n\
The lossy broadcast API must be named `subscribe_lossy` so callers cannot\n\
confuse it with guaranteed delivery."
);
}
if store_mod.contains("pub fn cursor(") {
panic!(
"PUBLIC API HONESTY VIOLATION: src/store/mod.rs still exports `pub fn cursor(`.\n\
The guaranteed replay API must be named `cursor_guaranteed`."
);
}
if store_mod.contains("Freshness::BestEffort") || store_mod.contains("BestEffort") {
panic!(
"PUBLIC API HONESTY VIOLATION: stale `Freshness::BestEffort` reference in src/store/mod.rs.\n\
Use `Freshness::MaybeStale {{ max_stale_ms }}`."
);
}
walk_rs_files(Path::new("src/store"), &mut |path, contents| {
let path_str = repo_relative_display(path);
if contents.contains("test-support") {
panic!(
"FEATURE HONESTY VIOLATION in {path_str}: stale `test-support` reference.\n\
The explicit risk-bearing feature name is `dangerous-test-hooks`."
);
}
});
}
fn check_no_fixed_temp_patterns() {
walk_rs_files(Path::new("src/store"), &mut |path, contents| {
let path_str = repo_relative_display(path);
if contents.contains("index.ckpt.tmp") || contents.contains(".tmp_{pid}_{n}") {
panic!(
"TEMP FILE HARDENING VIOLATION in {path_str}: fixed temp-file pattern found.\n\
Use same-directory `tempfile::NamedTempFile` instead of predictable names."
);
}
if contents.contains("create(true)") && contents.contains("truncate(true)") {
panic!(
"TEMP FILE HARDENING VIOLATION in {path_str}: `create(true)` + `truncate(true)` found.\n\
This is the symlink-clobber shape the release hardening pass bans in src/store."
);
}
});
}
fn check_store_config_field_usage() {
let config_src = fs::read_to_string("src/store/config.rs")
.expect("read src/store/config.rs for config check");
let config_ast = syn::parse_file(&config_src)
.expect("parse src/store/config.rs for config field usage check");
let fields = store_config_public_fields(&config_ast);
if fields.is_empty() {
return;
}
let mut used_fields = BTreeSet::new();
walk_rs_files(Path::new("src"), &mut |path, contents| {
if path
.to_string_lossy()
.replace('\\', "/")
.ends_with("src/store/config.rs")
{
return;
}
let file = syn::parse_file(contents).unwrap_or_else(|err| {
fail(&format!(
"CONFIG FIELD USAGE CHECK PARSE FAILURE in {}: {err}",
path.display()
))
});
let mut collector = StoreConfigFieldAccessCollector::new(&fields);
collector.visit_file(&file);
used_fields.extend(collector.found_fields);
});
for field in &fields {
if !used_fields.contains(field) {
panic!(
"STORE CONFIG FIELD UNUSED: `{field}` is defined in StoreConfig but never \
accessed in any parsed src/ file outside src/store/config.rs.\n\
Every config field must be wired to actual behavior.\n\
Either use the field or remove it from StoreConfig.\n\
See: the historical writer.stack_size / sync.mode bugs that slipped through review."
);
}
}
}
fn store_config_public_fields(file: &syn::File) -> BTreeSet<String> {
for item in &file.items {
if let syn::Item::Struct(item_struct) = item {
if item_struct.ident == "StoreConfig" {
let mut fields = BTreeSet::new();
for field in &item_struct.fields {
if matches!(field.vis, syn::Visibility::Public(_)) {
if let Some(ident) = &field.ident {
fields.insert(ident.to_string());
}
}
}
return fields;
}
}
}
BTreeSet::new()
}
struct StoreConfigFieldAccessCollector<'a> {
tracked_fields: &'a BTreeSet<String>,
found_fields: BTreeSet<String>,
}
impl<'a> StoreConfigFieldAccessCollector<'a> {
fn new(tracked_fields: &'a BTreeSet<String>) -> Self {
Self {
tracked_fields,
found_fields: BTreeSet::new(),
}
}
}
impl Visit<'_> for StoreConfigFieldAccessCollector<'_> {
fn visit_expr_field(&mut self, node: &syn::ExprField) {
if let syn::Member::Named(ident) = &node.member {
let field_name = ident.to_string();
if self.tracked_fields.contains(&field_name) {
self.found_fields.insert(field_name);
}
}
syn::visit::visit_expr_field(self, node);
}
}
fn check_pub_items_have_tests() {
let allowlist = load_pub_item_allowlist();
let allowed_names: BTreeSet<&str> = allowlist.iter().map(|entry| entry.name.as_str()).collect();
for entry in &allowlist {
if entry.witness.is_empty() {
fail(&format!(
"pub_item_allowlist entry `{}` must declare at least one `witness:` path pointing at a test that uses the item; narrative `justification:` is supplementary, not load-bearing",
entry.name
));
}
for witness in &entry.witness {
if !witness.path.starts_with("tests/")
&& !witness.path.starts_with("crates/core/tests/")
{
fail(&format!(
"pub_item_allowlist entry `{}` witness `{}` must point at a file under tests/, not production code",
entry.name, witness.path
));
}
if witness.lines.is_empty() {
fail(&format!(
"pub_item_allowlist entry `{}` witness `{}` must include at least one concrete line hint",
entry.name, witness.path
));
}
let witness_path =
shared_checks::resolve_repo_or_core_path(&repo_root(), Path::new(&witness.path));
let content = match fs::read_to_string(&witness_path) {
Ok(c) => c,
Err(err) => fail(&format!(
"pub_item_allowlist entry `{}` witness `{}` cannot be read: {err}",
entry.name, witness.path
)),
};
let file = match syn::parse_file(&content) {
Ok(f) => f,
Err(err) => fail(&format!(
"pub_item_allowlist entry `{}` witness `{}` does not parse: {err}",
entry.name, witness.path
)),
};
if !shared_checks::ast_references_name(&file, &entry.name) {
fail(&format!(
"pub_item_allowlist entry `{}` witness `{}` (line hints {:?}) has no real path-position reference to the item; update the witness path or hide the item via `#[doc(hidden)]`",
entry.name, witness.path, witness.lines,
));
}
}
}
let test_files: Vec<(std::path::PathBuf, syn::File)> = collect_rs_file_asts(Path::new("tests"));
walk_rs_files(Path::new("src"), &mut |path, contents| {
if path.ends_with("prelude.rs") {
return;
}
let path_str = repo_relative_display(path);
let file = syn::parse_file(contents).unwrap_or_else(|err| {
fail(&format!(
"PUB ITEM REFERENCE CHECK PARSE FAILURE in {path_str}: {err}\n\
This detector is syntax-aware by design; fix the source or the parser input."
))
});
for name in shared_checks::public_item_names(&file) {
if allowed_names.contains(name.as_str()) {
continue;
}
let witnessed = test_files
.iter()
.any(|(_, ast)| shared_checks::ast_references_name(ast, &name));
if !witnessed {
let (line, _col) = item_line_in_file(contents, &name);
fail(&format!(
"pub item `{name}` declared at {path_str}:{line} has no test reference (checked {n} test files via AST); either add a real test use, add an allowlist entry with a `witness:` path that points to an actual use, or hide the item via `#[doc(hidden)]`.",
n = test_files.len(),
));
}
}
});
}
fn collect_rs_file_asts(dir: &Path) -> Vec<(std::path::PathBuf, syn::File)> {
let mut out = Vec::new();
let entries = fs::read_dir(dir).unwrap_or_else(|err| {
fail(&format!(
"cannot read {} while collecting test witness ASTs: {err}",
dir.display()
))
});
for entry in entries {
let entry = entry.unwrap_or_else(|err| {
fail(&format!(
"cannot walk {} while collecting test witness ASTs: {err}",
dir.display()
))
});
let path = entry.path();
if path.is_dir() {
out.extend(collect_rs_file_asts(&path));
} else if path.extension().map(|e| e == "rs").unwrap_or(false) {
let contents = fs::read_to_string(&path).unwrap_or_else(|err| {
fail(&format!(
"cannot read {} while collecting test witness ASTs: {err}",
path.display()
))
});
let file = syn::parse_file(&contents).unwrap_or_else(|err| {
fail(&format!(
"cannot parse {} while collecting test witness ASTs: {err}",
path.display()
))
});
out.push((path, file));
}
}
out
}
fn item_line_in_file(contents: &str, name: &str) -> (usize, usize) {
for (idx, line) in contents.lines().enumerate() {
if line.contains(name) {
return (idx + 1, line.find(name).unwrap_or(0) + 1);
}
}
(0, 0)
}
fn load_pub_item_allowlist() -> Vec<PubItemAllowlistEntry> {
let repo_root = repo_root();
let path = repo_root.join("traceability/pub_item_allowlist.yaml");
let contents = fs::read_to_string(&path)
.unwrap_or_else(|err| fail(&format!("failed to read {}: {err}", path.display())));
let entries: Vec<PubItemAllowlistEntry> = yaml_serde::from_str(&contents)
.unwrap_or_else(|err| fail(&format!("failed to parse {}: {err}", path.display())));
for entry in &entries {
if entry.name.trim().is_empty() {
fail(&format!(
"invalid {} entry: `name` must not be empty (justification: {})",
path.display(),
entry.justification
));
}
if entry.justification.trim().is_empty() {
fail(&format!(
"invalid {} entry for `{}`: `justification` must not be empty",
path.display(),
entry.name
));
}
}
entries
}
fn walk_rs_files(dir: &Path, check: &mut dyn FnMut(&Path, &str)) {
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
walk_rs_files(&path, check);
} else if path.extension().map(|e| e == "rs").unwrap_or(false) {
if let Ok(contents) = fs::read_to_string(&path) {
check(&path, &contents);
}
}
}
}
}
fn walk_allow_checked_rs_files(check: &mut dyn FnMut(&Path, &str)) {
let core_root = core_root();
let repo_root = repo_root();
let roots = [
core_root.join("build.rs"),
core_root.join("src"),
repo_root.join("tools/xtask/src"),
repo_root.join("tools/integrity/src"),
];
for root in &roots {
if root.is_file() {
if let Ok(contents) = fs::read_to_string(root) {
check(root, &contents);
}
} else {
walk_rs_files(root, check);
}
}
}
fn walk_dead_code_checked_rs_files(check: &mut dyn FnMut(&Path, &str)) {
let core_root = core_root();
let repo_root = repo_root();
let roots = [
core_root.join("build.rs"),
core_root.join("src"),
core_root.join("tests"),
core_root.join("examples"),
core_root.join("benches"),
repo_root.join("tools/xtask/src"),
repo_root.join("tools/integrity/src"),
repo_root.join("crates/macros/src"),
repo_root.join("crates/macros-support/src"),
];
for root in &roots {
if root.is_file() {
if let Ok(contents) = fs::read_to_string(root) {
check(root, &contents);
}
} else {
walk_rs_files(root, check);
}
}
}