From 6b460683d797f88adc5ee725ddac2f252f177aa0 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sun, 11 Oct 2026 23:55:33 +0800 Subject: [PATCH] fix(javascript): respect hoisted fresh function declarations Function declarations provide fresh targets before their textual position. Retain initialization-order checks for variable and class values, including aliases. Cover the reviewed regression with source controls and the public CLI/MCP fixture. --- docs/javascript-artifact-reconstruction.md | 3 +- .../javascript/electronModuleInterop.test.ts | 30 ++++++++++++++++++- .../javascript/javascriptModuleInterop.ts | 3 +- .../electronModuleInteropCli.test.ts | 2 ++ 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/docs/javascript-artifact-reconstruction.md b/docs/javascript-artifact-reconstruction.md index 747cd37777..16844ef253 100644 --- a/docs/javascript-artifact-reconstruction.md +++ b/docs/javascript-artifact-reconstruction.md @@ -343,7 +343,8 @@ of intrinsic objects. Stable identifier chains to fresh object, array, function, undecorated class or RegExp syntax keep those targets separate from intrinsic objects when initialized before use; opaque, decorated, reassigned or not-yet-initialized targets remain unresolved. Writes during class initialization -are still retained. +are still retained. Function declarations are available before their textual +position; variable and class initializers must precede use. Direct and destructuring assignment, loop, update, and delete targets are checked through namespace aliases; unrelated member writes do not discard observed API identity. This static check does diff --git a/src/domain/javascript/electronModuleInterop.test.ts b/src/domain/javascript/electronModuleInterop.test.ts index 5fa2949e14..bfa59485d9 100644 --- a/src/domain/javascript/electronModuleInterop.test.ts +++ b/src/domain/javascript/electronModuleInterop.test.ts @@ -263,7 +263,7 @@ describe("Electron module interop pattern writes", () => { }); }); -describe("Electron module interop capture and scope", () => { +describe("Electron module interop intrinsic captures", () => { it.each([ "var captured = Object.defineProperty;", "const {defineProperty:captured} = Object;", @@ -307,6 +307,34 @@ describe("Electron module interop capture and scope", () => { `); expect(analysis.electron.browser_windows).toEqual([]); }); +}); + +describe("Electron module interop capture and scope", () => { + it.each(["metadata", "alias"])( + "recognizes a hoisted function target through %s", + (target) => { + const analysis = analyzeJavaScriptStaticSource(` + const alias = metadata; + Object.defineProperty(${target}, "x", {value:1}); + function metadata() {} + ${ROLLDOWN_MODULE_INTEROP} ${IMPORT} ${WRAP} ${WINDOW} + `); + expect(analysis.electron.browser_windows).toHaveLength(1); + }, + ); + + it.each([ + "var metadata = function () {};", + "const metadata = () => {};", + "class metadata {}", + ])("does not hoist fresh targets from %s", (declaration) => { + const analysis = analyzeJavaScriptStaticSource(` + Object.defineProperty(metadata, "x", {value:1}); + ${declaration} + ${ROLLDOWN_MODULE_INTEROP} ${IMPORT} ${WRAP} ${WINDOW} + `); + expect(analysis.electron.browser_windows).toEqual([]); + }); it.each([ 'const {defineProperty:change} = Object; change(Object,"create",{value:customCreate});', diff --git a/src/domain/javascript/javascriptModuleInterop.ts b/src/domain/javascript/javascriptModuleInterop.ts index ee45f6299f..f8eafd5620 100644 --- a/src/domain/javascript/javascriptModuleInterop.ts +++ b/src/domain/javascript/javascriptModuleInterop.ts @@ -187,7 +187,8 @@ export const createSemanticModuleInterop = ( if ( binding === undefined || initializer === undefined || - (initializer.end ?? Infinity) >= (current.start ?? -1) || + (!t.isFunctionDeclaration(initializer) && + (initializer.end ?? Infinity) >= (current.start ?? -1)) || visited.has(binding.bindingId) ) return false; diff --git a/tests/acceptance/applications/electronModuleInteropCli.test.ts b/tests/acceptance/applications/electronModuleInteropCli.test.ts index 146ee7d358..33de46ef31 100644 --- a/tests/acceptance/applications/electronModuleInteropCli.test.ts +++ b/tests/acceptance/applications/electronModuleInteropCli.test.ts @@ -50,6 +50,8 @@ cliTest.for(["CLI", "stdio MCP"] as const)( Object.defineProperty(localClass,"x",{value:1}); const localRegExp = /local/; Object.defineProperty(localRegExp,"x",{value:1}); + Object.defineProperty(hoistedFunction,"x",{value:1}); + function hoistedFunction() {} const captured = Object.defineProperty; Object.defineProperty = custom; ${ROLLDOWN_MODULE_INTEROP.replace("__defProp = Object.defineProperty", "__defProp = captured")}