Skip to content

fix(view): circle shape SIGABRT + CircleTo stack overflow#46

Merged
willwade merged 1 commit into
mainfrom
fix/circle-geometry-crashes
Jul 25, 2026
Merged

fix(view): circle shape SIGABRT + CircleTo stack overflow#46
willwade merged 1 commit into
mainfrom
fix/circle-geometry-crashes

Conversation

@willwade

Copy link
Copy Markdown

Fixes two crashes in CDasherViewSquare triggered when LP_SHAPE_TYPE == CIRCLE (the legacy circular/compass node shape), both reachable via the C-API render path that was never tested with it.

Bug 1 — IsSpaceAroundNode SIGABRT (debug builds)

DasherViewSquare.cpp:527 asserted CoversCrosshair(...) for any node spanning the visible region. That invariant only holds for rectangular shapes — a circle/ellipse (and triangles/quadrics) can span the full visible height yet legitimately miss the crosshair, so the assert was unsound.

Fix: guard the assert to DISJOINT_RECTANGLE, OVERLAPPING_RECTANGLE, and CUBE; non-rectangular shapes fall through to their correct per-shape computation.

Bug 2 — CircleTo stack overflow (SIGSEGV)

CircleTo() (DasherViewSquare.cpp:441) subdivided the arc recursively with no depth bound. The convergence test could be defeated two ways:

  • NaN poisoningDasherSpaceArc (the game-mode brachistochrone, GameModule.cpp:239) can pass arc endpoints with |y - cy| > r, making sq(r) - sq(cy-y2) negative → sqrt(negative) → NaN → UB on the myint cast. UBSan: "nan is outside the range of representable values of type 'long long'".
  • Integer-division stall — once y1, y3 are within 1, the midpoint stops advancing.

Either way the recursion never terminated → stack exhaustion → SIGSEGV.

Fix:

  • depth cap (kMaxCircleSubdivisionDepth = 20 — 2^20 segments is far more than any real arc needs; ~13 levels reaches pixel accuracy),
  • y1 >= y3 no-progress guard,
  • clamp the sqrt argument to >= 0.

The remaining arc at the cap is approximated by a straight line, which is visually fine at that granularity.

Audit for similar issues

Per the bug report's heads-up ("we may have other settings like this"), I checked every DASHER_ASSERT and recursive draw function in the file:

  • DasherLine2Screen (only other recursive draw fn) — bounded by integer-coordinate halving (~log₂(MAX_Y) ≈ 22 levels) + convergence guards. Safe.
  • TruncateTri's asserts are internal to the triangle clipper; hold for triangle geometry. In scope only for triangle shapes.
  • All remaining LP_SHAPE_TYPE dispatch sites (NewRender, CoversCrosshair, IsSpaceAroundNode) already handle CIRCLE.

So CircleTo was the sole unbounded recursion and IsSpaceAroundNode's was the sole geometry-unsound assert.

Tests

  • test_circle_geometry.cpp — C-API smoke (circle frames render, deep forward driving, parameter switching in/out of circle mode).
  • test_circle_view_internal.cpp — deterministic internal test driving DasherSpaceArc/CircleTo with degenerate coords (both LeftToRight and TopToBottom). Verified it crashes on the unpatched code (UBSan NaN cast + ASan SEGV) and passes clean after the fix.

Verification

  • clang-format clean
  • Debug build (asserts active): circle tests pass
  • Sanitize build (ASan+UBSan): internal test passes clean; crashes deterministically on pre-fix code
  • Release build: circle tests + all view/draw tests + deterministic_tests pass

Circle node shape (LP_SHAPE_TYPE == CIRCLE) crashed in two places in CDasherViewSquare, both exercised via the C-API render path that was never tested with the legacy circle layout.

Bug 1 (SIGABRT): IsSpaceAroundNode() asserted CoversCrosshair() for any node spanning the visible region. That invariant only holds for rectangular shapes — a circle/ellipse (or triangle/quadric) can span the full visible height yet legitimately miss the crosshair. Guard the assert to DISJOINT/OVERLAPPING_RECTANGLE and CUBE.

Bug 2 (SIGSEGV / stack overflow): CircleTo() subdivided the arc recursively with no depth bound. Degenerate endpoints (DasherSpaceArc can pass y with |cy - y| > r => sqrt(negative) => NaN, or integer-rounding stall once y1,y3 are within 1) defeated the convergence test and recursed until the stack was exhausted. Add a depth cap (kMaxCircleSubdivisionDepth = 20), a no-progress guard (y1 >= y3), and clamp the sqrt argument to >= 0.

Tests: test_circle_geometry.cpp (C-API smoke) and test_circle_view_internal.cpp (deterministic internal test that crashes on unpatched code via UBSan NaN cast + ASan SEGV, passes clean after).
Signed-off-by: will wade <willwade@gmail.com>
@willwade
willwade merged commit 58facb5 into main Jul 25, 2026
14 checks passed
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