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
    No fields configured for issues without a type.

    Projects

    Status
    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions