mirror of
https://github.com/morluto/rea
synced 2026-10-26 14:25:49 -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:
@@ -0,0 +1,403 @@
|
||||
import * as t from "@babel/types";
|
||||
import type {
|
||||
JavaScriptSemanticAnalysisState,
|
||||
JavaScriptSemanticBindingState,
|
||||
} from "./javascriptSemanticState.js";
|
||||
import {
|
||||
semanticPropertyPathKeyMatches,
|
||||
type JavaScriptSemanticPropertyPath,
|
||||
} from "./javascriptSemanticPropertyPaths.js";
|
||||
import { unwrapJavaScriptExpression } from "./javascriptAstValues.js";
|
||||
import {
|
||||
semanticObjectPropertyReferences,
|
||||
semanticArrayPropertyReferences,
|
||||
} from "./javascriptSemanticObjectReferences.js";
|
||||
import { SEMANTIC_ALIAS_SELECTION_LIMIT } from "./javascriptSemanticResourceLimits.js";
|
||||
import {
|
||||
semanticBindingAtRead,
|
||||
semanticDefiniteEffectPrecedesReference,
|
||||
semanticEffectTiming,
|
||||
semanticTopLevelEffectFollowsCapture,
|
||||
} from "./javascriptSemanticMutationInitializers.js";
|
||||
import {
|
||||
semanticCopiedReferencePath,
|
||||
semanticReferenceIterationUnknown,
|
||||
semanticReferenceSourcesInactive,
|
||||
} from "./javascriptSemanticReferenceSources.js";
|
||||
import { semanticStoredSourceSelection } from "./javascriptSemanticIterationSources.js";
|
||||
import {
|
||||
semanticAliasSelectionChoices,
|
||||
type SemanticAliasSelectionPath,
|
||||
} from "./javascriptSemanticAliasSelections.js";
|
||||
|
||||
export interface SemanticCapturedSelection {
|
||||
readonly at: t.Node;
|
||||
readonly depth: number;
|
||||
/** Object spread/rest reads only an own enumerable slot at this depth. */
|
||||
readonly ownEnumerable?: true;
|
||||
}
|
||||
|
||||
/** The next origin crossed owns a repeated copy boundary at the same depth. */
|
||||
export const semanticNormalizeCapturedSelections = (
|
||||
selections: readonly SemanticCapturedSelection[] | undefined,
|
||||
pathLength?: number,
|
||||
): readonly SemanticCapturedSelection[] | undefined => {
|
||||
const byDepth = new Map<number, SemanticCapturedSelection>();
|
||||
for (const capture of selections ?? [])
|
||||
if (
|
||||
capture.depth > 0 &&
|
||||
(pathLength === undefined || capture.depth <= pathLength)
|
||||
)
|
||||
byDepth.set(capture.depth, capture);
|
||||
const normalized = [...byDepth.values()].sort(
|
||||
(left, right) => left.depth - right.depth,
|
||||
);
|
||||
return normalized.length ? normalized : undefined;
|
||||
};
|
||||
|
||||
/** Keep capture boundaries aligned as a selected prefix is added or consumed. */
|
||||
export const semanticShiftCapturedSelections = (
|
||||
selections: readonly SemanticCapturedSelection[] | undefined,
|
||||
delta: number,
|
||||
): readonly SemanticCapturedSelection[] | undefined => {
|
||||
const shifted = selections?.flatMap((selection) =>
|
||||
selection.depth + delta > 0
|
||||
? [{ ...selection, depth: selection.depth + delta }]
|
||||
: [],
|
||||
);
|
||||
return semanticNormalizeCapturedSelections(shifted);
|
||||
};
|
||||
|
||||
interface ProjectedSource {
|
||||
readonly node: t.Node;
|
||||
readonly path: SemanticAliasSelectionPath;
|
||||
readonly capturedSelections?: readonly SemanticCapturedSelection[];
|
||||
}
|
||||
|
||||
/** A fresh parent does not prove a descendant receiver is a fresh allocation. */
|
||||
const hasFreshProjectedReceiver = (
|
||||
root: t.Node,
|
||||
path: JavaScriptSemanticPropertyPath,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): boolean => {
|
||||
const pending = [{ node: root, path }];
|
||||
const examined = new WeakMap<t.Node, Set<string>>();
|
||||
let selections = 0;
|
||||
while (pending.length > 0) {
|
||||
const current = pending.pop()!;
|
||||
const node = unwrapJavaScriptExpression(current.node).node;
|
||||
const key = JSON.stringify(current.path);
|
||||
const paths = examined.get(node) ?? new Set<string>();
|
||||
if (paths.has(key)) return false;
|
||||
paths.add(key);
|
||||
examined.set(node, paths);
|
||||
if (++selections > SEMANTIC_ALIAS_SELECTION_LIMIT) {
|
||||
state.valueResourceLimits.add("alias-selections");
|
||||
return false;
|
||||
}
|
||||
if (current.path.length === 0) {
|
||||
if (
|
||||
!t.isObjectExpression(node) &&
|
||||
!t.isArrayExpression(node) &&
|
||||
!t.isFunction(node) &&
|
||||
!t.isClassExpression(node)
|
||||
)
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
const [selected, ...remaining] = current.path;
|
||||
const sources = t.isObjectExpression(node)
|
||||
? semanticObjectPropertyReferences(node, [selected!], state)
|
||||
: t.isArrayExpression(node)
|
||||
? semanticArrayPropertyReferences(node, [selected!])
|
||||
: [];
|
||||
if (sources.length === 0) return false;
|
||||
if (
|
||||
selections + pending.length + sources.length >
|
||||
SEMANTIC_ALIAS_SELECTION_LIMIT
|
||||
) {
|
||||
state.valueResourceLimits.add("alias-selections");
|
||||
return false;
|
||||
}
|
||||
pending.push(
|
||||
...sources.map((source) => ({
|
||||
node: source.node,
|
||||
path: [...source.path, ...remaining],
|
||||
})),
|
||||
);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/** Select retained assignment and copy sources at one projected member read. */
|
||||
export const semanticProjectedSourcesAtRead = (
|
||||
...arguments_: Parameters<typeof semanticProjectedSourceFactsAtRead>
|
||||
): readonly ProjectedSource[] | undefined =>
|
||||
semanticProjectedSourceFactsAtRead(...arguments_)?.sources;
|
||||
|
||||
/** Retain the selected receiver's possible owners after its current value becomes opaque. */
|
||||
export const semanticProjectedSourceEligibilityAtRead = (
|
||||
binding: JavaScriptSemanticBindingState,
|
||||
reference: t.Node,
|
||||
path: SemanticAliasSelectionPath,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
memberReadAt: t.Node = reference,
|
||||
capturedSelections: readonly SemanticCapturedSelection[] = [],
|
||||
): readonly ProjectedSource[] | undefined =>
|
||||
projectedSourceFactsAtRead(
|
||||
binding,
|
||||
reference,
|
||||
path,
|
||||
state,
|
||||
false,
|
||||
memberReadAt,
|
||||
capturedSelections,
|
||||
true,
|
||||
)?.sources;
|
||||
|
||||
/** Keep imported origins subject to the same selected writes as other values. */
|
||||
export const semanticProjectedSourceFactsAtRead = (
|
||||
...arguments_: Parameters<typeof projectedSourceFactsAtRead>
|
||||
): ReturnType<typeof projectedSourceFactsAtRead> =>
|
||||
projectedSourceFactsAtRead(...arguments_);
|
||||
|
||||
const projectedSourceFactsAtRead = (
|
||||
binding: JavaScriptSemanticBindingState,
|
||||
reference: t.Node,
|
||||
path: SemanticAliasSelectionPath,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
includeUnknown: boolean,
|
||||
memberReadAt: t.Node = reference,
|
||||
capturedSelections: readonly SemanticCapturedSelection[] = [],
|
||||
retainEligibility = false,
|
||||
):
|
||||
| {
|
||||
readonly sources: readonly ProjectedSource[];
|
||||
readonly retainsOrigin: boolean;
|
||||
}
|
||||
| undefined => {
|
||||
const read = semanticBindingAtRead(binding, reference, state.parentsByNode);
|
||||
const captured = [
|
||||
...read.binding.initializers,
|
||||
...read.binding.referenceInitializers,
|
||||
];
|
||||
const normalizedCaptures = semanticNormalizeCapturedSelections(
|
||||
capturedSelections,
|
||||
path.length,
|
||||
);
|
||||
const captureForDepth = (depth: number): t.Node | undefined =>
|
||||
normalizedCaptures?.find((capture) => capture.depth >= depth)?.at;
|
||||
const selectedAt = (depth: number): t.Node =>
|
||||
captureForDepth(depth) ?? memberReadAt;
|
||||
const samePrefix = (
|
||||
prefix: JavaScriptSemanticBindingState["memberWrites"][number],
|
||||
effect: JavaScriptSemanticBindingState["memberWrites"][number],
|
||||
): boolean =>
|
||||
prefix.path.length < effect.path.length &&
|
||||
prefix.path.every(
|
||||
(key, index) =>
|
||||
key !== null &&
|
||||
typeof key !== "object" &&
|
||||
effect.path[index] !== null &&
|
||||
typeof effect.path[index] !== "object" &&
|
||||
String(key) === String(effect.path[index]),
|
||||
);
|
||||
const targetsReplacedPrefix = (
|
||||
effect: JavaScriptSemanticBindingState["memberWrites"][number],
|
||||
): boolean =>
|
||||
capturedSelections.length > 0 &&
|
||||
binding.memberWrites.some((replacement) => {
|
||||
const capture = captureForDepth(replacement.path.length);
|
||||
const receiverAt = effect.receiverAt ?? effect.node;
|
||||
return (
|
||||
capture !== undefined &&
|
||||
receiverAt !== undefined &&
|
||||
t.isAssignmentExpression(replacement.node, { operator: "=" }) &&
|
||||
samePrefix(replacement, effect) &&
|
||||
semanticTopLevelEffectFollowsCapture(
|
||||
capture,
|
||||
replacement.node,
|
||||
state.parentsByNode,
|
||||
) &&
|
||||
semanticDefiniteEffectPrecedesReference(
|
||||
replacement.node,
|
||||
receiverAt,
|
||||
state.parentsByNode,
|
||||
) &&
|
||||
hasFreshProjectedReceiver(
|
||||
replacement.node.right,
|
||||
effect.path.slice(replacement.path.length, -1),
|
||||
state,
|
||||
)
|
||||
);
|
||||
});
|
||||
const matches = (
|
||||
effect: JavaScriptSemanticBindingState["memberWrites"][number],
|
||||
) =>
|
||||
(effect.receiverInitializers === undefined ||
|
||||
effect.receiverInitializers.length === 0 ||
|
||||
captured.length === 0 ||
|
||||
effect.receiverInitializers.some((receiver) =>
|
||||
captured.some(
|
||||
(source) =>
|
||||
source.node === receiver.node &&
|
||||
JSON.stringify(source.projection) ===
|
||||
JSON.stringify(receiver.projection),
|
||||
),
|
||||
)) &&
|
||||
path.length > 0 &&
|
||||
effect.path.length <= path.length &&
|
||||
effect.path.every((key, index) => {
|
||||
const selected = path[index];
|
||||
return (
|
||||
selected !== undefined &&
|
||||
semanticAliasSelectionChoices(selected).some(
|
||||
(choice) =>
|
||||
choice === null ||
|
||||
typeof choice === "object" ||
|
||||
semanticPropertyPathKeyMatches(key, String(choice)),
|
||||
)
|
||||
);
|
||||
}) &&
|
||||
semanticEffectTiming(selectedAt(effect.path.length), effect.node, state) !==
|
||||
"after" &&
|
||||
!targetsReplacedPrefix(effect);
|
||||
const writes = binding.memberWrites.filter(matches);
|
||||
let replacement:
|
||||
| JavaScriptSemanticBindingState["memberWrites"][number]
|
||||
| undefined;
|
||||
for (const effect of writes) {
|
||||
if (
|
||||
t.isAssignmentExpression(effect.node, { operator: "=" }) &&
|
||||
effect.path.every(
|
||||
(key, index) =>
|
||||
key !== null &&
|
||||
typeof key !== "object" &&
|
||||
path[index] !== null &&
|
||||
typeof path[index] !== "object" &&
|
||||
String(key) === String(path[index]),
|
||||
) &&
|
||||
semanticDefiniteEffectPrecedesReference(
|
||||
effect.node,
|
||||
selectedAt(effect.path.length),
|
||||
state.parentsByNode,
|
||||
) &&
|
||||
(replacement === undefined ||
|
||||
semanticDefiniteEffectPrecedesReference(
|
||||
replacement.node!,
|
||||
effect.node,
|
||||
state.parentsByNode,
|
||||
) ||
|
||||
(replacement.node === effect.node &&
|
||||
(replacement.assignedSources?.[0]?.target?.start ?? -1) <
|
||||
(effect.assignedSources?.[0]?.target?.start ?? -1)))
|
||||
)
|
||||
replacement = effect;
|
||||
}
|
||||
const superseded = (
|
||||
effect: JavaScriptSemanticBindingState["memberWrites"][number],
|
||||
) =>
|
||||
replacement !== undefined &&
|
||||
effect.node !== undefined &&
|
||||
effect !== replacement &&
|
||||
(semanticEffectTiming(replacement.node!, effect.node, state) === "before" ||
|
||||
(effect.node === replacement.node &&
|
||||
(effect.assignedSources?.[0]?.target?.start ?? -1) <
|
||||
(replacement.assignedSources?.[0]?.target?.start ?? -1)));
|
||||
if (
|
||||
includeUnknown &&
|
||||
binding.escapedPaths.some(
|
||||
(effect) => matches(effect) && !superseded(effect),
|
||||
)
|
||||
)
|
||||
return undefined;
|
||||
const sources: ProjectedSource[] = [];
|
||||
for (const effect of writes) {
|
||||
if (!retainEligibility && superseded(effect)) continue;
|
||||
if (
|
||||
!t.isAssignmentExpression(effect.node) ||
|
||||
!["=", "||=", "&&=", "??="].includes(effect.node.operator)
|
||||
) {
|
||||
if (includeUnknown) return undefined;
|
||||
continue;
|
||||
}
|
||||
if (effect.assignedSources === undefined) {
|
||||
if (includeUnknown) return undefined;
|
||||
continue;
|
||||
}
|
||||
const remaining = path.slice(effect.path.length);
|
||||
for (const source of effect.assignedSources) {
|
||||
if (semanticReferenceIterationUnknown(source, state)) return undefined;
|
||||
if (semanticReferenceSourcesInactive(source, state)) continue;
|
||||
const [key, ...suffix] = remaining;
|
||||
for (const choice of key === undefined
|
||||
? [undefined]
|
||||
: semanticAliasSelectionChoices(key)) {
|
||||
const selection = semanticStoredSourceSelection(
|
||||
source,
|
||||
choice === undefined ? [] : [choice],
|
||||
);
|
||||
if (selection !== null)
|
||||
sources.push({
|
||||
node: source.node,
|
||||
path: [...selection.projection, ...suffix],
|
||||
...(selection.capturedSelectionDepth === undefined
|
||||
? {}
|
||||
: {
|
||||
capturedSelections: [
|
||||
{
|
||||
at: source.node,
|
||||
depth: selection.capturedSelectionDepth,
|
||||
...(selection.ownEnumerable
|
||||
? { ownEnumerable: true as const }
|
||||
: {}),
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!retainEligibility && replacement !== undefined)
|
||||
return { sources, retainsOrigin: false };
|
||||
for (const initializer of read.binding.initializers) {
|
||||
if (semanticReferenceIterationUnknown(initializer, state)) return undefined;
|
||||
if (semanticReferenceSourcesInactive(initializer, state)) continue;
|
||||
sources.push({
|
||||
node: initializer.node,
|
||||
path: [...initializer.projection, ...path],
|
||||
});
|
||||
}
|
||||
for (const initializer of read.binding.referenceInitializers) {
|
||||
if (semanticReferenceIterationUnknown(initializer, state)) return undefined;
|
||||
if (semanticReferenceSourcesInactive(initializer, state)) continue;
|
||||
const [key, ...suffix] = path;
|
||||
for (const choice of key === undefined
|
||||
? [undefined]
|
||||
: semanticAliasSelectionChoices(key)) {
|
||||
const projection = semanticCopiedReferencePath(
|
||||
initializer,
|
||||
choice === undefined ? [] : [choice],
|
||||
"identity",
|
||||
);
|
||||
if (projection !== null)
|
||||
sources.push({
|
||||
node: initializer.node,
|
||||
path: [...projection, ...suffix],
|
||||
capturedSelections:
|
||||
initializer.copyProjectionOffset === undefined
|
||||
? []
|
||||
: [
|
||||
{
|
||||
at: initializer.node,
|
||||
depth: projection.length + suffix.length - path.length + 1,
|
||||
...(initializer.copyKind === "object-rest"
|
||||
? { ownEnumerable: true as const }
|
||||
: {}),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
return { sources, retainsOrigin: true };
|
||||
};
|
||||
Reference in New Issue
Block a user