Skip to content

Fix O(n²) performance regression in spoiler span analysis - #312

Merged
mity merged 4 commits into
mity:masterfrom
hryhoriiK97:fix/spoilers-quadratic-perf
May 4, 2026
Merged

mity merged 4 commits into
mity:masterfrom
hryhoriiK97:fix/spoilers-quadratic-perf

Conversation

@hryhoriiK97

@hryhoriiK97 hryhoriiK97 commented May 4, 2026 •

Copy link
Copy Markdown
Contributor

md_analyze_link_contents() is called recursively for every resolved link
pair. Each call runs the spoiler loop over its mark range, but unlike
md_analyze_marks(), the loop had no skip optimization for resolved spans
— it walked through every inner mark one by one.

With 64,135 resolved links in the OSS-Fuzz testcase, this caused
4.86 billion loop iterations instead of 268,499 — O(n²) behavior.

Fix: when the loop hits a resolved [ or ! opener, jump to
mark->next directly, mirroring what md_analyze_marks() already does

Fixes: #311

The spoiler analysis loop in md_analyze_link_contents() was called
recursively for every resolved link pair (by md_resolve_links()), but
lacked the skip-resolved-spans optimization present in md_analyze_marks().

With deeply nested bracket structures (e.g. the OSS-Fuzz testcase
clusterfuzz-testcase-fuzz-mdhtml-5579634954797056 with 37,034 nesting
levels), the loop iterated O(n^2) over the same marks, causing 13+
second parse times for a 661 kB input.

Fix: when the loop encounters a resolved link or image opener ('[', '!'),
jump directly to mark->next (the closer) instead of walking through
every mark inside the span one by one. This mirrors the skip logic in
md_analyze_marks() and reduces total iterations from O(n^2) to O(n).

Closes mity#311.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codecov

codecov Bot commented May 4, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.95%. Comparing base (347b528) to head (f169318).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #312      +/-   ##
==========================================
+ Coverage   78.94%   78.95%   +0.01%     
==========================================
  Files           5        5              
  Lines        3401     3403       +2     
  Branches     1138     1139       +1     
==========================================
+ Hits         2685     2687       +2     
  Misses        275      275              
  Partials      441      441              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

hryhoriiK97 and others added 3 commits May 4, 2026 22:45
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…coverable branch

ISANYOF_ expands to ((ch) != '\0' && ...), which introduces an impossible
branch because no mark ever has ch == '\0'. Replace with an explicit
(mark->ch == '[' || mark->ch == '!') check for full branch coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>
@mity
mity merged commit f169318 into mity:master May 4, 2026
8 checks passed
@mity

mity commented May 4, 2026

Copy link
Copy Markdown
Owner

Yep, great job. Thx.

I've somewhat refactored md_analyze_link_contents() to reuse md_analyze_marks() instead of duplicating its logic. It lead also to some other code cleanup.

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.

Pathologic input leading to heavily sub-linear parsing times

2 participants