Files
reverse-engineering-anything/src/domain/javascript/electronStaticAnalysisReturnedApp.test.ts
T
ShuaixinHuang e9fc8fb30c fix(javascript): preserve runtime path bytes and Electron ownership (#4941)
* fix(javascript): preserve literal percent runtime paths

* fix(javascript): ignore resolve prefixes before the final anchor

* fix(electron): retain application roots for loadFile declarations

* fix(electron): resolve loadFile from enclosing application package

* fix(electron): retain outer application root across package scopes

* fix(electron): derive application ownership from inventoried packages

* fix(electron): retain resolved package entrypoint ownership

* fix(electron): preserve competing application ownership

* fix(electron): retain invalid and omitted runtime ownership

* fix(electron): preserve omitted module ownership and runtime paths

* fix(electron): preserve proven roots before confining page paths

* fix(electron): retain possible owners behind missing imports

* fix(electron): distinguish absent imports and owned page candidates

* fix(electron): keep unavailable JSON out of owner gaps

* fix(electron): retain exact loader formats and default entry status

* fix(electron): retain unavailable entrypoint ownership candidates

* test(ghidra): preserve Windows Node probe prerequisites

* test(ghidra): assert cancellation during profile shutdown

* fix(electron): separate examined imports from frontend ownership

* fix(electron): retain unavailable directory entrypoint owners

* test(jeb): retain Linux ownership prerequisite in isolation

* fix(javascript): retain unchecked loader ownership and literal main paths

* fix(javascript): preserve runtime paths and call-site module aliases

* fix(javascript): bound module alias traversal and preserve projections

* fix(javascript): retain host module and nested artifact path boundaries

* fix(javascript): preserve ESM and archive module loader provenance

* test(javascript): distinguish unmapped containers from missing exports

* fix: retain default module aliases and Electron builtin imports

* fix: preserve host file URL and inward ASAR ownership

* fix: preserve CommonJS file URL classification in CI fixtures

* fix: retain bound module loaders and Windows entrypoint uncertainty

* fix: retain ASAR uncertainty and invalidate changed Electron app roots

* fix(javascript): retain ownership uncertainty across loader boundaries

* fix(javascript): preserve loader sources and bound alias queries

* fix(javascript): retain destructured loader sources

* fix(javascript): preserve loader uncertainty at resolution boundaries

* fix(javascript): preserve callable and package launch ownership

* fix(javascript): preserve captured values across member reads

* test(process): assert tagged filesystem cancellation failures

* fix(javascript): preserve inventory bases and invocation read timing

* fix(javascript): preserve ownership across indirect and unread loads

* test(javascript): order fixture directory creation on Windows

* test(javascript): share ordered filesystem fixture writer

* fix(javascript): unify loader provenance and package validation

* fix(javascript): preserve captured Node require loader values

* fix(javascript): preserve loader invocation authority

* fix(javascript): verify generic loader invocation authority

* fix(javascript): preserve conditional loader method alternatives

* fix(javascript): require proven factories for source association

* fix(javascript): honor replaced Electron app path setters

* fix(javascript): prove path and assignment source ownership

* fix(javascript): retain native delegate eligibility through shared writes

* fix(process): settle stale baseline errors after confirmed absence

* fix(javascript): preserve assignment and path boundary evidence

* fix(javascript): prove source-directory resolve anchors

* fix: preserve application path provenance and portable schemas

* fix(javascript): retain CommonJS exported setter effects

* fix: isolate Linux identity observations and retain native module exports

* fix(javascript): preserve published app and helper provenance

* fix(javascript): normalize typed stores and native path fixtures
2026-10-20 17:25:19 +08:00

232 lines
6.9 KiB
TypeScript

import { expect, it } from "vitest";
import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js";
it.each([
[
"direct factory",
"const getApp=()=>app;getApp().setAppPath('./lib');",
true,
],
[
"returned alias",
"const getApp=()=>app;const runtime=getApp();runtime.setAppPath('./lib');",
true,
],
[
"method factory",
"const api={get(){return app}};api.get().setAppPath('./lib');",
true,
],
[
"projected return",
"const getApp=()=>({runtime:app});getApp().runtime.setAppPath('./lib');",
true,
],
["method return", "const change=()=>app.setAppPath;change()('./lib');", true],
[
"awaited factory",
"const getApp=async()=>app;(await getApp()).setAppPath('./lib');",
true,
],
[
"unawaited promise",
"const getApp=async()=>app;getApp().setAppPath('./lib');",
false,
],
[
"generator",
"function*getApp(){return app}getApp().setAppPath('./lib');",
false,
],
[
"awaited generator",
"function*getApp(){return app}(await getApp()).setAppPath('./lib');",
false,
],
[
"local factory",
"const getApp=()=>({setAppPath(){}});getApp().setAppPath('./lib');",
false,
],
[
"escaped local",
"const local={setAppPath(){}};escape(local);local.setAppPath('./lib');",
false,
],
[
"external local",
"const local=require('local-library');local.setAppPath('./lib');",
false,
],
[
"unknown return",
"const getApp=()=>unknown;getApp().setAppPath('./lib');",
false,
],
[
"unrelated unknown invocation",
"const local={make(){}};escape(local);local.make();",
false,
],
[
"local method",
"const api={get(){return {setAppPath(){}}}};api.get().setAppPath('./lib');",
false,
],
[
"shadowed app",
"function local(app){const getApp=()=>app;getApp().setAppPath('./lib');}",
false,
],
[
"mixed return",
"const getApp=()=>flag?app:{setAppPath(){}};getApp().setAppPath('./lib');",
true,
],
[
"replacement factory",
"const api={get:()=>({setAppPath(){}})};api.get=()=>app;api.get().setAppPath('./lib');",
true,
],
[
"local replacement",
"const api={get:()=>app};api.get=()=>({setAppPath(){}});api.get().setAppPath('./lib');",
false,
],
[
"later member replacement",
"const getApp=()=>app;getApp().setAppPath('./lib');app.setAppPath=()=>{};",
true,
],
[
"returned setter call",
"const getApp=()=>app;getApp().setAppPath.call(app,'./lib');",
true,
],
[
"returned setter reflect",
"const getApp=()=>app;Reflect.apply(getApp().setAppPath,app,['./lib']);",
true,
],
[
"factory call delegate",
"const getApp=()=>app;getApp.call(null).setAppPath('./lib');",
true,
],
[
"factory apply delegate",
"const getApp=()=>app;getApp.apply(null,[]).setAppPath('./lib');",
true,
],
[
"captured app before replacement",
"let runtime=app;const held=runtime;runtime={setAppPath(){}};const getApp=()=>held;getApp().setAppPath('./lib');",
true,
],
[
"captured local before replacement",
"let runtime={setAppPath(){}};const held=runtime;runtime=app;const getApp=()=>held;getApp().setAppPath('./lib');",
false,
],
[
"captured local after replacement",
"let runtime=app;runtime={setAppPath(){}};const held=runtime;const getApp=()=>held;getApp().setAppPath('./lib');",
false,
],
[
"captured app after replacement",
"let runtime={setAppPath(){}};runtime=app;const held=runtime;const getApp=()=>held;getApp().setAppPath('./lib');",
true,
],
] as const)(
"retains only possible Electron app path changes from %s",
(_name, body, expected) => {
const analysis = analyzeJavaScriptStaticSource(
`const {app}=require('electron');${body}`,
);
expect(analysis.parse_error_count).toBe(0);
expect((analysis.electron.app_path_changes ?? []).length > 0).toBe(
expected,
);
// A possible return must not promote other APIs to proven Electron identity.
expect(analysis.electron.browser_windows).toEqual([]);
},
);
it.each([
"const getApp=()=>require('electron').app;getApp().setAppPath('./lib');",
"const getElectron=()=>require('electron');getElectron().app.setAppPath('./lib');",
"const getApp=()=>require('other').app;getApp().setAppPath('./lib');",
])(
"follows returned require origins without an import shortcut: %s",
(source) => {
const analysis = analyzeJavaScriptStaticSource(source);
expect(analysis.parse_error_count).toBe(0);
expect((analysis.electron.app_path_changes ?? []).length > 0).toBe(
!source.includes("'other'"),
);
},
);
it("does not promote a returned Electron constructor to proven API identity", () => {
const analysis = analyzeJavaScriptStaticSource(
"const {BrowserWindow}=require('electron');const get=()=>BrowserWindow;new (get())();",
);
expect(analysis.electron.browser_windows).toEqual([]);
expect(analysis.electron.app_path_changes ?? []).toEqual([]);
});
it.each([
["call", true],
["call", false],
["apply", true],
["apply", false],
] as const)(
"uses the actual receiver of a captured %s wrapper (host=%s)",
(method, host) => {
const local = "({setAppPath(){}})";
const analysis = analyzeJavaScriptStaticSource(
`const {app}=require('electron');
const original=()=>${host ? local : "app"};
const selected=()=>${host ? "app" : local};
const invoke=original.${method};
invoke.call(selected,null${method === "apply" ? ",[]" : ""}).setAppPath('./lib');`,
);
expect(analysis.parse_error_count).toBe(0);
expect((analysis.electron.app_path_changes ?? []).length > 0).toBe(host);
},
);
it.each([true, false])(
"keeps a finite source closure after an Electron app selector budget is reached (host=%s)",
(host) => {
const declarations = Array.from(
{ length: 550 },
(_, index) =>
`const selected${index}=selected${index === 0 ? "Root" : index - 1};`,
).join("");
const analysis = analyzeJavaScriptStaticSource(
`const {app}=require('electron');const selectedRoot={runtime:${host ? "app" : "{setAppPath(){}}"}};${declarations}const getApp=()=>selected549.runtime;getApp().setAppPath('./lib');`,
);
expect(analysis.parse_error_count).toBe(0);
expect((analysis.electron.app_path_changes ?? []).length > 0).toBe(host);
expect(analysis.limitations).toEqual(
expect.arrayContaining([
expect.stringContaining("Alias selection expansion"),
]),
);
},
);
it.each([true, false])(
"closes cyclic returned app copies without inventing an Electron origin (host=%s)",
(host) => {
const analysis = analyzeJavaScriptStaticSource(
`const {app}=require('electron');let box={runtime:${host ? "app" : "{setAppPath(){}}"}};while(flag)box={...box};const getApp=()=>box.runtime;getApp().setAppPath('./lib');`,
);
expect(analysis.parse_error_count).toBe(0);
expect((analysis.electron.app_path_changes ?? []).length > 0).toBe(host);
},
);