candor-scan 0.3.2

candor's STABLE-Rust effect scanner — syntactic call-graph + effect report, no nightly.
# candor-scan

**A stable-Rust effect scanner for Rust code.** It maps which functions perform side effects —
filesystem, network, subprocess, database, clock, env, randomness, IPC — and **how those effects
propagate transitively across the call graph**, including the *blast radius* of editing any function.

No nightly, no `rustc_private`, no dylint. It parses your `.rs` files with [`syn`](https://docs.rs/syn)
and runs anywhere `cargo` does — CI, a locked-down box, or `cargo install`. It does **not build your
crate**, so it can even analyze a dependency's source without compiling it.

```sh
cargo install candor-scan
candor-scan path/to/crate              # writes <crate>/.candor/report.<crate>.scan.json
candor-scan . --json                   # print the report to stdout instead
```

The report is the same JSON the full [candor](https://github.com/tombaldwin/candor) nightly lint
produces, so the candor CLI's read-only queries (`show`/`where`/`callers`/`map`) read it identically.

## What it does well, and where it stops

`candor-scan` is **syntactic** — it sees what's *written*, not what the compiler *resolves*. It is
calibrated to **never fabricate an effect** (validated across 1294 real crates: zero false positives on
a curated-pure set). When it can't see an effect, it stays silent rather than guessing — an honest
under-report, never a wrong label.

- **Catches:** path-qualified effect calls (`std::fs::read`, `Command::new`, `reqwest::Client::execute`),
  `use`-aliases, transitive propagation, calls hidden in macros (`try_call!`, `println!`), four C-library
  FFI tiers (libc, libsqlite3, libgit2, libssl), and method dispatch via light local type inference
  (struct fields, params, constructors, factory return types).
- **Honest `Unknown`:** when the body invokes a callable the scan can't see through — a closure or
  fn-pointer held in a field, dispatch table, or index (`(self.handler)()`, `arr[i]()`) — it marks the
  function `Unknown` rather than silently certifying it pure, and propagates that like any effect (so
  the receipt's unresolved count is truthful, not a hardcoded 0). A LOCAL closure whose body IS visible
  (`let f = |..| ..; f()`) is NOT flagged — its effects were already walked lexically.
- **Misses (silently):** effects reached only through trait-object (`dyn`) dispatch on an uninferrable
  receiver, overloaded operators / `?` / `.await` desugars, RAII drops (an effectful `Drop::drop` has no
  call expression — it runs at scope end), a custom `Iterator::next` reached only via a `for`-loop
  desugar, and cross-crate propagation by stable identity. These need the semantic resolution only the
  nightly lint has (the soundness fuzzer locks all of them against the lint: forms `op_add`/`index`/
  `deref`/`try_from`/`await_poll`/`drop`/`iterator`/`eq`/`add_assign`).

For the soundness contract (`Unknown` over-approximation), conformance checking, and the policy/guard
CI gates, use the full nightly [candor](https://github.com/tombaldwin/candor) lint. The two share one
classifier, so they never disagree on what counts as an effect.

## Usage

```
candor-scan [<dir>] [--out <prefix>] [--json] [--include-tests]
```

- `<dir>` — crate root to scan (default `.`).
- `--out <prefix>` — report path prefix (default `<dir>/.candor/report`); writes
  `<prefix>.<crate>.scan.json` plus a call-graph sidecar.
- `--json` — print the report to stdout instead of writing files.
- `--include-tests` — also scan `tests/`/`benches/`/`examples/` and `#[cfg(test)]` modules (off by
  default, so the report describes the crate, not its test harness).

Requires a reasonably recent stable Rust (edition 2024 → Rust 1.85+).

Licensed under MIT OR Apache-2.0. Part of [candor](https://github.com/tombaldwin/candor); see the repo
for the calibration (`eval/calibration/`) and the full effect model.