Skip to content

Fix quadratic-time backtracking in attr_list attribute regex - #1656

Merged
waylan merged 5 commits into
Python-Markdown:masterfrom
naaz234:attr-list-quadratic-backtracking
Oct 21, 2026
Merged

waylan merged 5 commits into
Python-Markdown:masterfrom
naaz234:attr-list-quadratic-backtracking

Conversation

@naaz234

@naaz234 naaz234 commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Description

AttrListTreeprocessor.BASE_RE in the attr_list extension is \{\:?[ ]*([^\}\n ][^\n]*)[ ]*\}. The capture group [^\n]* already eats spaces, and the [ ]* after it eats spaces too, so a run of trailing spaces can be split between the two pieces in n+1 ways. That's harmless when a } is found, but when a block ends with an unclosed { followed by lots of spaces there is no }, and the engine tries every split before giving up. The time grows quadratically with the number of spaces.

I noticed this while reading the regex, and confirmed it with a timing run against markdown.markdown(text, extensions=['attr_list']) on 'text\n{a' + ' ' * n:

trailing spaces before after
25,000 300 ms 3 ms
50,000 1,213 ms 6 ms
100,000 3,565 ms 11 ms
200,000 10,680 ms 22 ms

Doubling the spaces roughly quadruples the time before the change, and roughly doubles it after.

The fix is to drop the trailing [ ]*. It can never match anything in a successful match anyway: the greedy [^\n]* already consumes the trailing spaces and only gives back what \} needs, so the [ ]* is always empty. Removing it leaves every match and capture group the same (I checked old vs new patterns against the existing attr_list test cases, valid attribute lists, and a few thousand random brace/space/tab strings) and takes away the ambiguity, so the failing case is now linear. This affects HEADER_RE, BLOCK_RE and INLINE_RE, which are all built from BASE_RE.

It's the same sort of problem as the ReferenceProcessor fix in #798. Added a regression test with a 50k-space input and a changelog entry.

AI Assistance Disclosure

  • No AI tools were used in preparing this PR.
  • If AI tools were used, I have disclosed which ones, and fully reviewed and verified their output.

Claude (via Claude Code) was used to help draft this PR description and to make the follow-up docstring change. I reviewed the change and can explain it.

Checklist

`AttrListTreeprocessor.BASE_RE` matched the attribute content with
`([^\}\n ][^\n]*)` and then allowed trailing spaces before the closing
brace with a separate `[ ]*`. Because `[^\n]*` already matches spaces,
the two groups both cover the same run of trailing spaces. When a block
ends with an unclosed attribute list, the closing brace is never found,
so for a run of n spaces the engine retries the match from each of the
n+1 ways to split the spaces between the two groups, which is O(n^2).

The trailing `[ ]*` can only ever match zero characters in a successful
match (the greedy `[^\n]*` consumes the spaces first), so dropping it
leaves every match and capture group unchanged while removing the
ambiguity. A two-line block such as `text\n{a` followed by 200k spaces
drops from ~10s to ~20ms, and the cost is now linear in the input size.
@waylan

waylan commented Oct 14, 2026

Copy link
Copy Markdown
Member

Please review our AI Policy. Specifically, the following:

Contributors must be able to explain all changes upon request during review.

... The use of an AI agent to submit a pull request, issue, or comment autonomously is not permitted.

You description reads like it was written by AI. Also, as the PR template was deleted, I am assuming you had AI autonomously submit the PR. This presents 3 problems:

  1. Because you didn't write the description yourself, we don't know if you are able to explain the changes.
  2. Because the PR template was deleted, you did not disclose AI use.
  3. Having AI submit the PR is expressly forbidden and while this is a simple change to the code which is relatively easy to review, the policy requires that we close the request.

That said, it is possible that this was not AI generated. If you can honestly confirm that, then we will proceed. Otherwise, this will be closed.

PS to other maintainers: this is going to be my standard response to AI submitted PRs/issues from now on. Feel free to respond in kind if you see a new PR first.

@naaz234

naaz234 commented Oct 15, 2026

Copy link
Copy Markdown
Contributor Author

Hi @waylan, I apologize for this, I used AI for the wordings and stuff and pasted it without noticing.
Since I have gone through the change myself:The regex has two pieces in a row that can both eat spaces. When there’s a { with lots of spaces after it and no closing }, the regex engine tries every possible way of dividing the spaces between those two pieces before it gives up, and it repeats that from many starting points. That’s why it gets so slow. The second piece, [ ]*, never actually does anything useful, because the first piece already grabs all the spaces. Removing it leaves only one way to match, so the engine gives up quickly.

If the policy means this has to be closed, I understand. I’m happy to resubmit with the template filled in and the AI use disclosed, or to open an issue instead so a maintainer can make the fix.

@waylan

waylan commented Oct 16, 2026

Copy link
Copy Markdown
Member

It is fine of you provide thevinfo requested in the template.

Comment thread tests/test_syntax/extensions/test_attr_list.py Outdated
@waylan waylan added the requires-changes Awaiting updates after a review. label Oct 19, 2026
@naaz234

naaz234 commented Oct 21, 2026

Copy link
Copy Markdown
Contributor Author

I've filled in the AI disclosure section of the template in the description. Claude was used for the description wording and the docstring follow-up.

@waylan waylan added approved The pull request is ready to be merged. and removed requires-changes Awaiting updates after a review. labels Oct 21, 2026
@waylan
waylan merged commit b29a5b6 into Python-Markdown:master Oct 21, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved The pull request is ready to be merged.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants