From 1260cacc67b8e0c93fb9434452053feca956e7e7 Mon Sep 17 00:00:00 2001 From: Kaoru0822 Date: Tue, 6 Oct 2026 20:33:55 -0700 Subject: [PATCH] fix(javascript): preserve computed Electron member uncertainty --- src/domain/electronStaticAnalysisIpc.ts | 7 ++- src/domain/electronStaticAnalysisNative.ts | 3 +- .../electronApplicationAnalysis.test.ts | 46 +++++++++++++++++++ 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/domain/electronStaticAnalysisIpc.ts b/src/domain/electronStaticAnalysisIpc.ts index 74861732a..2a7998a46 100644 --- a/src/domain/electronStaticAnalysisIpc.ts +++ b/src/domain/electronStaticAnalysisIpc.ts @@ -14,8 +14,8 @@ import { addLocatedFinding } from "./javascriptStaticAnalysisFindings.js"; import { argumentNode, calleeName, - propertyName, range, + semanticStaticPropertyName, } from "./javascriptStaticAnalysisHelpers.js"; import type { JavaScriptFindingContext } from "./javascriptStaticAnalysisState.js"; @@ -124,7 +124,10 @@ const inspectValidationCall = ( !t.isOptionalMemberExpression(node.callee) ) return; - const method = propertyName(node.callee.property); + const method = semanticStaticPropertyName( + node.callee.property, + node.callee.computed, + ); if (!["startsWith", "endsWith", "includes"].includes(method)) return; const subject = t.isNode(node.callee.object) ? senderSubject(node.callee.object) diff --git a/src/domain/electronStaticAnalysisNative.ts b/src/domain/electronStaticAnalysisNative.ts index 42f09e43c..6e6c75e0e 100644 --- a/src/domain/electronStaticAnalysisNative.ts +++ b/src/domain/electronStaticAnalysisNative.ts @@ -9,6 +9,7 @@ import { calleeName, propertyName, range, + semanticStaticPropertyName, stringValue, } from "./javascriptStaticAnalysisHelpers.js"; import type { JavaScriptFindingContext } from "./javascriptStaticAnalysisState.js"; @@ -161,7 +162,7 @@ const nativeRequire = ( ? nativeRequire(node.object) : undefined; if (nested === undefined) return undefined; - const member = propertyName(node.property); + const member = semanticStaticPropertyName(node.property, node.computed); return { ...nested, member: member === "" ? nested.member : member }; } if (!t.isCallExpression(node)) return undefined; diff --git a/tests/composition/javascript-applications/electronApplicationAnalysis.test.ts b/tests/composition/javascript-applications/electronApplicationAnalysis.test.ts index 4c7982a5c..2fd4f8815 100644 --- a/tests/composition/javascript-applications/electronApplicationAnalysis.test.ts +++ b/tests/composition/javascript-applications/electronApplicationAnalysis.test.ts @@ -1,3 +1,6 @@ +import { mkdir, writeFile } from "node:fs/promises"; +import { join } from "node:path"; + import { describe, expect, it } from "vitest"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; @@ -64,6 +67,28 @@ describe("static Electron application analysis", () => { }); }); + it("keeps computed identifier member keys unknown", async () => { + const root = await computedMemberFixtureDirectory(); + + const result = await reconstructJavaScriptArtifact({ input_path: root }); + const graph = parseJavaScriptApplicationGraph(result.graph); + const requestedMembers = graph.nodes.flatMap((node) => + node.kind === "native-export" + ? node.observations.flatMap(({ properties }) => { + const members = properties.requested_members; + return Array.isArray(members) ? members : []; + }) + : [], + ); + + expect(result.electron_summary).toMatchObject({ + sender_validation_observations: 0, + native_addon_bindings: 1, + resolved_native_addon_bindings: 1, + }); + expect(requestedMembers).toEqual(["*"]); + }); + it("returns a tagged cancellation without executing application code", async () => { const root = await fixtureDirectory(); const controller = new AbortController(); @@ -273,3 +298,24 @@ const fixtureDirectory = async (): Promise => { await writeElectronBoundaryFixture(root); return root; }; + +const computedMemberFixtureDirectory = async (): Promise => { + const root = await createTestTempDirectory("rea-electron-computed-members-"); + await mkdir(join(root, "native")); + await Promise.all([ + writeFile( + join(root, "main.js"), + String.raw` +const member = "read"; +const selected = require("./native/addon.node")[member]; +const startsWith = "includes"; +function validate(event) { + return event.senderFrame.url[startsWith]("file://"); +} +module.exports = { selected, validate }; +`, + ), + writeFile(join(root, "native", "addon.node"), Buffer.from([0, 1, 2, 3])), + ]); + return root; +};