diff --git a/src/Lexer.ts b/src/Lexer.ts index 89e291f19c..32969ba096 100644 --- a/src/Lexer.ts +++ b/src/Lexer.ts @@ -306,14 +306,11 @@ export class _Lexer { let maskedSrc = src; // Mask out reflinks - if (this.tokens.links) { - const links = Object.keys(this.tokens.links); - if (links.length > 0) { - maskedSrc = maskedSrc.replace(this.tokenizer.rules.inline.reflinkSearch, match0 => - links.includes(match0.slice(match0.lastIndexOf('[') + 1, -1)) - ? '[' + 'a'.repeat(match0.length - 2) + ']' - : match0); - } + if (this.tokens.links && src.includes('[')) { + maskedSrc = maskedSrc.replace(this.tokenizer.rules.inline.reflinkSearch, match0 => + Object.hasOwn(this.tokens.links, match0.slice(match0.lastIndexOf('[') + 1, -1)) + ? '[' + 'a'.repeat(match0.length - 2) + ']' + : match0); } // Mask out escaped characters diff --git a/test/specs/redos/quadratic_footnote_defs.cjs b/test/specs/redos/quadratic_footnote_defs.cjs new file mode 100644 index 0000000000..a832d18549 --- /dev/null +++ b/test/specs/redos/quadratic_footnote_defs.cjs @@ -0,0 +1,17 @@ +// Regression: many reference definitions + references must not be quadratic. +// Lexer.inlineTokens rebuilt its reflink-masking preamble on every call; +// with n defs and n refs that took ~24s at n=13000 (exponent ~3). +const n = 13000; +let refs = ''; +const defs = []; +const links = []; +for (let i = 0; i < n; i++) { + refs += `[^${i}] `; + defs.push(`[^${i}]: x`); + links.push(`^${i}`); +} + +module.exports = { + markdown: `${refs}\n\n${defs.join('\n')}`, + html: `

${links.join(' ')}

\n`, +}; diff --git a/test/specs/redos/quadratic_nested_reflink_defs.cjs b/test/specs/redos/quadratic_nested_reflink_defs.cjs new file mode 100644 index 0000000000..178db291a5 --- /dev/null +++ b/test/specs/redos/quadratic_nested_reflink_defs.cjs @@ -0,0 +1,14 @@ +// Regression: recursive link text containing '[' must not rebuild lookup state +// for every reference when many link definitions are present. +const n = 13000; +const defs = ['[id]: /url']; +const links = []; +for (let i = 0; i < n; i++) { + defs.push(`[unused-${i}]: /${i}`); + links.push('[x]'); +} + +module.exports = { + markdown: `${'[[x]][id] '.repeat(n)}\n\n${defs.join('\n')}`, + html: `

${links.join(' ')}

\n`, +};