fix(javascript): prove namespace interop before attributing Electron APIs (#1857)

* fix(javascript): prove Electron module namespace conversion

* refactor(javascript): share AST child field metadata

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>

* fix(javascript): retain destructured intrinsic writes

Share one pattern-target walker across lexical definitions, member mutation
tracking and Electron identity writes. Keep overwritten globals unresolved
without treating property keys or shadowed locals as global replacements.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>

* fix(javascript): preserve intrinsic projections and fresh target identity

Resolve known mutators through static binding projections and prove stable
identifier chains to fresh literals before widening unknown mutation targets.
Keep reassigned, uninitialized and member-projected targets conservative.
Cover both review regressions through source tests and inert CLI/MCP fixtures.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>

* fix(javascript): retain fresh function and class target identity

Recognize stable local function, undecorated class and RegExp targets before
widening intrinsic mutations. Keep decorated classes opaque because their
binding value can be replaced, and preserve class-initialization writes.
Cover fresh targets and replacement decorators in inert CLI/MCP fixtures.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>

* fix(javascript): preserve intrinsic alias capture timing

Validate each alias read after initialization and retain the original function capture across copies. Share declaration initializer facts with RPC and Electron analysis, including class declarations used for fresh-target identity. Accept single-statement control blocks while keeping lexical roles and added operations checked. Cover the capture regressions through inert source and public CLI/MCP workflows.

* 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.

* fix(javascript): preserve proven interop result projections

* fix(javascript): retain stable declarations and bound intrinsic writes

Keep repeated variable declarations with one unconditional initializer while
rejecting assignments, conditional initialization and multiple values.
Normalize known intrinsic calls, aliases and bound argument prefixes at the
semantic owner, retaining opaque targets without tainting unused bindings or
fresh local objects. Cover both regressions through source and CLI/MCP tests.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>

* fix(javascript): retain bound intrinsic mutation effects

* fix(javascript): preserve bound receivers for intrinsic invocation

* fix(javascript): prove generic binding and invocation effects

* fix(javascript): retain Reflect set receiver effects

---------

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-10-12 01:06:33 +08:00
1 parent 1769417001
commit 9b3e59804d
15 files changed
+2043 -221

No files matched your search

@@ -0,0 +1,618 @@
import { describe, expect, it } from "vitest";
import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js";
import { ROLLDOWN_MODULE_INTEROP } from "./javascriptModuleInterop.fixture.js";
const WINDOW =
'new electron.BrowserWindow({webPreferences:{preload:"./preload.js"}});';
const IMPORT = 'let electron = require("electron");';
const WRAP = "electron = __toESM(electron, 1);";
// The upstream runtime uses control-body braces that its bundled printer omits.
// https://github.com/rolldown/rolldown/blob/22a0ab13732d81f2559ad2b7064b688699324889/crates/rolldown/src/runtime/runtime-base.js
const BLOCK_MODULE_INTEROP = ROLLDOWN_MODULE_INTEROP.replace(
'if (from && typeof from === "object" || typeof from === "function") for',
'if (from && typeof from === "object" || typeof from === "function") { for',
)
.replace(
"if (!__hasOwnProp.call(to, key) && key !== except) __defProp",
"if (!__hasOwnProp.call(to, key) && key !== except) { __defProp",
)
.replace(" });", " });\n }")
.replace(" return to;", " }\n return to;");
describe("Electron module interop identity", () => {
it("preserves named APIs through the released Rolldown namespace conversion", () => {
const analysis = analyzeJavaScriptStaticSource(`${ROLLDOWN_MODULE_INTEROP}
${IMPORT} ${WRAP} ${WINDOW}
new electron.BrowserWindow({webPreferences:{preload:runtimePath}});
electron.ipcMain.handle("ping", () => "pong");
electron.contextBridge.exposeInMainWorld("api", {ping:true});
`);
expect(analysis.electron.browser_windows).toMatchObject([
{ preload_path: "./preload.js" },
{ preload_path: null },
]);
expect(analysis.electron.ipc).toMatchObject([
{ channel: "ping", side: "main" },
]);
expect(analysis.electron.context_bridge_apis).toMatchObject([
{ api_key: "api" },
]);
});
it("follows lexical identities when helper and parameter names change", () => {
const source = `${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP} ${WINDOW}`
.replaceAll("__toESM", "namespaceOf")
.replaceAll("__copyProps", "copyNamed")
.replaceAll("mod", "inputModule")
.replaceAll("isNodeMode", "nodeMode")
.replaceAll("target", "namespaceTarget")
.replaceAll("from", "sourceObject");
expect(
analyzeJavaScriptStaticSource(source).electron.browser_windows,
).toMatchObject([{ preload_path: "./preload.js" }]);
});
it("preserves the same operations through control-body braces", () => {
expect(
analyzeJavaScriptStaticSource(
`${BLOCK_MODULE_INTEROP}\n${IMPORT} ${WRAP} ${WINDOW}`,
).electron.browser_windows,
).toMatchObject([{ preload_path: "./preload.js" }]);
});
it("keeps additional operations inside a control block unresolved", () => {
const altered = BLOCK_MODULE_INTEROP.replace(
"{ __defProp",
"{ from = unrelated; __defProp",
);
expect(
analyzeJavaScriptStaticSource(`${altered}\n${IMPORT} ${WRAP} ${WINDOW}`)
.electron.browser_windows,
).toEqual([]);
});
});
const BOUND_INTRINSIC_MUTATIONS: [string, string][] = [
[
"intrinsic changed through aliased prebound arguments",
`const objectAlias = Object; const key = "create"; const define = Object.defineProperty; const mutate = define.bind(null, objectAlias, key); mutate({value:customCreate}); ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"intrinsic changed through nested prebinding",
`const first = Object.defineProperty.bind(null, Object); const second = first.bind(null, "create"); second({value:customCreate}); ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"a replaced bound mutator bind method is unknown",
`const first = Object.defineProperty.bind(null, Object); first.bind = customBind; const second = first.bind(null, "create"); second({value:customCreate}); ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"intrinsic changed through a computed prebound call",
`const mutate = Object.defineProperty.bind(null, Object, "create"); mutate["call"](null, {value:customCreate}); ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"intrinsic changed through a computed prebound apply",
`const mutate = Object.defineProperty.bind(null, Object, "create"); mutate["apply"](null, [{value:customCreate}]); ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"a replaced bind intrinsic makes a prebound mutator unknown",
`Function.prototype.bind = customBind; const mutate = Object.defineProperty.bind(null, Object, "create"); mutate({value:customCreate}); ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
];
describe("Electron module interop uncertainty", () => {
it.each([
...BOUND_INTRINSIC_MUTATIONS,
[
"intrinsic changed through an unresolved mutator target",
`Object.defineProperty(getTarget(), "create", {value:customCreate}); ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"intrinsic changed via dynamic apply",
`const args = [Object, "getOwnPropertyNames", {value:() => []}]; Object.defineProperty.apply(null, args); ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"intrinsic changed via spread call",
`const args = [Object, "getOwnPropertyNames", {value:() => []}]; Object.defineProperty.call(null, ...args); ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"intrinsic changed via Reflect.apply",
`const args = [Object, "getOwnPropertyNames", {value:() => []}]; Reflect.apply(Object.defineProperty, null, args); ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"intrinsic changed via spread Reflect.apply",
`const args = [null, [Object, "create", {value:customCreate}]]; Reflect.apply(Object.defineProperty, ...args); ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"intrinsic changed via call",
`Object.defineProperty.call(null, Object, "getOwnPropertyNames", {value:() => []}); ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"intrinsic changed via apply",
`Object.defineProperty.apply(null, [Object, "getOwnPropertyNames", {value:() => []}]); ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"intrinsic changed with defineProperty",
`Object.defineProperty(Object, "getOwnPropertyNames", {value:() => []}); ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"intrinsic changed with Reflect.set",
`Reflect.set(Object, "getOwnPropertyNames", () => []); ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"intrinsic changed with an aliased mutator",
`const change = Object.defineProperty; change(Object, "create", {value:customCreate}); ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"intrinsic changed with assign",
`Object.assign(Object, {getOwnPropertyNames:() => []}); ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"intrinsic changed with defineProperties",
`Object.defineProperties(Function.prototype, {bind:{value:customBind}}); ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"overwritten Object global",
`Object = customObject; ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"overwritten intrinsic through an alias",
`const objectAlias = Object; objectAlias.create = customCreate; ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"overwritten intrinsic through globalThis",
`globalThis.Object.create = customCreate; ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"helper called before initialization",
`${IMPORT} ${WRAP} ${ROLLDOWN_MODULE_INTEROP}`,
],
[
"fake helper",
`${ROLLDOWN_MODULE_INTEROP}\n__toESM = (mod) => ({BrowserWindow:Fake}); ${IMPORT} ${WRAP}`,
],
[
"changed getter",
`${ROLLDOWN_MODULE_INTEROP.replace("from[k]", "to[k]")}\n${IMPORT} ${WRAP}`,
],
[
"replaced copy helper",
`${ROLLDOWN_MODULE_INTEROP}\n__copyProps = () => ({}); ${IMPORT} ${WRAP}`,
],
[
"replaced intrinsic alias",
`${ROLLDOWN_MODULE_INTEROP}\n__defProp = () => ({}); ${IMPORT} ${WRAP}`,
],
[
"shadowed Object",
`const Object = customObject; ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"overwritten intrinsic",
`Object.create = customCreate; ${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP}`,
],
[
"overwritten alias call",
`${ROLLDOWN_MODULE_INTEROP}\n__hasOwnProp.call = customCall; ${IMPORT} ${WRAP}`,
],
[
"overwritten Function bind",
`${ROLLDOWN_MODULE_INTEROP}\nFunction.prototype.bind = customBind; ${IMPORT} ${WRAP}`,
],
[
"conditional conversion",
`${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} if (flag) { ${WRAP} }`,
],
[
"compound assignment",
`${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} electron ||= __toESM(electron, 1);`,
],
[
"other input",
`${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} electron = __toESM(other, 1);`,
],
[
"later overwrite",
`${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP} electron = unrelated;`,
],
[
"overwritten constructor",
`${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP} electron.BrowserWindow = Fake;`,
],
])("does not infer Electron identity after %s", (_name, setup) => {
expect(
analyzeJavaScriptStaticSource(`${setup}\n${WINDOW}`).electron
.browser_windows,
).toEqual([]);
});
});
describe("Electron module interop helper results", () => {
it.each([
'const {BrowserWindow} = __toESM(require("electron"), 1); new BrowserWindow({});',
'const {BrowserWindow: Window} = __toESM(require("electron"), 1); new Window({});',
'const {default: {BrowserWindow}} = __toESM(require("electron"), 1); new BrowserWindow({});',
'const Window = __toESM(require("electron"), 1).BrowserWindow; new Window({});',
])("preserves a proven result projection: %s", (source) => {
expect(
analyzeJavaScriptStaticSource(`${ROLLDOWN_MODULE_INTEROP}\n${source}`)
.electron.browser_windows,
).toHaveLength(1);
});
it.each([
'const {[key]: Window} = __toESM(require("electron"), 1); new Window({});',
'const {...electron} = __toESM(require("electron"), 1); new electron.BrowserWindow({});',
'let {BrowserWindow} = __toESM(require("electron"), 1); BrowserWindow = Fake; new BrowserWindow({});',
])("keeps an uncertain result projection unresolved: %s", (source) => {
expect(
analyzeJavaScriptStaticSource(`${ROLLDOWN_MODULE_INTEROP}\n${source}`)
.electron.browser_windows,
).toEqual([]);
});
it.each([
"",
", 0",
", false",
", true",
", 1",
', "node"',
', ""',
", null",
])("preserves named exports with a literal node mode: %s", (mode) => {
const analysis = analyzeJavaScriptStaticSource(`${ROLLDOWN_MODULE_INTEROP}
const fresh = __toESM(require("electron")${mode});
new fresh.BrowserWindow({});
const {BrowserWindow} = __toESM(require("electron")${mode});
new BrowserWindow({});
let electron = require("electron");
electron = __toESM(electron${mode}); ${WINDOW}
`);
expect(analysis.electron.browser_windows).toHaveLength(3);
});
it.each(["", ", 0", ", false"])(
"does not assume a module-valued default with node mode: %s",
(mode) => {
const analysis = analyzeJavaScriptStaticSource(`${ROLLDOWN_MODULE_INTEROP}
const original = require("electron");
original.__esModule = true;
original.default = {BrowserWindow: Fake};
const {default: {BrowserWindow}} = __toESM(require("electron")${mode});
new BrowserWindow({});
const other = __toESM(require("electron")${mode}).default;
new other.BrowserWindow({});
new (__toESM(require("electron")${mode}).default.BrowserWindow)({});
`);
expect(analysis.electron.browser_windows).toEqual([]);
},
);
it.each(["__toESM", "identity"])(
"rejects side-effecting extra arguments to %s",
(helper) => {
const analysis = analyzeJavaScriptStaticSource(`${ROLLDOWN_MODULE_INTEROP}
function identity(mod) { return mod; }
const electron = ${helper}(require("electron"), mutate()); ${WINDOW}
`);
expect(analysis.electron.browser_windows).toEqual([]);
},
);
it.each([
"const __toESM = () => ({BrowserWindow:FakeWindow});",
"const __toESM = async (mod) => mod;",
"function* __toESM(mod) { return mod; }",
"const __toESM = (this && this.__toESM) || ((mod) => mod);",
])("keeps an unproven fresh result unresolved: %s", (helper) => {
const analysis = analyzeJavaScriptStaticSource(`${helper}
const electron = __toESM(require("electron"), 1); ${WINDOW}
new (__toESM(require("electron"), 1)).BrowserWindow({});
`);
expect(analysis.electron.browser_windows).toEqual([]);
});
it("preserves a proven fresh namespace and a pure identity helper", () => {
const analysis = analyzeJavaScriptStaticSource(`${ROLLDOWN_MODULE_INTEROP}
const electron = __toESM(require("electron"), 1); ${WINDOW}
new (__toESM(require("electron"), 1)).BrowserWindow({});
function passthrough(value) {return value;}
const plain = passthrough(require("electron"));
new plain.BrowserWindow({});
`);
expect(analysis.electron.browser_windows).toHaveLength(3);
});
});
describe("Electron module interop pattern writes", () => {
it.each([
"({Object} = replacements);",
"[Function] = replacements;",
"({nested:{Object}} = replacements);",
"({replacement:Object = customObject} = replacements);",
"({...Object} = replacements);",
"[...Function] = replacements;",
"for ({Object} of replacements) {}",
"for ([Function] in replacements) {}",
])("rejects an intrinsic replaced through %s", (write) => {
const analysis = analyzeJavaScriptStaticSource(`
${write} ${ROLLDOWN_MODULE_INTEROP} ${IMPORT} ${WRAP} ${WINDOW}
`);
expect(analysis.parse_error_count).toBe(0);
expect(analysis.electron.browser_windows).toEqual([]);
});
it.each([
"({Object:unrelated} = replacements);",
"function decoy(Object) {({Object} = replacements);}",
])("preserves intrinsic identity for an unrelated target: %s", (write) => {
const analysis = analyzeJavaScriptStaticSource(`
${write} ${ROLLDOWN_MODULE_INTEROP} ${IMPORT} ${WRAP} ${WINDOW}
`);
expect(analysis.parse_error_count).toBe(0);
expect(analysis.electron.browser_windows).toHaveLength(1);
});
it("preserves captured operations after later property destructuring", () => {
const analysis = analyzeJavaScriptStaticSource(`
${ROLLDOWN_MODULE_INTEROP} ({create:Object.create} = replacements);
${IMPORT} ${WRAP} ${WINDOW}
`);
expect(analysis.electron.browser_windows).toHaveLength(1);
});
});
describe("Electron module interop intrinsic captures", () => {
it.each([
"var captured = Object.defineProperty;",
"const {defineProperty:captured} = Object;",
"var first = Object.defineProperty; const captured = first;",
])("retains the original function capture through %s", (capture) => {
const runtime = ROLLDOWN_MODULE_INTEROP.replace(
"__defProp = Object.defineProperty",
"__defProp = captured",
);
const analysis = analyzeJavaScriptStaticSource(`
${capture} Object.defineProperty = custom;
${runtime} ${IMPORT} ${WRAP} ${WINDOW}
`);
expect(analysis.electron.browser_windows).toHaveLength(1);
});
it.each(["var", "const"])(
"keeps an intrinsic alias read before its %s initializer unresolved",
(kind) => {
const runtime = ROLLDOWN_MODULE_INTEROP.replace(
"__defProp = Object.defineProperty",
"__defProp = later",
);
const analysis = analyzeJavaScriptStaticSource(`
${runtime} ${kind} later = Object.defineProperty;
${IMPORT} ${WRAP} ${WINDOW}
`);
expect(analysis.electron.browser_windows).toEqual([]);
},
);
it("requires the helper's intrinsic binding to initialize before conversion", () => {
const runtime = ROLLDOWN_MODULE_INTEROP.replace(
"var __defProp = Object.defineProperty;",
"",
);
const analysis = analyzeJavaScriptStaticSource(`
var captured = Object.defineProperty;
${runtime} ${IMPORT} ${WRAP} ${WINDOW}
var __defProp = captured;
`);
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});',
"const objectAlias = Object; const {assign:change} = objectAlias; change(Object,{create:customCreate});",
'const {set:change} = Reflect; change(Object,"create",customCreate);',
'const {Object:{defineProperty:change}} = globalThis; change(Object,"create",{value:customCreate});',
])("rejects a projected intrinsic mutator alias: %s", (write) => {
const source = `${write} ${ROLLDOWN_MODULE_INTEROP}
${IMPORT} ${WRAP} ${WINDOW}
`.replaceAll("__toESM", "namespaceOf");
const analysis = analyzeJavaScriptStaticSource(source);
expect(analysis.parse_error_count).toBe(0);
expect(analysis.electron.browser_windows).toEqual([]);
});
it.each([
"const metadata = {};",
"const metadata = [];",
"const initial = {}; const metadata = initial;",
"const metadata = () => {};",
"const metadata = function () {};",
"const metadata = async () => {};",
"const metadata = function* () {};",
"const metadata = class {};",
"const metadata = /local/;",
"function metadata() {}",
"class metadata {}",
])(
"keeps a fresh mutator target separate from intrinsic identity: %s",
(setup) => {
const analysis = analyzeJavaScriptStaticSource(`
${setup} Object.defineProperty(metadata,"x",{value:1});
${ROLLDOWN_MODULE_INTEROP} ${IMPORT} ${WRAP} ${WINDOW}
`);
expect(analysis.electron.browser_windows).toHaveLength(1);
},
);
it.each([
"const metadata = getMetadata();",
"let metadata = {}; metadata = Object;",
"Object.defineProperty(metadata,'x',{value:1}); const metadata = {};",
"const metadata = {intrinsic:Object}; Object.defineProperty(metadata.intrinsic,'create',{value:customCreate});",
"const metadata = class {static {Object.create = customCreate;}};",
"@(() => Object) class metadata {}",
])("keeps an unproven mutator target unresolved: %s", (setup) => {
const analysis = analyzeJavaScriptStaticSource(`
${setup} Object.defineProperty(metadata,"x",{value:1});
${ROLLDOWN_MODULE_INTEROP} ${IMPORT} ${WRAP} ${WINDOW}
`);
expect(analysis.electron.browser_windows).toEqual([]);
});
it("does not reject unrelated intrinsic property definitions", () => {
const analysis = analyzeJavaScriptStaticSource(`
Object.defineProperty(Object, "unrelated", {value:1});
${ROLLDOWN_MODULE_INTEROP} ${IMPORT} ${WRAP} ${WINDOW}
`);
expect(analysis.electron.browser_windows).toHaveLength(1);
});
it("keeps opaque targets in a generic helper separate from outer module identity", () => {
const analysis = analyzeJavaScriptStaticSource(`
function genericAssign() { Object.assign.apply(null, arguments); }
${ROLLDOWN_MODULE_INTEROP} ${IMPORT} ${WRAP} ${WINDOW}
`);
expect(analysis.electron.browser_windows).toHaveLength(1);
});
it("rejects an opaque intrinsic mutation in the namespace's own callable", () => {
const analysis = analyzeJavaScriptStaticSource(`${ROLLDOWN_MODULE_INTEROP}
function openWindow(args) {
Object.defineProperty.apply(null, args);
${IMPORT} ${WRAP} ${WINDOW}
}
`);
expect(analysis.electron.browser_windows).toEqual([]);
});
it("preserves captured intrinsics across later vendor polyfills and callable writes", () => {
const analysis = analyzeJavaScriptStaticSource(`${ROLLDOWN_MODULE_INTEROP}
Object.create = customCreate;
${IMPORT} ${WRAP}
function runLoop(key) { globalThis[key] = this; }
function polyfill() { Object.create = customCreate; }
${WINDOW}
`);
expect(analysis.electron.browser_windows).toHaveLength(1);
});
it("keeps a shadowed namespace separate from the converted module", () => {
const analysis = analyzeJavaScriptStaticSource(`${ROLLDOWN_MODULE_INTEROP}
${IMPORT} ${WRAP} ${WINDOW}
function decoy(electron) { ${WINDOW} }
`);
expect(analysis.electron.browser_windows).toHaveLength(1);
});
});
describe("Electron module interop bound mutators", () => {
it.each([
'const change = Object.defineProperty.bind(null); change(Object,"create",{value:customCreate});',
'const change = Object.defineProperty.bind(null,Object,"create"); change({value:customCreate});',
'const first = Object.defineProperty.bind(null,Object); const change = first; change("create",{value:customCreate});',
'const change = Object.defineProperty.bind(null,Object); change.call(null,"create",{value:customCreate});',
'const change = Object.defineProperty.bind(null,Object); change.apply(null,["create",{value:customCreate}]);',
'const change = Object.defineProperty.bind(null,Object); Reflect.apply(change,null,["create",{value:customCreate}]);',
'Object.defineProperty.bind(null,Object,"create")({value:customCreate});',
'const change = Object.defineProperty.bind(null,...args); change(Object,"create",{value:customCreate});',
'const metadata = {}; Object.defineProperty.call(...args,metadata,"x",{value:1});',
'const metadata = {}; Object.defineProperty.apply(...args,[metadata,"x",{value:1}]);',
'const change = Function.prototype.call.bind(Object.defineProperty,null); change(Object,"create",{value:customCreate});',
'const first = Function.prototype.call.bind(Object.defineProperty,null); const change = first.bind(customReceiver,Object,"create"); change({value:customCreate});',
'const change = Function.prototype.apply.bind(Object.defineProperty,null); change([Object,"create",{value:customCreate}]);',
'Function.prototype.call.call(Object.defineProperty,null,Object,"create",{value:customCreate});',
'Function.prototype.apply.call(Object.defineProperty,null,[Object,"create",{value:customCreate}]);',
'const uncurry = Function.prototype.call.bind(Function.prototype.call); uncurry(Object.defineProperty,null,Object,"create",{value:customCreate});',
'const change = Function.prototype.bind.call(Object.defineProperty,null); change(Object,"create",{value:customCreate});',
'const bind = Function.prototype.call.bind(Function.prototype.bind); const change = bind(Object.defineProperty,null); change(Object,"create",{value:customCreate});',
'const change = Function.prototype.bind.apply(Object.defineProperty,[null,Object,"create"]); change({value:customCreate});',
'const change = Reflect.apply(Function.prototype.bind,Object.defineProperty,[null,Object,"create"]); change({value:customCreate});',
'const first = Object.defineProperty.bind(null,Object); const change = Function.prototype.bind.call(first,null,"create"); change({value:customCreate});',
'Reflect.apply = Object.defineProperty; Reflect.apply(Object,"create",{value:customCreate});',
'const apply = Reflect.apply; Reflect.apply = Object.defineProperty; Reflect.apply(Object,"create",{value:customCreate});',
'const change = Reflect.set.bind(null,Object,"create"); change(customCreate);',
'Reflect.set({},"create",customCreate,Object);',
'const metadata = {}; Reflect.set(metadata,"create",customCreate,Object);',
'const change = Reflect.set.bind(null,{},"create",customCreate); change(Object);',
'Reflect.apply(Reflect.set,null,[{},"create",customCreate,Object]);',
'Reflect.set({},"create",customCreate,...receivers);',
'Reflect.set({},"create",...valuesAndReceivers);',
"Reflect.set({},...keysValuesAndReceivers);",
'Reflect.set({},"create",customCreate,receiver);',
'const change = Object.defineProperty.call; change(null,Object,"create",{value:customCreate});',
'const {apply:change} = Object.defineProperty; change(null,[Object,"create",{value:customCreate}]);',
'const change = Reflect.apply.call; change(null,Object.defineProperty,null,[Object,"create",{value:customCreate}]);',
])("rejects intrinsic writes through a bound mutator: %s", (write) => {
expect(
analyzeJavaScriptStaticSource(
`${write}\n${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP} ${WINDOW}`,
).electron.browser_windows,
).toEqual([]);
});
it.each([
'const change = Object.defineProperty.bind(null); change(metadata,"x",{value:1});',
'const change = Object.defineProperty.bind(null,metadata,"x"); change({value:1});',
'const first = Object.defineProperty.bind(null,metadata); const change = first.bind(null,"x"); change({value:1});',
'const change = Function.prototype.call.bind(Object.defineProperty,null); change(metadata,"x",{value:1});',
'const change = Function.prototype.apply.bind(Object.defineProperty,null); change([metadata,"x",{value:1}]);',
'const change = Function.prototype.bind.call(Object.defineProperty,null,metadata,"x"); change({value:1});',
'const bind = Function.prototype.call.bind(Function.prototype.bind); const change = bind(Object.defineProperty,null,metadata,"x"); change({value:1});',
'const change = Function.prototype.bind.apply(Object.defineProperty,[null,metadata,"x"]); change({value:1});',
'const change = Reflect.apply(Function.prototype.bind,Object.defineProperty,[null,metadata,"x"]); change({value:1});',
'const apply = Reflect.apply; Reflect.apply = replacement; apply(Object.defineProperty,null,[metadata,"x",{value:1}]);',
'const change = Object.defineProperty.bind(null,Object,"create");',
'Object.defineProperty.call(null,metadata,"x",{value:1});',
'Object.defineProperty.apply(null,[metadata,"x",{value:1}]);',
'const change = Object.defineProperty.call; change(null,metadata,"x",{value:1});',
'const {apply:change} = Object.defineProperty; change(null,[metadata,"x",{value:1}]);',
'Reflect.set(metadata,"x",1,{});',
'const receiver = {}; Reflect.set({},"x",1,receiver);',
])("keeps a fresh bound target or unused binding separate: %s", (write) => {
expect(
analyzeJavaScriptStaticSource(
`const metadata = {}; ${write}\n${ROLLDOWN_MODULE_INTEROP}\n${IMPORT} ${WRAP} ${WINDOW}`,
).electron.browser_windows,
).toHaveLength(1);
});
});
it("keeps unrelated exports after an aliased prebound key write", () => {
const analysis = analyzeJavaScriptStaticSource(`
const electron = require("electron");
const key = "Unrelated";
const mutate = Object.defineProperty.bind(null, electron, key);
mutate({value: custom});
new electron.BrowserWindow({});
`);
expect(analysis.electron.browser_windows).toHaveLength(1);
});