Skip to content

Fix quadratic-time backtracking when a reference link has no URL - #1631

Open
afonsojanu wants to merge 1 commit into
Python-Markdown:masterfrom
afonsojanu:fix/reference-link-quadratic-time
Open

Fix quadratic-time backtracking when a reference link has no URL#1631
afonsojanu wants to merge 1 commit into
Python-Markdown:masterfrom
afonsojanu:fix/reference-link-quadratic-time

Conversation

@afonsojanu

Copy link
Copy Markdown

Fixes #798 (specifically the sub-case flagged by @stsewd in this comment, a regex in ReferenceProcessor).

markdown.markdown('[id]:' + ' ' * 50000) currently takes about 8 seconds. The reference-link regex has two adjacent [ ]* groups around an optional newline ([ ]*\n?[ ]*) sitting right before a mandatory non-whitespace group. When there's no newline, those two groups both match the same run of spaces, so for n trailing spaces there are n+1 ways to split them between the two groups before the engine gives up looking for the URL. That's quadratic in the length of the run.

I rewrote it so the first run of spaces is consumed by a single greedy group, with the newline-plus-more-spaces case folded into one unambiguous alternative right after ([ ]*(?:\n[ ]*)?). Checked this against the reference-link shapes the existing test suite covers (with/without title, title on its own line, id/url split across a line break, leading indent) and got byte-for-byte identical match groups on all of them.

Added a new test file (test_reference_links.py) with a few basic reference-link assertions plus the specific regression case, timed with a 2-second ceiling. Confirmed it fails on unmodified master (13.4s) and passes with the fix (well under a second for the whole file). Full existing suite (1096 tests) still passes, and flake8 is clean on the touched files.

No CLA or DCO step in this repo as far as I could find.

A malformed reference definition line, one with a label but no URL
(just trailing whitespace after the colon), makes ReferenceProcessor's
regex backtrack badly. The pattern had two adjacent [ ]* groups around
an optional newline, both matching the same run of spaces, so for a
string of n spaces there were n+1 ways to split them before the engine
gave up and tried the next split. That turns markdown.markdown('[id]:'
+ ' ' * 50000) into an eight second call instead of a near-instant one,
and it gets worse fast as the input grows.

Rewrote the pattern so the leading run of spaces is consumed greedily
by a single group, with the optional newline plus more spaces folded
into one non-ambiguous alternative after it. Verified this produces
identical matches (and identical groups) as the old pattern on the
handful of valid reference-link shapes the tests already cover, and
added a dedicated regression test that fails on unmodified master and
passes with the fix.

Fixes Python-Markdown#798.
See https://github.com/Python-Markdown/markdown/issues/798
"""
text = '[id]:' + (' ' * 50000)
start = time.time()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timed tests should likely be avoided as some systems may be running some slow hardware.

@facelessuser facelessuser left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outside the timing tests, I think this looks okay. Please remove the timed test.

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.

Parsing [[[[[[… takes quadratic time

2 participants