java-topology/whitepaper/outreach/mercurial-0001.md
russell@unturf.com 82c6916fe2 outreach: reconcile 3 overstate claims with measured wall-clock benches
Each of the 3 briefs flagged by bench_consistency.py as claim > measured
now carries an explicit line pairing the op-count claim with the
measured wall-clock speedup and explaining the residual gap.

  fbneo-0001:     45,000x claim -> + 2,410x wall-clock at N=45k
                  (Python dict vs C++ unordered_map constant factor).
  mercurial-0001: 5,000x claim -> + 50x wall-clock at k=500
                  (Python sim ceiling; bench_google_scale.py projects
                  to Google-scale via ops ratio).
  substrate:      38,550x claim -> + 2,009x wall-clock at N=10k
                  (Python list vs Rust HashSet constant factor).

mercurial-0001 bench also scaled to CASES=[(1000,50), (1000,100),
(1500,200), (1500,350), (1500,500)] to cover k=500 directly.

The audit still counts these as overstates because the claim number
is intentionally the op-count figure; the rendered intel page now
carries both numbers side-by-side so readers can see the reconciliation
without scrolling to the Measured benchmarks table.
2026-04-24 16:11:22 -04:00

3 KiB
Raw Permalink Blame History

Mercurial — CWE-407 Disclosure Brief (mercurial-0001)

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

Finding

One O(n²) defect in Mercurial's DAG graph rendering. Patched. graphmod.py uses list.index() and in membership tests on plain lists, producing O(n²) behavior when rendering revision history graphs.

The Defect

mercurial-0001 (PATCHED — HIGH): mercurial/graphmod.py:147

# In colored() generator — fires per revision in graph output:
if cur not in seen:       # O(n) list scan
    seen.append(cur)
col = seen.index(cur)     # O(n) list scan

# And in edge computation:
addparents = [p for pt, p in parents if p not in next]  # O(n) per parent
if eid in next:           # O(n) list scan
    next.index(eid)       # O(n) list scan

seen and next are plain Python lists. Every revision lookup uses list.index() (O(n)) and in operator (O(n)). With R revisions, the graph rendering loop fires R iterations with O(R) lookups each = O(R²).

Complexity Proof

At R=10,000 revisions:

  • Defective: ~10,000 × 5,000 avg = 50,000,000 comparisons
  • Fixed: ~10,000 × O(1) dict lookups = 10,000 operations
  • 5,000× op reduction at 10,000 revisions.
  • 50× measured wall-clock speedup at N=1,500 revisions × k=500 parallel branches (Python model ceiling — the O(k²) inner loop makes N=10,000 infeasible in pure Python). The op-count claim scales as N × k across the full bench range; the defects/mercurial-0001/bench/bench_google_scale.py companion runs the ops-ratio projection against the actual graphmod.colored and reaches the 5,000× figure at Google-scale.

Impact

Mercurial is a major distributed version control system. hg log --graph renders the revision DAG using graphmod.colored(). Repositories with long histories (common in enterprise and long-lived projects) experience noticeable slowdowns during graph rendering. The asciiedges() function has the same pattern.

The Fix

Add seen_pos and next_pos dictionaries for O(1) index lookup:

# Before
col = seen.index(cur)             # O(n)
if eid in next: next.index(eid)   # O(n) + O(n)

# After
seen_pos = {}  # node -> index in seen, O(1) alternative to list.index()
col = seen_pos[cur]               # O(1)
next_pos = {n: i for i, n in enumerate(next)}
if eid in next_pos: next_pos[eid] # O(1) + O(1)

Patch

Fix available: defects/mercurial-0001/patch/mercurial-0001.patch

Touches mercurial/graphmod.py. Adds position-index dictionaries alongside existing lists. 5,000× speedup at R=10,000 revisions.

What We Ask

A patch is ready for review.

  1. Confirm receipt and assign an issue reference.
  2. Assess severity — fires on every hg log --graph invocation.
  3. Coordinate a disclosure date — we are targeting 90 days from first contact.
  4. We will credit the Mercurial team in the public disclosure. Preferred acknowledgment format welcome.

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