Catch async bugs and performance anti-patterns that clippy misses
  • Rust 97.6%
  • Emacs Lisp 0.7%
  • Lua 0.7%
  • Shell 0.5%
  • TypeScript 0.5%
Find a file
dependabot[bot] 305f5ba864
deps: bump tokio from 1.52.3 to 1.53.1 (#36)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.52.3 to 1.53.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.52.3...tokio-1.53.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.53.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-21 06:30:30 +00:00
.github feat(accuracy): labeled precision/recall scorecard + robustness scans (C1) 2026-07-01 05:35:33 -04:00
benchmarks
docs docs(accuracy): document the adversarial hunt harness and the 1.00 floor 2026-07-10 18:06:54 -04:00
editors
examples
fuzz fix(parser): contain parser panics + add cargo-fuzz harness (H4) 2026-07-01 05:25:14 -04:00
scripts feat(accuracy): labeled precision/recall scorecard + robustness scans (C1) 2026-07-01 05:35:33 -04:00
src style(fmt): apply rustfmt across the accuracy-hardening changes 2026-07-10 18:58:20 -04:00
tests style(fmt): apply rustfmt across the accuracy-hardening changes 2026-07-10 18:58:20 -04:00
.gitignore
.taplo.toml
action.yml
cargo-perf.schema.json
Cargo.lock deps: bump tokio from 1.52.3 to 1.53.1 (#36) 2026-07-21 06:30:30 +00:00
Cargo.toml feat(dist): cargo-binstall support + adoption/onboarding guide (H8) 2026-07-01 05:27:23 -04:00
CHANGELOG.md
CONTRIBUTING.md
DESIGN.md docs(design): reposition honestly as additive to clippy 2026-07-01 05:36:47 -04:00
LICENSE-APACHE
LICENSE-MIT
README.md docs(accuracy): document the adversarial hunt harness and the 1.00 floor 2026-07-10 18:06:54 -04:00
SECURITY.md

cargo-perf

Static analysis for async correctness and runtime performance in Rust.

Crates.io CI License

The Problem

These bugs compile fine and ship to production:

// Blocks the async runtime — causes timeouts under load
async fn read_config() -> Config {
    let data = std::fs::read_to_string("config.toml").unwrap();
    toml::from_str(&data).unwrap()
}

// Deadlock risk — a *synchronous* guard held across an await point
async fn update(mutex: &std::sync::Mutex<Data>) {
    let guard = mutex.lock().unwrap();
    some_async_op().await; // sync guard held across .await can deadlock the runtime
}

// 737x slower — regex compilation in hot loop
for line in lines {
    if Regex::new(r"\d+").unwrap().is_match(line) { ... }
}

cargo-perf catches all of these.

Installation

Prebuilt binary (fastest — no compile), via cargo-binstall:

cargo binstall cargo-perf

Or build from source:

cargo install cargo-perf

Either way the binary installs as cargo-perf, so it runs as a cargo subcommand: cargo perf.

Adopting on an existing codebase? Don't let pre-existing findings block you — see Getting Started for the baseline-and-ratchet workflow that fails CI only on new issues.

Usage

cargo perf                          # Analyze current directory
cargo perf --strict                 # High-confidence rules only (CI recommended)
cargo perf --strict --fail-on error # Fail CI on issues
cargo perf --format sarif           # For GitHub Code Scanning
cargo perf fix --dry-run            # Preview auto-fixes
cargo perf fix                      # Apply auto-fixes

Rules

Errors (High Confidence)

Rule What it catches
async-block-in-async std::fs, thread::sleep, blocking I/O in async functions
lock-across-await Synchronous MutexGuard/RwLockGuard (std/parking_lot) held across .await — deadlock risk
n-plus-one-query Database queries inside loops (SQLx, Diesel, SeaORM)

Note on lock-across-await: An async lock guard (tokio::sync::Mutex, RwLock) held across .await is correct by design — that is what async locks are for. cargo-perf reports that case as a Warning (it serializes tasks and can throttle throughput), not an error. Only synchronous guards held across .await are flagged as errors, because those can deadlock the runtime. This is deliberately narrower than clippy::await_holding_lock, which warns on every guard regardless of lock type.

Warnings (Medium Confidence)

Rule What it catches Impact
unbounded-channel mpsc::channel(), unbounded_channel() Memory exhaustion
unbounded-spawn tokio::spawn in loops Resource exhaustion
regex-in-loop Regex::new() inside loops 737x slower
clone-in-hot-loop .clone() in loops (excludes Arc/Rc refcount clones) 48x slower
collect-then-iterate .collect().iter() 2.3x slower
vec-no-capacity Vec::new() + push in loop 1.8x slower
hashmap-no-capacity HashMap::new() + insert in loop Repeated rehashing
string-no-capacity String::new() + push_str in loop Repeated realloc
format-in-loop format!() inside loops Allocates each iteration
string-concat-loop String + in loops Use push_str()
mutex-in-loop Lock acquired inside loop Acquire once outside

Accuracy

A linter you can't trust gets muted or uninstalled. cargo-perf measures its own precision and recall against a hand-labeled corpus and enforces a floor on both in CI — per rule, not just in aggregate. Every one of the 14 rules is scored by both a positive fixture (it must fire when it should) and a negative fixture (it must stay silent when it shouldn't), so the scorecard can't be gamed by a rule that never runs. The negatives directly guard the false positives other linters are infamous for: Arc::clone/Rc::clone refcount bumps, Copy values cloned in loops, io::Read/Write and AtomicUsize::load in loops, custom .output()/.load() methods mistaken for blocking/DB calls, and async guards dropped before .await.

The corpus was hardened by a repeatable adversarial FP/FN hunt — the last pass confirmed 38 defects and drove a 10-batch, test-first remediation — and the floor is now ratcheted to a perfect 1.00 / 1.00: on this corpus a reintroduced false positive or a dropped true positive fails the build. Cases the tool honestly can't yet get right live in tests/corpus/known_gaps/ (tracked, unscored) rather than dragging the number down quietly.

OVERALL   TP: 28   FP: 0   FN: 0   precision: 1.00   recall: 1.00   (80 fixtures, 14/14 rules)

Reproduce with cargo test --test accuracy -- --nocapture. Full methodology — the scorecard, the adversarial hunt, the known_gaps/ policy, and the fuzzing and real-crate robustness scans — in docs/accuracy.md.

CI Integration

GitHub Action

Use the official GitHub Action for the easiest setup:

# .github/workflows/perf.yml
name: Performance Analysis

on: [push, pull_request]

jobs:
  cargo-perf:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write  # For SARIF upload

    steps:
      - uses: actions/checkout@v4
      - uses: cschuman/cargo-perf@v1
        with:
          path: '.'
          fail-on-error: 'true'
          sarif: 'true'  # Enables GitHub Code Scanning integration

Action Inputs

Input Default Description
path . Path to analyze
fail-on-error true Fail if errors found
fail-on-warning false Fail if warnings found
sarif true Upload results to GitHub Code Scanning
version latest cargo-perf version to install

Manual Setup

# .github/workflows/ci.yml
- name: Performance lint
  run: |
    cargo install cargo-perf
    cargo perf --strict --fail-on error

For a complete workflow with SARIF integration for GitHub Code Scanning, see examples/github-workflow.yml.

Suppressing warnings

// cargo-perf-ignore: clone-in-hot-loop
let owned = data.clone(); // intentional in cold path

Or for a whole function:

#[allow(cargo_perf::clone_in_hot_loop)]
fn cold_path() { ... }

Benchmarks

Real measurements (Apple M1 Pro, 1000 iterations):

Anti-pattern Impact
Regex::new() in loop 737x slower
clone() in loop 48x slower
collect().iter() 2.3x slower
Blocking in async Blocks runtime thread
Sync lock across await Deadlock

See benchmarks/ for methodology.

IDE Integration

cargo-perf includes an LSP server for real-time diagnostics in your editor.

Installation

cargo install cargo-perf --features lsp

VS Code

See editors/vscode/ for the extension.

Neovim

require('lspconfig.configs').cargo_perf = {
  default_config = {
    cmd = { 'cargo-perf', 'lsp' },
    filetypes = { 'rust' },
    root_dir = require('lspconfig.util').root_pattern('Cargo.toml'),
  },
}
require('lspconfig').cargo_perf.setup({})

Other Editors

See editors/README.md for Emacs, Helix, Zed, and generic LSP setup.

Custom Rules (Plugin System)

Extend cargo-perf with your own rules:

use cargo_perf::plugin::{PluginRegistry, analyze_with_plugins};
use cargo_perf::rules::{Rule, Diagnostic, Severity};

struct MyCustomRule;

impl Rule for MyCustomRule {
    fn id(&self) -> &'static str { "my-rule" }
    fn name(&self) -> &'static str { "My Rule" }
    fn description(&self) -> &'static str { "Detects my anti-pattern" }
    fn default_severity(&self) -> Severity { Severity::Warning }

    fn check(&self, ctx: &AnalysisContext) -> Vec<Diagnostic> {
        // Your detection logic
        Vec::new()
    }
}

let mut registry = PluginRegistry::new();
registry.add_rule(Box::new(MyCustomRule));
let diagnostics = analyze_with_plugins(path, &config, &registry)?;

See examples/custom_rule.rs for a complete example.

License

MIT OR Apache-2.0