/** * Whether a JS function binds a name itself (jsFunctionLocalScope, #2226) is * read off the function's comment-stripped code with a parameter-list regex. * Its `(? { let tmpDir: string | undefined; let cg: CodeGraph | undefined; afterEach(() => { cg?.close(); cg = undefined; if (tmpDir) fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5 }); tmpDir = undefined; }); const callees = (graph: CodeGraph, name: string): string[] => { const from = graph.getNodesByName(name).find((n) => n.kind === 'function')!; return graph.getOutgoingEdges(from.id).filter((e) => e.kind === 'calls') .map((e) => graph.getNode(e.target)!.name); }; it('stays linear when V8 stops optimizing regexes', async () => { // Blanked by the comment stripper into one 60,000-character run of spaces. const comment = `/* ${'word '.repeat(12_000)} */`; tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-js-lookbehind-')); fs.writeFileSync(path.join(tmpDir, 'paint.js'), [ 'function paintWidget() {\n return 1;\n}', // Calls the file's own paintWidget: the check scans all of host's code and finds no binding. `function host() {\n ${comment}\n return paintWidget();\n}`, // Calls its parameter, which shadows the file's paintWidget. `function shadow(paintWidget) {\n ${comment}\n return paintWidget();\n}`, '', ].join('\n')); v8.setFlagsFromString('--no-regexp-optimization'); const started = Date.now(); try { cg = CodeGraph.initSync(tmpDir); await cg.indexAll(); } finally { v8.setFlagsFromString('--regexp-optimization'); } const elapsed = Date.now() - started; expect(callees(cg, 'host')).toEqual(['paintWidget']); expect(callees(cg, 'shadow')).toEqual([]); // About a second with the fix; minutes when the lookbehind reads back through the run at every position. expect(elapsed).toBeLessThan(20_000); }, 300_000); });