java-topology/whitepaper/outreach/cargo.md
russell@unturf.com 7e7ec2c3d3 feat: add 10 outreach docs (20 defects) for 2-patch projects
amarok, arrow, audacity, cargo, clementine, composer, dask,
deluge, dosbox-x, dragonfly. All CWE-407.
2026-04-14 13:50:33 -04:00

3.9 KiB
Raw Blame History

Cargo — CWE-407 Disclosure Brief

2026-04-13 · Patch available — awaiting upstream merge

Finding

Two O(n²) defects in Cargo's dependency tree printer: one in the cycle-detection stack and one in the edge deduplication layer. Both patched. Patches ready for upstream review.

The Defects

cargo-0001 (PATCHED — MEDIUM): src/cargo/ops/tree/mod.rs:277

// In print() — cycle detection during `cargo tree` output:
let mut print_stack = vec![];
// ...
// In print_node():
print_stack.push(node_index);
// Cycle check uses Vec::contains() — O(n) per node

print_stack is Vec<NodeId>. contains() is a linear scan. When --no-dedupe is used, every node in the dependency tree checks the full stack for cycles — O(D) per node where D = tree depth. For deeply nested dependency trees, this compounds to O(N×D).

cargo-0002 (PATCHED — MEDIUM): src/cargo/ops/tree/graph.rs

// In Edges::add_edge() — fires during dependency graph construction:
fn add_edge(&mut self, edge: Edge) {
    let indexes = self.0.entry(edge.kind()).or_default();
    if !indexes.contains(&edge) {  // Vec::contains() — O(k) per edge
        indexes.push(edge)
    }
}

Edges wraps HashMap<EdgeKind, Vec<Edge>>. contains() is O(k) per insertion where k = edges of that kind for a node. In --graph-features mode, heavily-featured crates (tokio, serde) accumulate 100+ edges, making total cost O(E²/K).

Complexity Proof

cargo-0001: At N=500 nodes, D=50 depth:

  • Defective: 500 × 25 (avg depth) = 12,500 comparisons
  • Fixed: 500 × O(1) = 500 lookups (HashSet)
  • ~25× op reduction.

cargo-0002: At 100 edges per kind for a heavily-featured node:

  • Defective: 99 + 98 + ... ≈ 5,000 comparisons per node
  • Fixed: 100 × O(1) = 100 insertions (IndexSet)
  • ~50× op reduction per heavily-featured node.

Impact

Cargo is the Rust package manager and build system — used by every Rust developer worldwide. cargo tree is a commonly-used diagnostic command for understanding dependency graphs. Large Rust projects (web frameworks, embedded systems, crypto libraries) with deep dependency trees and feature-heavy crates hit both paths.

cargo-0001 fires during every cargo tree --no-dedupe invocation. cargo-0002 fires during graph construction in --graph-features mode, where crates like tokio and serde expose dozens of feature edges.

The Fix

cargo-0001: Replace Vec<NodeId> with HashSet<NodeId>:

// Before
let mut print_stack = vec![];
print_stack.push(node_index);

// After
// CWE-407 fix: HashSet for O(1) contains() vs O(n) Vec::contains.
let mut print_stack = HashSet::new();
print_stack.insert(node_index);

cargo-0002: Replace Vec<Edge> with IndexSet<Edge>:

// Before
if !indexes.contains(&edge) {
    indexes.push(edge)
}

// After
// CWE-407 fix: IndexSet::insert is O(1) amortised and ignores duplicates.
self.0.entry(edge.kind()).or_default().insert(edge);

IndexSet preserves insertion order (matching Vec iteration behavior) while providing O(1) amortized insert with built-in deduplication.

Patch

Fix available: defects/cargo/patch/cargo-0001-print-stack-hashset.patch and defects/cargo/patch/cargo-0002-edges-add-edge-indexset.patch

Two-file patch across tree/mod.rs and tree/graph.rs.

cargo-0001: ~25× speedup at N=500, D=50. cargo-0002: ~50× speedup per heavily-featured node.

What We Ask

A patch is ready for review.

  1. Confirm receipt and assign an issue reference (rust-lang/cargo).
  2. Assess severity — both defects affect cargo tree output for large dependency graphs, a commonly-used diagnostic tool.
  3. Coordinate a disclosure date — we are targeting 90 days from first contact.
  4. We will credit the Cargo team in the public disclosure. Preferred acknowledgment format welcome.

Contact: see cover email. This brief is confidential until coordinated disclosure.