Skip to content

Commit 84c5d92

Browse files
Fix O(n^2) parsing time with --fspoilers and deeply nested brackets
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 #311. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 347b528 commit 84c5d92

1 file changed

Lines changed: 8 additions & 1 deletion

File tree

src/md4c.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4173,8 +4173,15 @@ md_analyze_link_contents(MD_CTX* ctx, const MD_LINE* lines, MD_SIZE n_lines,
41734173
if(ctx->parser.flags & MD_FLAG_SPOILERS) {
41744174
for(i = mark_beg; i < mark_end; i++) {
41754175
MD_MARK* mark = &ctx->marks[i];
4176-
if(mark->flags & MD_MARK_RESOLVED)
4176+
if(mark->flags & MD_MARK_RESOLVED) {
4177+
/* Skip resolved link/image spans. Their contents were already
4178+
* analyzed by the recursive md_analyze_link_contents() call
4179+
* inside md_resolve_links(). Without this, deeply nested
4180+
* brackets cause O(n^2) parsing time. */
4181+
if((mark->flags & MD_MARK_OPENER) && ISANYOF_(mark->ch, "[!"))
4182+
i = mark->next;
41774183
continue;
4184+
}
41784185
if(mark->ch == '|' && mark->end - mark->beg == 2)
41794186
md_analyze_pipe(ctx, i);
41804187
}

0 commit comments

Comments
 (0)