From b19f9e4d2dc0c4e863b4b2e567b3eaf9cf320983 Mon Sep 17 00:00:00 2001 From: hhang <17309584214@163.com> Date: Tue, 13 Oct 2026 23:36:35 +0800 Subject: [PATCH] fix(javascript): keep spread child argv counts unknown (#2748) * fix(javascript): keep spread child argv counts unknown * test(javascript): own runtime controls and tighten evidence assertions * test(apple): keep dyld slice regression portable --------- Co-authored-by: hhang <169027046+xu91102@users.noreply.github.com> --- .../javascriptSemanticChildProcesses.ts | 5 +- ...javascriptChildProcessArgumentsCli.test.ts | 72 +++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 tests/acceptance/applications/javascriptChildProcessArgumentsCli.test.ts diff --git a/src/domain/javascript/javascriptSemanticChildProcesses.ts b/src/domain/javascript/javascriptSemanticChildProcesses.ts index 0fd0c9a35f..1cffb51642 100644 --- a/src/domain/javascript/javascriptSemanticChildProcesses.ts +++ b/src/domain/javascript/javascriptSemanticChildProcesses.ts @@ -339,7 +339,10 @@ const spawnOptions = (candidate: SpawnCandidate): t.ObjectExpression | null => { const spawnArgvCount = (candidate: SpawnCandidate): number | null => { if (candidate.method === "exec") return null; const args = candidate.node.arguments[1]; - return t.isArrayExpression(args) ? args.elements.length : null; + return t.isArrayExpression(args) && + !args.elements.some((element) => t.isSpreadElement(element)) + ? args.elements.length + : null; }; const objectHasProperty = ( diff --git a/tests/acceptance/applications/javascriptChildProcessArgumentsCli.test.ts b/tests/acceptance/applications/javascriptChildProcessArgumentsCli.test.ts new file mode 100644 index 0000000000..84bfca0304 --- /dev/null +++ b/tests/acceptance/applications/javascriptChildProcessArgumentsCli.test.ts @@ -0,0 +1,72 @@ +import { join } from "node:path"; + +import { expect } from "vitest"; + +import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; +import { cliTest } from "../../support/cli/cliFixture.js"; +import { analyzeJavaScriptDirectory } from "../../support/cli/javascriptApplication.js"; +import { writeFixtureFiles } from "../../support/javascriptApplicationFixture.js"; + +cliTest.for(["CLI", "stdio MCP"] as const)( + "keeps child argv counts unknown after spread through %s", + async (surface, { cli, processes }) => { + const root = await createTestTempDirectory("rea-child-argv-"); + await writeFixtureFiles(root, { + "entry.mjs": [ + 'import { spawn, execFile, fork } from "node:child_process";', + 'const args = ["a", "b"];', + 'spawn("spread-spawn", [...args]);', + 'execFile("spread-exec", ["prefix", ...args]);', + 'fork("spread-fork", [...args, "suffix"]);', + 'spawn("fixed", ["a", "b"]);', + 'execFile("empty", []);', + ].join("\n"), + "child.cjs": "console.log(process.argv.slice(2).length);", + }); + const args = ["a", "b"]; + const counts: number[] = []; + for (const values of [ + [...args], + ["prefix", ...args], + [...args, "suffix"], + ["a", "b"], + [], + ]) { + const control = await processes.run( + process.execPath, + [join(root, "child.cjs"), ...values], + { timeoutMs: 5_000 }, + ); + expect(control.exitCode, control.stderr).toBe(0); + counts.push(Number(control.stdout)); + } + expect(counts).toEqual([2, 3, 3, 2, 0]); + const graph = (await analyzeJavaScriptDirectory(surface, cli, root)) + .semantic_graph; + const children = graph.nodes.filter( + (node) => node.kind === "child-process", + ); + expect(children).toHaveLength(5); + for (const command of ["spread-spawn", "spread-exec", "spread-fork"]) { + const child = children.find( + (node) => node.properties.command === command, + ); + expect(child?.properties.argv_count, command).toBeNull(); + expect( + graph.relations.filter( + (relation) => + relation.relation === "supplies-argv" && + relation.target_node_id === child?.node_id, + ), + ).toEqual([]); + } + expect( + children.find((node) => node.properties.command === "fixed")?.properties + .argv_count, + ).toBe(2); + expect( + children.find((node) => node.properties.command === "empty")?.properties + .argv_count, + ).toBe(0); + }, +);