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
This commit is contained in:
ShuaixinHuang
2026-10-20 17:25:19 +08:00
committed by GitHub
parent d532b18123
commit e9fc8fb30c
149 changed files with 16881 additions and 988 deletions
@@ -1,17 +1,114 @@
import type * as t from "@babel/types";
import type { JavaScriptSemanticAnalysisState } from "./javascriptSemanticState.js";
import type { JavaScriptSemanticPropertyPath as PropertyPath } from "./javascriptSemanticPropertyPaths.js";
import { evaluateSemanticExpression } from "./javascriptSemanticValues.js";
import {
semanticArrayIndex,
semanticPropertyPathKeyMatches,
type JavaScriptSemanticPropertyPath as PropertyPath,
} from "./javascriptSemanticPropertyPaths.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import {
evaluateSemanticExpression,
semanticValueDefinitelyDefined,
} from "./javascriptSemanticValues.js";
import { semanticSlotAtPath } from "./javascriptSemanticSlots.js";
import type { JavaScriptSemanticValue } from "./javascriptSemanticValueTypes.js";
import { semanticEffectTiming } from "./javascriptSemanticMutationInitializers.js";
/**
* Node's wrapper module inherits require. A copy cannot read that delegate
* unless an earlier own-slot write or opaque module escape may have exposed it.
*/
export const semanticCommonJsRequireOwnEnumerableAt = (
capture: t.Node,
state: JavaScriptSemanticAnalysisState,
): boolean => {
const selected = ["commonjs-module", "require"];
const mayAffect = (
effect: JavaScriptSemanticAnalysisState["ambientWriteEffects"][number],
) =>
effect.path.length <= selected.length &&
effect.path.every((key, index) =>
semanticPropertyPathKeyMatches(key, selected[index] ?? ""),
) &&
semanticEffectTiming(capture, effect.node, state) !== "after";
return (
state.ambientWriteEffects.some(mayAffect) ||
state.ambientEscapeEffects.some(mayAffect)
);
};
interface ReferenceSource {
readonly node: t.Node;
readonly projection: PropertyPath;
readonly iterationSources?: readonly ReferenceSource[];
}
/** Native array iteration preserves numeric slots only while its iterator is intact. */
export const semanticKnownArrayIteration = (
value: JavaScriptSemanticValue | undefined,
state: JavaScriptSemanticAnalysisState,
): boolean =>
!state.arrayIterationUnknown &&
value?.status === "array" &&
value.items.every((item) => semanticArrayIndex(item.name) !== null);
/** Invocation spreads use the same native-iteration proof at their source read. */
export const semanticKnownArrayIterationAt = (
node: t.Node,
state: JavaScriptSemanticAnalysisState,
): boolean =>
semanticKnownArrayIteration(
evaluateSemanticExpression(node, state, node),
state,
);
/** Numeric destructuring projections do not select slots of custom iterators. */
export const semanticReferenceIterationUnknown = (
source: { readonly iterationSources?: readonly ReferenceSource[] },
state: JavaScriptSemanticAnalysisState,
): boolean =>
source.iterationSources?.some(
(iterable) => !selectedReferenceIterationKnown(iterable, state),
) === true;
const selectedReferenceIterationKnown = (
source: ReferenceSource,
state: JavaScriptSemanticAnalysisState,
): boolean => {
let values = [evaluateSemanticExpression(source.node, state, source.node)];
for (const key of source.projection) {
if (typeof key === "object" && key !== null) return false;
const selected = new Set<JavaScriptSemanticValue>();
for (const value of values) {
if (key === null) {
// for-of projects any position of a native array. Its finite slot
// inventory can prove every possible nested iterable without treating
// a computed object property or custom iterator as an own-index read.
if (
!semanticKnownArrayIteration(value, state) ||
value.status !== "array" ||
value.unknownItems
)
return false;
for (const slot of value.items) {
if (slot.presence !== "present") return false;
selected.add(slot.value);
}
} else {
const slot = semanticSlotAtPath(value, [String(key)]);
if (slot.presence !== "present") return false;
selected.add(slot.value);
}
}
values = [...selected];
}
return values.every((value) => semanticKnownArrayIteration(value, state));
};
/** Exclude references whose source slot or destructuring fallback is absent. */
export const semanticReferenceSourcesInactive = (
reference: {
readonly iterationSources?: readonly ReferenceSource[];
readonly requiredSources?: readonly ReferenceSource[];
readonly fallbackSources?: readonly ReferenceSource[];
},
@@ -19,19 +116,18 @@ export const semanticReferenceSourcesInactive = (
): boolean =>
reference.requiredSources?.some(
(source) =>
!semanticReferenceIterationUnknown(source, state) &&
semanticSelectedReferenceSlot(source, state, source.node)?.presence ===
"absent",
"absent",
) === true ||
reference.fallbackSources?.some((source) => {
// Selection precedes effects through the chosen reference. A later escape
// cannot make a fallback that was excluded at destructuring reachable.
const slot = semanticSelectedReferenceSlot(source, state, source.node);
return (
!semanticReferenceIterationUnknown(source, state) &&
slot?.presence === "present" &&
(slot.value.status === "literal" ||
slot.value.status === "union" ||
slot.value.status === "object" ||
slot.value.status === "array")
semanticValueDefinitelyDefined(slot.value)
);
}) === true;
@@ -51,3 +147,55 @@ export const semanticSelectedReferenceSlot = (
path,
);
};
interface CopiedReferenceSource {
readonly projection: PropertyPath;
readonly copyKind?: "object-rest" | "array-rest";
readonly copyProjectionOffset?: number;
readonly copyExcludedKeys?: readonly string[];
readonly copyStartIndex?: number;
}
/** Map copied child selections once; the fresh copy itself keeps its own identity. */
export const semanticCopiedReferencePath = (
initializer: CopiedReferenceSource,
path: PropertyPath,
effect: "write" | "escape" | "identity",
): PropertyPath | null => {
const offset = initializer.copyProjectionOffset;
if (offset === undefined) return [...initializer.projection, ...path];
const copiedPath = [...initializer.projection.slice(offset + 1), ...path];
if (effect === "identity" && copiedPath.length === 0) return null;
if (effect === "write" && copiedPath.length < 2) return null;
let [key, ...remaining] = copiedPath;
if (initializer.copyKind === "array-rest") {
const startIndex = initializer.copyStartIndex ?? 0;
if (key === undefined || key === null)
key = { excludedKeys: [], startIndex };
else if (typeof key === "object")
key = {
excludedKeys: key.excludedKeys.flatMap((name) => {
const index = semanticArrayIndex(name);
return index === null ? [] : [String(index + startIndex)];
}),
startIndex: (key.startIndex ?? 0) + startIndex,
};
else {
const index = semanticArrayIndex(key);
if (index === null) return null;
key = index + startIndex;
}
} else {
const excludedKeys = initializer.copyExcludedKeys ?? [];
if (key === undefined || key === null) key = { excludedKeys };
else if (typeof key === "object")
key = {
...key,
excludedKeys: [...new Set([...key.excludedKeys, ...excludedKeys])].sort(
compareUnicodeCodePoints,
),
};
else if (excludedKeys.includes(String(key))) return null;
}
return [...initializer.projection.slice(0, offset), key, ...remaining];
};