You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#826's Tier 4 audit identified two recurring gotcha patterns that need global flow analysis to detect — both involve module-load-time initialization that can silently happen in the wrong order:
std::env::set_var after the consumer has already read the var. Classic example: paths.py was caching FBUILD_DEV_MODE at module import time, but cli.py was setting the env var after that import. The result: dev-mode paths silently resolved to prod. (Fixed by making paths.py lazy — see the MEMORY note "Daemon paths.py import-time bug".)
OnceLock::set() / tracing_subscriber::registry().init() called after the first consumer has already initialized a default. Same bug class: order-of-init dependency that's invisible at the call site.
Why this is NOT a single-PR dylint
Both lints require:
Cross-crate flow analysis.set_var in cli.rs and var in paths.rs are in different crates. A late lint pass sees one crate at a time.
Initialization-order modeling. Even within a crate, static FOO: OnceLock<T> = OnceLock::new(); and the FOO.get_or_init(...) are arbitrarily far apart, and the "set" might happen in a main() that the analyzer can't easily prove runs first.
PR #833 (the first 5 lints) and PR #837 (the next 5 lints) deliberately scoped to local AST shapes — Command::new literal, type-path match in a file, attribute walk on a function. These two need a different toolkit.
Approaches to investigate
MIR-level dataflow. Late-lint passes can cx.tcx.optimized_mir(def_id) and walk basic blocks; cross-fn within a crate is feasible.
Whole-crate-graph analysis via cargo-deny-style top-level script. Trade per-commit speed for completeness — run as a separate CI job, not a per-file lint.
Convention-based static check. Forbid std::env::set_var outside main.rs entirely (companion to ban_process_exit_outside_main in PR dylint: extended gotcha sweep (#826 — out-of-scope items) #837). Side-steps the analysis problem by removing the foot-gun's surface.
Scope of this issue
Pick an approach (likely convention-based for fastest landing; MIR for completeness later).
Implement both lints as separate crates under dylints/, mirroring the established pattern.
Add allowlists for legitimate setup paths (test harnesses, main.rs).
Background
#826's Tier 4 audit identified two recurring gotcha patterns that need global flow analysis to detect — both involve module-load-time initialization that can silently happen in the wrong order:
std::env::set_varafter the consumer has already read the var. Classic example:paths.pywas cachingFBUILD_DEV_MODEat module import time, butcli.pywas setting the env var after that import. The result: dev-mode paths silently resolved to prod. (Fixed by makingpaths.pylazy — see the MEMORY note "Daemon paths.py import-time bug".)OnceLock::set()/tracing_subscriber::registry().init()called after the first consumer has already initialized a default. Same bug class: order-of-init dependency that's invisible at the call site.Why this is NOT a single-PR dylint
Both lints require:
set_varincli.rsandvarinpaths.rsare in different crates. A late lint pass sees one crate at a time.static FOO: OnceLock<T> = OnceLock::new();and theFOO.get_or_init(...)are arbitrarily far apart, and the "set" might happen in amain()that the analyzer can't easily prove runs first.PR #833 (the first 5 lints) and PR #837 (the next 5 lints) deliberately scoped to local AST shapes —
Command::newliteral, type-path match in a file, attribute walk on a function. These two need a different toolkit.Approaches to investigate
MIR-level dataflow. Late-lint passes cancx.tcx.optimized_mir(def_id)and walk basic blocks; cross-fn within a crate is feasible.cargo-deny-style top-level script. Trade per-commit speed for completeness — run as a separate CI job, not a per-file lint.std::env::set_varoutsidemain.rsentirely (companion toban_process_exit_outside_mainin PR dylint: extended gotcha sweep (#826 — out-of-scope items) #837). Side-steps the analysis problem by removing the foot-gun's surface.Scope of this issue
dylints/, mirroring the established pattern.main.rs).References
MEMORY.mdnote "Daemon paths.py import-time bug" — concrete example of the bug class