From 21eef76c17687efda5f9b9f1d6dd8ab3ea13d64d Mon Sep 17 00:00:00 2001 From: JSap0914 Date: Tue, 23 Jun 2026 21:44:45 +0900 Subject: [PATCH] =?UTF-8?q?fix(autoInject):=20eliminate=20O(n=C2=B2)=20str?= =?UTF-8?q?ipComments=20and=20regex=20stack=20overflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit stripComments used character-by-character string concatenation (stripped += string[index]) which is O(n²) in V8 for large inputs — ~1 second for a 6 M-character string. Switch to slice-based accumulation: collect non-comment slices into an array, push only at comment boundaries, join at the end. This reduces the cost to O(n). Additionally, ARROW_FN_ARGS regex applied to a very long string with no '=>' caused RangeError: Maximum call stack size exceeded due to deep recursive NFA backtracking. Arrow functions always contain '=>', so guard the regex call with src.indexOf('=>') !== -1 to skip it entirely for inputs that can never match. Together these fix the failing test: autoInject > should not be subject to ReDoS Fixes #1975 regression introduced by #1980. --- lib/autoInject.js | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/autoInject.js b/lib/autoInject.js index 68eb3c99c..a8721d587 100644 --- a/lib/autoInject.js +++ b/lib/autoInject.js @@ -8,37 +8,45 @@ var FN_ARG_SPLIT = /,/; var FN_ARG = /(=.+)?(\s*)$/; function stripComments(string) { - let stripped = ''; + const parts = []; let index = 0; + let segmentStart = 0; let endBlockComment = string.indexOf('*/'); while (index < string.length) { if (string[index] === '/' && string[index+1] === '/') { - // inline comment + // inline comment: flush current segment, skip to end of line + if (index > segmentStart) parts.push(string.slice(segmentStart, index)); let endIndex = string.indexOf('\n', index); index = (endIndex === -1) ? string.length : endIndex; + segmentStart = index; } else if ((endBlockComment !== -1) && (string[index] === '/') && (string[index+1] === '*')) { - // block comment + // block comment: flush current segment, skip to end of block + if (index > segmentStart) parts.push(string.slice(segmentStart, index)); let endIndex = string.indexOf('*/', index); if (endIndex !== -1) { index = endIndex + 2; endBlockComment = string.indexOf('*/', index); } else { - stripped += string[index]; - index++; + index = string.length; } + segmentStart = index; } else { - stripped += string[index]; index++; } } - return stripped; + if (segmentStart < string.length) parts.push(string.slice(segmentStart)); + return parts.join(''); } function parseParams(func) { const src = stripComments(func.toString()); let match = src.match(FN_ARGS); if (!match) { - match = src.match(ARROW_FN_ARGS); + // Arrow functions always contain '=>'; skip the regex for inputs that + // lack it to avoid catastrophic backtracking on large non-function strings. + if (src.indexOf('=>') !== -1) { + match = src.match(ARROW_FN_ARGS); + } } if (!match) throw new Error('could not parse args in autoInject\nSource:\n' + src) let [, args] = match