feat: Add comprehensive tests for contentPattern and glob pattern matching

- Implemented final verification tests for contentPattern to validate behavior with empty strings, dangerous patterns, and normal patterns.
- Created glob pattern matching tests to verify regex conversion and matching functionality.
- Developed infinite loop risk tests using Worker threads to isolate potential blocking operations.
- Introduced optimized contentPattern tests to validate improvements in the findMatches function.
- Added verification tests to assess the effectiveness of contentPattern optimizations.
- Conducted safety tests for contentPattern to identify edge cases and potential vulnerabilities.
- Implemented unrestricted loop tests to analyze infinite loop risks without match limits.
- Developed tests for zero-width pattern detection logic to ensure proper handling of dangerous regex patterns.
This commit is contained in:
catlog22
2026-02-09 11:13:01 +08:00
parent dfe153778c
commit 964292ebdb
62 changed files with 7588 additions and 374 deletions

173
test-infinite-loop.mjs Normal file
View File

@@ -0,0 +1,173 @@
/**
* contentPattern 无限循环风险测试
* 使用 Worker 隔离环境,防止主线程卡死
*/
import { Worker } from 'worker_threads';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// 测试函数代码
const workerCode = `
function findMatches(content, pattern) {
try {
const regex = new RegExp(pattern, 'gm');
const matches = [];
let match;
let iterations = 0;
const MAX_ITERATIONS = 1000;
while ((match = regex.exec(content)) !== null && matches.length < 10) {
iterations++;
if (iterations > MAX_ITERATIONS) {
return { error: 'Exceeded max iterations - possible infinite loop', iterations };
}
const lineStart = content.lastIndexOf('\\n', match.index) + 1;
const lineEnd = content.indexOf('\\n', match.index);
const line = content.substring(lineStart, lineEnd === -1 ? undefined : lineEnd).trim();
matches.push(line.substring(0, 200));
}
return { matches, iterations };
} catch (error) {
return { error: error.message };
}
};
self.on('message', ({ content, pattern }) => {
const result = findMatches(content, pattern);
self.postMessage(result);
});
`;
async function testInfiniteLoop(content, pattern, testName, timeout = 2000) {
console.log(`\n测试: ${testName}`);
console.log(`模式: "${pattern}"`);
// 创建临时 worker
const blob = new Blob([workerCode], { type: 'application/javascript' });
const workerUrl = URL.createObjectURL(blob);
try {
const worker = new Worker(workerUrl);
const result = await new Promise((resolve) => {
const timer = setTimeout(() => {
worker.terminate();
resolve({ error: 'TIMEOUT - Infinite loop detected!', timeout: true });
}, timeout);
worker.once('message', (data) => {
clearTimeout(timer);
worker.terminate();
resolve(data);
});
worker.once('error', (error) => {
clearTimeout(timer);
worker.terminate();
resolve({ error: error.message });
});
worker.postMessage({ content, pattern });
});
URL.revokeObjectURL(workerUrl);
if (result.error) {
if (result.timeout) {
console.log(`❌ 超时 - 检测到无限循环!`);
return { hasInfiniteLoop: true };
} else {
console.log(`⚠️ 错误: ${result.error}`);
return { hasInfiniteLoop: false, error: result.error };
}
} else {
console.log(`✅ 结果: ${result.matches?.length || 0} 个匹配, ${result.iterations} 次迭代`);
return { hasInfiniteLoop: false, iterations: result.iterations };
}
} catch (error) {
console.log(`❌ 异常: ${error.message}`);
return { hasInfiniteLoop: false, error: error.message };
}
}
async function main() {
console.log('=== contentPattern 无限循环风险测试 ===\n');
console.log('⚠️ 危险测试将在 Worker 中运行2秒超时\n');
const tests = [
{
name: '正常模式',
content: 'Line 1\nLine 2\nLine 3',
pattern: 'Line',
expected: '正常'
},
{
name: '空字符串模式(危险)',
content: 'Line 1\nLine 2\nLine 3',
pattern: '',
expected: '无限循环'
},
{
name: '零宽匹配(危险)',
content: 'abc\ndef\nghi',
pattern: 'x*', // 匹配 0 个或多个 x
expected: '无限循环'
},
{
name: '或运算符空匹配(危险)',
content: 'some text',
pattern: 'a|', // 匹配 'a' 或空
expected: '无限循环'
},
{
name: 'ReDoS 攻击(危险)',
content: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab',
pattern: '(a+)+b',
expected: '超时'
},
{
name: '正常匹配',
content: 'TODO fix this\nTODO fix that',
pattern: 'TODO',
expected: '正常'
}
];
const results = [];
for (const test of tests) {
const result = await testInfiniteLoop(test.content, test.pattern, test.name, 2000);
results.push({ ...test, result });
}
console.log(`\n${'='.repeat(60)}`);
console.log('\n测试结果汇总:\n');
const infiniteLoopCount = results.filter(r => r.result.hasInfiniteLoop).length;
const timeoutCount = results.filter(r => r.result.error?.includes('TIMEOUT')).length;
results.forEach((r, i) => {
const status = r.result.hasInfiniteLoop ? '❌ 无限循环' :
r.result.error?.includes('TIMEOUT') ? '⏱️ 超时' :
r.result.error ? '⚠️ 错误' : '✅ 正常';
console.log(`${i + 1}. ${r.name.padEnd(30)} ${status}`);
if (r.result.iterations) {
console.log(` 迭代次数: ${r.result.iterations}`);
}
});
console.log(`\n总结:`);
console.log(`- 无限循环风险: ${infiniteLoopCount}`);
console.log(`- 超时风险: ${timeoutCount}`);
console.log(`- 正常工作: ${results.length - infiniteLoopCount - timeoutCount}`);
if (infiniteLoopCount > 0 || timeoutCount > 0) {
console.log(`\n⚠️ contentPattern 存在严重的无限循环和 ReDoS 风险!`);
}
}
main().catch(console.error);