Conversation
timzifer
marked this pull request as ready for review
September 17, 2026 12:24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Large finite coordinates can crash the analytic rasterizer with an unrecoverable stack overflow.
EdgeBuilder.addCubicandaddQuadsubdivide until an absolute 0.1 px deviation test passes, but neither bounds recursion. At float32 precision, a midpoint can round back to an input point and leave a child identical to its parent. The deviation then never decreases.For example, the cubic with x coordinates
{2289292.75, 2289293, 2289293, 2289293}and all y coordinates equal tomath.Float32frombits(0x43817f0a)repeats indefinitely. Its endpoints are one float32 ULP apart. Quadratics have the same failure with x coordinates{2289293, 2289292.75, 2289292.75}.Setting a clip rectangle does not prevent this: the out-of-bounds curve guard runs after the recursive deviation branch.
Fix
Thread a depth through the native quadratic and cubic subdivision helpers. At
depth > 10, emit the remaining chord throughaddLine, matching the existing flattening routines. This keeps line clipping and winding handling on the fallback path and bounds subdivision even when float32 rounding prevents progress.The existing tolerance, subdivision arithmetic and clip-check ordering stay in place. There is no public API change.
Minimal example
This only needs gg; no GPU or window is involved:
On v0.52.5 this ends in
fatal error: stack overflow. With the fix,Fillreturns nil and the clipped blue region covers x=10..99, y=10..89. The standalone reproducer checks all 10,000 pixels: 7,200 filled pixels, with the rest transparent.Tests
TestEdgeBuilderCurveSubdivisionTerminates: 16 subprocess cases cover cubic and quadratic fixed points, both coordinate axes, both signs, and with/without clipping. All 16 fail with stack overflow on unmodified main (bd563f4); all pass with the fix. Each child has a 1 MiB stack limit and a timeout so a regression cannot exhaust the test runner's stack or hang the suite.TestEdgeBuilderLargeCurvesPreserveClippedFill: cubic and quadratic contours extending far beyond the right clip boundary retain the expected visible fill, checked pixel by pixel. These also run in bounded subprocesses.go test -race -tags nogpu ./...passes withCGO_ENABLED=1, following the repository's CI configuration. The raster package was rerun after adding the clipped-fill assertions.go build ./...andgolangci-lint run --timeout=5mpass withCGO_ENABLED=0; the final linter run reports zero issues.Validated on Windows amd64 with Go 1.25.3, based on upstream main at
bd563f4c293fd1f70afe56276c444a38302bac15.