mirror of
https://github.com/morluto/rea
synced 2026-10-27 06:33:32 -04:00
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:
@@ -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];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user