fix: Avoid O(n^2) backtracking in inline link href regex - #1
Closed
hong4rc wants to merge 1 commit into
Closed
Conversation
hong4rc
force-pushed
the
perf/linear-link-regex
branch
from
July 9, 2026 14:48
a777b8d to
c9e9192
Compare
hong4rc
added a commit
that referenced
this pull request
Jul 19, 2026
…js#4015) A line like `#1 Goals` or `#hashtag` followed by a setext underline was rendered as a paragraph plus `<hr>` instead of a heading. A line only starts an ATX heading when the `#`s are followed by whitespace or EOL, so these are paragraph text and the underline makes a setext heading. The lheading ATX-interrupt check was bare ` {0,3}#{1,6}`, missing the `(?:\s|$)` that the paragraph and ATX heading rules already require.
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.
Marked version: current
master(regex unchanged since 18.0.5)Markdown flavor: n/a
Description
The inline link regex parses an unclosed link followed by a long whitespace run in O(n²) time.
marked.parse('[](' + ' '.repeat(n))is just text — an unclosed link — but with default options the parse time roughly quadruples each time the input doubles: n=16,000 → ~0.3 s, 32,000 → ~1.2 s, 64,000 → ~4.9 s, 125,000 → ~21 s.The cause is the unquoted-href alternative
[^ \t\n\x00-\x1f]*insrc/rules.ts. Since it can match empty at any whitespace position, when there is no closing)the leading\s*, the (empty) href, the optional title separator, and the trailing\s*\)all re-partition the same whitespace run at ~n positions, so the engine backtracks quadratically. markedjs#3902 hardened the title separator ([ \t]*→[ \t]+|\n) against the same class of problem but left this zero-length href branch, so input that never reaches the title branch still backtracks.The fix requires the unquoted-href branch to match at least one non-whitespace character (
*→+) and keeps the legitimate empty-href case[foo]()via a(?=\))lookahead. CommonMark specifies that an unquoted link destination ends at whitespace, so requiring ≥1 non-whitespace character there is spec-correct. After the change the same input parses in linear time (64 KB → ~1 ms) and ordinary links are unaffected: the full spec and unit suites stay green (test:specs1749/0,test:unit188/0), and I addedtest/specs/redos/quadratic_link_empty_href.cjsnext to the existing quadratic guards so any regression fails fast.Contributor
test/specs/redos/quadratic_link_empty_href.cjs.