mirror of
https://github.com/morluto/rea
synced 2026-10-26 22:28:27 -04:00
e9fc8fb30c
* 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
202 lines
7.4 KiB
TypeScript
202 lines
7.4 KiB
TypeScript
import type * as t from "@babel/types";
|
|
import type { JavaScriptSemanticAnalysisState } from "./javascriptSemanticState.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[];
|
|
},
|
|
state: JavaScriptSemanticAnalysisState,
|
|
): boolean =>
|
|
reference.requiredSources?.some(
|
|
(source) =>
|
|
!semanticReferenceIterationUnknown(source, state) &&
|
|
semanticSelectedReferenceSlot(source, state, source.node)?.presence ===
|
|
"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" &&
|
|
semanticValueDefinitelyDefined(slot.value)
|
|
);
|
|
}) === true;
|
|
|
|
/** Select a retained source slot at its capture, before later effects. */
|
|
export const semanticSelectedReferenceSlot = (
|
|
source: ReferenceSource,
|
|
state: JavaScriptSemanticAnalysisState,
|
|
capturePoint?: t.Node,
|
|
) => {
|
|
const path: string[] = [];
|
|
for (const key of source.projection) {
|
|
if (key === null || typeof key === "object") return undefined;
|
|
path.push(String(key));
|
|
}
|
|
return semanticSlotAtPath(
|
|
evaluateSemanticExpression(source.node, state, capturePoint),
|
|
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];
|
|
};
|