Skip to content

Fix two more divide-by-zero bugs found by the formalization audit - #1078

Merged
emeryberger merged 2 commits into
masterfrom
fix-stacks-total-cpu-zerodiv
Jul 1, 2026
Merged

Fix two more divide-by-zero bugs found by the formalization audit#1078
emeryberger merged 2 commits into
masterfrom
fix-stacks-total-cpu-zerodiv

Conversation

@emeryberger

@emeryberger emeryberger commented Jul 1, 2026

Copy link
Copy Markdown
Member

Two more divide-by-zero defects of the same class as #1077, found by re-running the formalization audit's "every denominator is a claim to verify against the code" sweep (formal/README.md → "Bugs the formalization found") across all three of Scalene's output renderers.

Bug 1 — per-stack CPU normalization (scalene_json.py)

The stats.stacks normalization loop divided by stats.cpu_stats.total_cpu_samples with no guard. That denominator can be 0.0 while stats.stacks is non-empty:

  • A memory-only run with --stacks records stack entries (the CPU sampler's stack collection runs), but it is memory activity — not CPU — that passes the "nothing to output" gate at the top of output_profiles.
  • No CPU sample recorded ⇒ total_cpu_samples stays 0.0ZeroDivisionError.

Sibling per-file/per-line normalizations (~556, ~1259, ~1337) were already guarded; this one was missed. Fix guards the loop (raw stack entries preserved when the total is 0).

tests/test_stacks_zero_cpu_samples.py drives the full output_profiles path — crashes pre-fix, passes after.

Bug 2 — CLI-renderer twin of the leak-velocity divide (scalene_output.py)

Scalene has three separate output renderers (see Scalene-Debugging.md). #1077 fixed the unguarded leak_velocity / stats.elapsed_time only in the JSON renderer; the CLI renderer (scalene view --cli, scalene_output.py:699) carried the identical unguarded divide.

Same reachability: compute_leaks gates on allocation growth rate, not wall-clock time, so a leak can be reported on a sub-ms run where elapsed_time is still 0.0. Fix mirrors the #1077 guard; tests/test_cli_leak_velocity_zero_elapsed.py added.

The other CLI-path divides (~339, ~379, ~416, ~657) were audited and are already guarded.

Notes

  • ruff clean on all changed files; mypy delta zero (only pre-existing PEP585 warnings remain).
  • Takeaway recorded in formal/HANDOFF.md: a fix in one renderer does not cover its siblings — audit all three. The formalization "bugs found" tally is now four.

…only)

The stacks-normalization loop in ScaleneJSON.output_profiles divided each
recorded stack's timings by stats.cpu_stats.total_cpu_samples with no guard.
That denominator can be 0.0 while stats.stacks is non-empty: a memory-only
run with --stacks records stack entries (from the CPU sampler's stack
collection) while it is *memory* activity -- not CPU activity -- that passes
the 'nothing to output' gate at the top of output_profiles. With no CPU
sample ever recorded, total_cpu_samples stays at its initial 0.0 and the loop
raises ZeroDivisionError.

The sibling per-file/per-line CPU normalizations (~556, ~1259, ~1337) already
guard this; this site was missed. Same bug class as the leak-velocity divide
(#1077). Found by re-running the formalization audit's 'every denominator is
a claim to verify' sweep across the output path (formal/README.md 'Bugs the
formalization found', now three).

Guard the loop on total_cpu_samples (raw stack entries preserved when 0).
Regression test drives the full output_profiles path, so it crashes pre-fix
and passes after.
Bug #1 (fixed in #1077) was an unguarded leak_velocity / stats.elapsed_time
in the JSON renderer. Scalene has three separate output renderers
(Scalene-Debugging.md); the CLI renderer scalene_output.py:699 (scalene view
--cli) carried the identical unguarded divide, which #1077 did not touch.

Same reachability as #1: compute_leaks gates on allocation growth rate, not
wall-clock time, so a leak can be reported on a sub-millisecond run where
elapsed_time is still 0.0 -> ZeroDivisionError.

Found by re-running the denominator audit across the CLI output path (the
other output.py divides at ~339/~379/~416/~657 are already guarded). Guard
the velocity denominator, mirroring the #1077 json.py fix. Regression test
added. Also updates formal/README.md + HANDOFF.md (now four bugs found).

The takeaway, now recorded in HANDOFF: a fix in one renderer does not cover
its siblings -- audit all three.
@emeryberger emeryberger changed the title Fix divide-by-zero in per-stack CPU normalization (--stacks + memory-only run) Fix two more divide-by-zero bugs found by the formalization audit Jul 1, 2026
@emeryberger
emeryberger merged commit 79609ea into master Jul 1, 2026
41 checks passed
emeryberger added a commit that referenced this pull request Jul 1, 2026
…PRs (#1081)

Clears the stale §3b entry (#1078 was left listed as open) and records #1080
under merged. Supersedes the closed #1079.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant