Skip to content

dylint: initializer-order lints (ban_env_var_set_after_import, require_oncelock_install_before_use) — design and implementation #840

Description

@zackees

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:

  1. 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".)

  2. 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

  1. Pick an approach (likely convention-based for fastest landing; MIR for completeness later).
  2. Implement both lints as separate crates under dylints/, mirroring the established pattern.
  3. Add allowlists for legitimate setup paths (test harnesses, main.rs).
  4. Run against the post-PR-dylint: extended gotcha sweep (#826 — out-of-scope items) #837 state and report violations.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions