From c8e85b962a58929cc272d966cfc0dd6e2409ed1d Mon Sep 17 00:00:00 2001 From: Naimur Reza <95697060+naimur-reza@users.noreply.github.com> Date: Tue, 13 Oct 2026 19:46:27 +0600 Subject: [PATCH] fix(javascript): recover child-process options without args (#2781) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: celebrate 180,000 GitHub stars 🎉 * fix(javascript): recover child-process options without args * fix(javascript): preserve execFile callback overload options Choose execFile options by the second argument object shape rather than total argument count, preserving callback overloads. Source inspection and git diff --check; tests/builds/typechecks/CI reruns skipped at explicit user request. --------- Co-authored-by: morluto <76467478+morluto@users.noreply.github.com> --- README.md | 2 +- .../childProcessOptionsRegression.test.ts | 97 +++++++++++++++++++ .../javascriptSemanticChildProcesses.ts | 13 ++- 3 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 src/domain/javascript/childProcessOptionsRegression.test.ts diff --git a/README.md b/README.md index c4a89bceee..05b4179598 100644 --- a/README.md +++ b/README.md @@ -322,7 +322,7 @@ Report vulnerabilities through [SECURITY.md](SECURITY.md). ## Star history -🎉 **170,000 GitHub stars — thank you!** +🎉 **180,000 GitHub stars — thank you!** Thanks to everyone using REA, reporting bugs, requesting features, testing builds, and contributing fixes. diff --git a/src/domain/javascript/childProcessOptionsRegression.test.ts b/src/domain/javascript/childProcessOptionsRegression.test.ts new file mode 100644 index 0000000000..8c18fe8305 --- /dev/null +++ b/src/domain/javascript/childProcessOptionsRegression.test.ts @@ -0,0 +1,97 @@ +import { describe, expect, it } from "vitest"; +import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js"; + +describe("child-process options detection", () => { + it("detects options without an args array", () => { + const ir = analyzeJavaScriptSemantics(` + import { spawn, execFile, fork } from "node:child_process"; + + spawn("tool", { + env: { A: "1" }, + stdio: "pipe" + }); + + execFile("tool", { + env: { A: "1" } + }); + + fork("worker.js", { + env: { A: "1" }, + stdio: ["pipe", "pipe", "pipe", "ipc"] + }); + `); + + expect( + ir.childProcessSpawns.map( + ({ method, environmentSupplied, stdioMode, resolution }) => ({ + method, + environmentSupplied, + stdioMode, + resolution, + }), + ), + ).toEqual([ + { + method: "spawn", + environmentSupplied: true, + stdioMode: "pipe", + resolution: "complete", + }, + { + method: "execFile", + environmentSupplied: true, + stdioMode: "default", + resolution: "complete", + }, + { + method: "fork", + environmentSupplied: true, + stdioMode: "array", + resolution: "complete", + }, + ]); + }); + + it("preserves behavior with explicit args arrays", () => { + const ir = analyzeJavaScriptSemantics(` + import { spawn, execFile, fork } from "node:child_process"; + + spawn("tool", [], { env: { A: "1" }, stdio: "pipe" }); + execFile("tool", [], { env: { A: "1" } }); + fork("worker.js", [], { + env: { A: "1" }, + stdio: ["pipe", "pipe", "pipe", "ipc"] + }); + `); + + expect( + ir.childProcessSpawns.map(({ environmentSupplied, stdioMode }) => ({ + environmentSupplied, + stdioMode, + })), + ).toEqual([ + { environmentSupplied: true, stdioMode: "pipe" }, + { environmentSupplied: true, stdioMode: "default" }, + { environmentSupplied: true, stdioMode: "array" }, + ]); + }); + + it("does not invent options when omitted", () => { + const ir = analyzeJavaScriptSemantics(` + import { spawn, execFile, fork } from "node:child_process"; + + spawn("tool"); + execFile("tool"); + fork("worker.js"); + `); + + expect( + ir.childProcessSpawns.every( + ({ environmentSupplied, stdioMode }) => + environmentSupplied === false && stdioMode === "default", + ), + ).toBe(true); + + expect(ir.childProcessSpawns).toHaveLength(3); + }); +}); diff --git a/src/domain/javascript/javascriptSemanticChildProcesses.ts b/src/domain/javascript/javascriptSemanticChildProcesses.ts index fe7b5e8022..0fd0c9a35f 100644 --- a/src/domain/javascript/javascriptSemanticChildProcesses.ts +++ b/src/domain/javascript/javascriptSemanticChildProcesses.ts @@ -324,13 +324,18 @@ const resolveChildCandidates = ( ? [] : (spawnsByBinding.get(binding.bindingId) ?? []); }; - const spawnOptions = (candidate: SpawnCandidate): t.ObjectExpression | null => { - const index = candidate.method === "exec" ? 1 : 2; - const value = candidate.node.arguments[index]; + const args = candidate.node.arguments; + + const value = + candidate.method === "exec" + ? args[1] + : t.isObjectExpression(args[1]) + ? args[1] + : args[2]; + return t.isObjectExpression(value) ? value : null; }; - const spawnArgvCount = (candidate: SpawnCandidate): number | null => { if (candidate.method === "exec") return null; const args = candidate.node.arguments[1];