mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 01:41:21 +00:00
fix(javascript): resolve module-level Electron options in functions (#8960)
* fix(javascript): resolve module-level Electron options in functions * fix(javascript): limit module option ancestry walk * fix(javascript): preserve uncertain captured argument ordering * fix(javascript): retain hoisted activation uncertainty * fix(javascript): retain function ownership through loops * fix(javascript): account for classic global activations * fix(javascript): bind first activations to lexical identities * fix(javascript): retain uncertain global activation timing * fix(javascript): isolate receiver facts and share computed reads * fix(javascript): retain projected and opaque global activations * fix(javascript): refresh activation bounds as opaque effects grow * fix(ci): format website watch directories without changing configuration * fix(javascript): share source reads across opaque-effect updates * fix(javascript): retain constructed-code global activation bounds * fix(javascript): share runtime activation and receiver facts * fix(javascript): reuse pattern reads and function positions * fix(javascript): isolate copied paths and preserve runtime reads * style(javascript): format migrated reference imports * fix(javascript): normalize constructor activation and receiver effects * fix(javascript): retain aliased receiver timing and lexical global this * fix(javascript): preserve effect-free receiver identity across activations * fix(javascript): retain outer activation and prototype effects * fix(javascript): index implicit reads without deep path rescans * fix(javascript): retain aliased prototype and parameter read uncertainty * fix(javascript): index reflected reads through intrinsic invocation facts * fix(javascript): retain unresolved global activation reads * chore: refresh PR branch after review fix publication * fix(javascript): retain projected receiver reads and cache method facts * fix(javascript): retain inherited and implicit activation reads * fix(javascript): retain caller-owned this uncertainty * fix(javascript): retain reflected reads with unresolved spread arguments * fix(javascript): preserve enclosing activation for decorators * fix(javascript): track intrinsic literal prototype effects * fix(javascript): retain catch activation from implicit reads * perf(javascript): reuse effective keys for projected reads * fix(javascript): retain possible caller-owned ambient effects
This commit is contained in:
1 parent
ce07d358cd
commit
a2134b3a74
40 files changed
+2585
-191
No files matched your search
@@ -1,20 +1,35 @@
|
||||
import * as t from "@babel/types";
|
||||
import {
|
||||
isErasedJavaScriptValueChild,
|
||||
traverseJavaScriptAst,
|
||||
} from "./javascriptSemanticTraversal.js";
|
||||
import { isTypeOnlyExportDeclaration } from "./javascriptModuleDeclaration.js";
|
||||
import {
|
||||
javaScriptRuntimeContext,
|
||||
javaScriptThisOwner,
|
||||
} from "./javascriptSourceParser.js";
|
||||
import { semanticPropertyReadSources } from "./javascriptSemanticIterationSources.js";
|
||||
|
||||
import {
|
||||
delayedInstanceFieldValue,
|
||||
semanticComputedMethodKey,
|
||||
semanticMethodDefinitionExpression,
|
||||
semanticBranchMayBeSkipped,
|
||||
semanticIterationCardinality,
|
||||
} from "./javascriptSemanticEvaluationOrder.js";
|
||||
|
||||
import {
|
||||
staticObjectPropertyReader,
|
||||
staticObjectOwnMethodReader,
|
||||
semanticStaticPropertyKey,
|
||||
semanticWriteSiteReadsFirst,
|
||||
unwrapJavaScriptExpression,
|
||||
} from "./javascriptAstValues.js";
|
||||
import {
|
||||
resolveSemanticBindingState,
|
||||
semanticResolutionBlocked,
|
||||
semanticScopeId,
|
||||
semanticPrototypeOwners,
|
||||
type SemanticPrototypeOwner,
|
||||
type JavaScriptSemanticBindingState,
|
||||
type JavaScriptSemanticAnalysisState,
|
||||
type JavaScriptSemanticEffectTiming,
|
||||
@@ -55,6 +70,22 @@ interface InitializerPositionCache {
|
||||
readonly mutation: WeakMap<t.Node, StatementPosition | null>;
|
||||
readonly reference: WeakMap<t.Node, StatementPosition | null>;
|
||||
readonly read: WeakMap<t.Node, BindingReadPosition>;
|
||||
readonly enclosingFunction: WeakMap<t.Node, t.Function | null>;
|
||||
readonly functionPosition: WeakMap<t.Function, StatementPosition | null>;
|
||||
readonly statementIndices: WeakMap<
|
||||
readonly t.Statement[],
|
||||
ReadonlyMap<t.Statement, number>
|
||||
>;
|
||||
readonly moduleFunctionReads: WeakMap<
|
||||
readonly t.Statement[],
|
||||
{
|
||||
readonly reads: WeakMap<t.FunctionDeclaration, number | null>;
|
||||
readonly memberEffectsComplete: boolean;
|
||||
readonly opaquePositions: WeakMap<t.Node, number | null>;
|
||||
opaqueSitesIndexed: number;
|
||||
firstOpaqueRead: number | null;
|
||||
}
|
||||
>;
|
||||
readonly evaluationChildren: WeakMap<
|
||||
t.Node,
|
||||
ReadonlyMap<t.Node, number> | null
|
||||
@@ -80,6 +111,10 @@ const initializerPositionCache = (
|
||||
mutation: new WeakMap(),
|
||||
reference: new WeakMap(),
|
||||
read: new WeakMap(),
|
||||
enclosingFunction: new WeakMap(),
|
||||
functionPosition: new WeakMap(),
|
||||
statementIndices: new WeakMap(),
|
||||
moduleFunctionReads: new WeakMap(),
|
||||
evaluationChildren: new WeakMap(),
|
||||
expressionAncestry: undefined,
|
||||
};
|
||||
@@ -101,7 +136,13 @@ const statementPosition = (
|
||||
: statement;
|
||||
const parent = parents.get(enclosing);
|
||||
if (!t.isProgram(parent) && !t.isBlockStatement(parent)) return null;
|
||||
const index = parent.body.indexOf(enclosing);
|
||||
const cache = initializerPositionCache(parents).statementIndices;
|
||||
let positions = cache.get(parent.body);
|
||||
if (positions === undefined) {
|
||||
positions = new Map(parent.body.map((entry, index) => [entry, index]));
|
||||
cache.set(parent.body, positions);
|
||||
}
|
||||
const index = positions.get(enclosing) ?? -1;
|
||||
return index < 0
|
||||
? null
|
||||
: {
|
||||
@@ -135,7 +176,8 @@ const computeTopLevelStatementPosition = (
|
||||
while (node !== undefined) {
|
||||
if (
|
||||
(t.isFunction(node) &&
|
||||
(child === undefined || !semanticComputedMethodKey(node, child))) ||
|
||||
(child === undefined ||
|
||||
!semanticMethodDefinitionExpression(node, child))) ||
|
||||
t.isLoop(node)
|
||||
)
|
||||
return null;
|
||||
@@ -731,6 +773,38 @@ export const semanticMutationInitializers = (
|
||||
};
|
||||
};
|
||||
|
||||
/** Function ownership is independent of uncertain ordering within a loop. */
|
||||
const bindingEnclosingFunction = (
|
||||
read: t.Node,
|
||||
parents: WeakMap<t.Node, t.Node>,
|
||||
): t.Function | undefined => {
|
||||
const cache = initializerPositionCache(parents).enclosingFunction;
|
||||
const path: t.Node[] = [];
|
||||
let current = read;
|
||||
let result: t.Function | null = null;
|
||||
while (true) {
|
||||
const cached = cache.get(current);
|
||||
if (cached !== undefined) {
|
||||
result = cached;
|
||||
break;
|
||||
}
|
||||
path.push(current);
|
||||
const parent = parents.get(current);
|
||||
if (parent === undefined || delayedInstanceFieldValue(parent, current))
|
||||
break;
|
||||
if (
|
||||
t.isFunction(parent) &&
|
||||
!semanticMethodDefinitionExpression(parent, current)
|
||||
) {
|
||||
if (current === parent.body) result = parent;
|
||||
break;
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
for (const node of path) cache.set(node, result);
|
||||
return result ?? undefined;
|
||||
};
|
||||
|
||||
/** Reuse immutable ancestry facts across reads nested in the same expression. */
|
||||
const bindingReadPosition = (
|
||||
read: t.Node,
|
||||
@@ -754,7 +828,8 @@ const bindingReadPosition = (
|
||||
}
|
||||
if (
|
||||
parent === undefined ||
|
||||
(t.isFunction(parent) && !semanticComputedMethodKey(parent, current)) ||
|
||||
(t.isFunction(parent) &&
|
||||
!semanticMethodDefinitionExpression(parent, current)) ||
|
||||
delayedInstanceFieldValue(parent, current)
|
||||
) {
|
||||
result = { position: null, repeatingLoop: undefined };
|
||||
@@ -820,11 +895,608 @@ type SemanticBindingRead = {
|
||||
readonly binding: JavaScriptSemanticBindingState;
|
||||
};
|
||||
|
||||
/** A hoisted function needs a module-level use after initialization, not a later declaration. */
|
||||
const moduleFunctionFirstRead = (
|
||||
declaration: t.FunctionDeclaration,
|
||||
body: readonly t.Statement[],
|
||||
parents: WeakMap<t.Node, t.Node>,
|
||||
state?: JavaScriptSemanticAnalysisState,
|
||||
): number | null => {
|
||||
const cache = initializerPositionCache(parents).moduleFunctionReads;
|
||||
let retained = cache.get(body);
|
||||
const memberEffectsComplete = state?.memberMutationFactsComplete === true;
|
||||
if (retained?.memberEffectsComplete !== memberEffectsComplete)
|
||||
retained = undefined;
|
||||
if (retained === undefined) {
|
||||
const reads = new WeakMap<t.FunctionDeclaration, number | null>();
|
||||
retained = {
|
||||
reads,
|
||||
memberEffectsComplete,
|
||||
opaquePositions: new WeakMap(),
|
||||
opaqueSitesIndexed: 0,
|
||||
firstOpaqueRead: Infinity,
|
||||
};
|
||||
cache.set(body, retained);
|
||||
const functions = new Map<string, t.FunctionDeclaration | null>();
|
||||
for (const statement of body) {
|
||||
const exported =
|
||||
t.isExportNamedDeclaration(statement) ||
|
||||
t.isExportDefaultDeclaration(statement);
|
||||
if (
|
||||
t.isExportNamedDeclaration(statement) &&
|
||||
isTypeOnlyExportDeclaration(statement)
|
||||
)
|
||||
continue;
|
||||
const node = exported ? statement.declaration : statement;
|
||||
if (!t.isFunctionDeclaration(node)) continue;
|
||||
reads.set(node, exported ? null : Infinity);
|
||||
if (node.id == null) continue;
|
||||
const name = node.id.name;
|
||||
if (functions.has(name)) {
|
||||
const previous = functions.get(name);
|
||||
if (previous != null) reads.set(previous, null);
|
||||
reads.set(node, null);
|
||||
functions.set(name, null);
|
||||
} else functions.set(name, node);
|
||||
}
|
||||
const indexedReads = reads;
|
||||
const firstStatement = body[0];
|
||||
const program =
|
||||
firstStatement === undefined ? undefined : parents.get(firstStatement);
|
||||
const classicScript =
|
||||
t.isProgram(program) && javaScriptRuntimeContext(program) === "unknown";
|
||||
const globalReceivers = new WeakMap<t.Node, boolean | null>();
|
||||
const propertyReaders = new WeakMap<
|
||||
t.Node,
|
||||
ReturnType<typeof staticObjectPropertyReader>
|
||||
>();
|
||||
const ownMethodReaders = new WeakMap<t.Node, (name: string) => boolean>();
|
||||
const ownMethodAtSource = (node: t.Node, name: string): boolean => {
|
||||
let read = ownMethodReaders.get(node);
|
||||
if (read === undefined) {
|
||||
read = staticObjectOwnMethodReader(node);
|
||||
ownMethodReaders.set(node, read);
|
||||
}
|
||||
return read(name);
|
||||
};
|
||||
const receiverPrototypeOwners = new WeakMap<
|
||||
t.Node,
|
||||
ReadonlySet<SemanticPrototypeOwner>
|
||||
>();
|
||||
|
||||
const bindingReceivers = new WeakMap<
|
||||
JavaScriptSemanticBindingState,
|
||||
t.Node
|
||||
>();
|
||||
const globalReceiver = (input: t.Node): boolean | null => {
|
||||
const pending: { node: t.Node; finish: boolean }[] = [
|
||||
{ node: input, finish: false },
|
||||
];
|
||||
const visiting = new Set<t.Node>();
|
||||
const origins = new WeakMap<
|
||||
t.Node,
|
||||
{
|
||||
dependencies: t.Node[];
|
||||
unknown: boolean;
|
||||
global: boolean;
|
||||
owners: Set<SemanticPrototypeOwner>;
|
||||
}
|
||||
>();
|
||||
while (pending.length > 0) {
|
||||
const frame = pending.pop();
|
||||
if (frame === undefined) continue;
|
||||
const current = frame.node;
|
||||
if (frame.finish) {
|
||||
const fact = origins.get(current);
|
||||
if (fact === undefined) continue;
|
||||
let global = fact.global;
|
||||
let unknown = fact.unknown;
|
||||
for (const dependency of fact.dependencies) {
|
||||
for (const owner of receiverPrototypeOwners.get(dependency) ?? [])
|
||||
fact.owners.add(owner);
|
||||
const answer = globalReceivers.get(dependency);
|
||||
if (answer === true) global = true;
|
||||
if (answer === null || answer === undefined) unknown = true;
|
||||
}
|
||||
receiverPrototypeOwners.set(current, fact.owners);
|
||||
globalReceivers.set(current, global ? true : unknown ? null : false);
|
||||
visiting.delete(current);
|
||||
continue;
|
||||
}
|
||||
if (globalReceivers.has(current)) continue;
|
||||
if (visiting.has(current)) {
|
||||
// A back edge has no finite identity proof; retain uncertainty.
|
||||
globalReceivers.set(current, null);
|
||||
continue;
|
||||
}
|
||||
visiting.add(current);
|
||||
const fact = {
|
||||
dependencies: [] as t.Node[],
|
||||
unknown: false,
|
||||
global: false,
|
||||
owners: new Set<SemanticPrototypeOwner>(),
|
||||
};
|
||||
const node = unwrapJavaScriptExpression(current).node;
|
||||
if (t.isArrayExpression(node)) fact.owners.add("Array");
|
||||
else if (t.isFunction(node)) fact.owners.add("Function");
|
||||
else if (t.isObjectExpression(node)) fact.owners.add("Object");
|
||||
else if (t.isStringLiteral(node) || t.isTemplateLiteral(node))
|
||||
fact.owners.add("String");
|
||||
else if (t.isNumericLiteral(node)) fact.owners.add("Number");
|
||||
else if (t.isBooleanLiteral(node)) fact.owners.add("Boolean");
|
||||
else if (t.isRegExpLiteral(node)) fact.owners.add("RegExp");
|
||||
else if (t.isBigIntLiteral(node)) fact.owners.add("BigInt");
|
||||
if (node !== current) fact.dependencies.push(node);
|
||||
else if (t.isThisExpression(node)) {
|
||||
const owner = t.isProgram(program)
|
||||
? javaScriptThisOwner(node, program, parents)
|
||||
: "caller";
|
||||
fact.global = owner === "global";
|
||||
fact.unknown = owner === "caller";
|
||||
} else if (t.isIdentifier(node)) {
|
||||
const binding =
|
||||
state === undefined
|
||||
? undefined
|
||||
: resolveSemanticBindingState(state, node, node.name);
|
||||
if (binding === undefined) {
|
||||
if (["globalThis", "window", "self"].includes(node.name))
|
||||
fact.global = true;
|
||||
else fact.unknown = true;
|
||||
} else {
|
||||
const representative = bindingReceivers.get(binding);
|
||||
if (representative !== undefined && representative !== current) {
|
||||
fact.dependencies.push(representative);
|
||||
} else {
|
||||
bindingReceivers.set(binding, current);
|
||||
if (
|
||||
binding.initializers.length === 0 ||
|
||||
binding.referenceInitializers.length > 0
|
||||
)
|
||||
fact.unknown = true;
|
||||
for (const initializer of binding.initializers) {
|
||||
if (initializer.projection.length > 0) fact.unknown = true;
|
||||
else fact.dependencies.push(initializer.node);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (t.isObjectExpression(node)) {
|
||||
fact.unknown = node.properties.some(
|
||||
(property) =>
|
||||
t.isSpreadElement(property) ||
|
||||
(t.isObjectProperty(property) &&
|
||||
semanticStaticPropertyKey(property.key, property.computed) ===
|
||||
"__proto__"),
|
||||
);
|
||||
} else if (
|
||||
!t.isArrayExpression(node) &&
|
||||
!t.isLiteral(node) &&
|
||||
!t.isFunction(node)
|
||||
)
|
||||
fact.unknown = true;
|
||||
origins.set(current, fact);
|
||||
pending.push({ node: current, finish: true });
|
||||
for (const dependency of fact.dependencies)
|
||||
pending.push({ node: dependency, finish: false });
|
||||
}
|
||||
return globalReceivers.get(input) ?? null;
|
||||
};
|
||||
interface ReceiverBound {
|
||||
index: number;
|
||||
node?: t.Node;
|
||||
ambiguous: boolean;
|
||||
}
|
||||
const receiverBounds = new WeakMap<
|
||||
JavaScriptSemanticBindingState,
|
||||
ReceiverBound | null
|
||||
>();
|
||||
const receiverBound = (
|
||||
root: JavaScriptSemanticBindingState,
|
||||
): ReceiverBound | null => {
|
||||
const pending = [{ binding: root, finish: false }];
|
||||
const activeBindings = new Set<JavaScriptSemanticBindingState>();
|
||||
const dependencies = new WeakMap<
|
||||
JavaScriptSemanticBindingState,
|
||||
JavaScriptSemanticBindingState[]
|
||||
>();
|
||||
const merge = (
|
||||
left: ReceiverBound,
|
||||
right: ReceiverBound,
|
||||
): ReceiverBound => {
|
||||
if (left.index < right.index) return left;
|
||||
if (right.index < left.index) return right;
|
||||
return {
|
||||
...left,
|
||||
ambiguous:
|
||||
left.ambiguous || right.ambiguous || left.node !== right.node,
|
||||
};
|
||||
};
|
||||
while (pending.length > 0) {
|
||||
const frame = pending.pop()!;
|
||||
const binding = frame.binding;
|
||||
if (frame.finish) {
|
||||
let bound: ReceiverBound | null = {
|
||||
index: Infinity,
|
||||
ambiguous: false,
|
||||
};
|
||||
for (const effects of [binding.memberWrites, binding.escapedPaths]) {
|
||||
for (const effect of effects) {
|
||||
const position =
|
||||
effect.node === undefined
|
||||
? null
|
||||
: directMutationPosition(effect.node, parents);
|
||||
if (
|
||||
effect.node === undefined ||
|
||||
position === null ||
|
||||
position.body !== body
|
||||
) {
|
||||
bound = null;
|
||||
break;
|
||||
}
|
||||
bound = merge(bound, {
|
||||
index: position.index,
|
||||
node: effect.node,
|
||||
ambiguous: false,
|
||||
});
|
||||
}
|
||||
if (bound === null) break;
|
||||
}
|
||||
for (const dependency of dependencies.get(binding) ?? []) {
|
||||
const inherited = receiverBounds.get(dependency);
|
||||
if (
|
||||
bound === null ||
|
||||
inherited === null ||
|
||||
inherited === undefined
|
||||
) {
|
||||
bound = null;
|
||||
break;
|
||||
}
|
||||
bound = merge(bound, inherited);
|
||||
}
|
||||
receiverBounds.set(binding, bound);
|
||||
activeBindings.delete(binding);
|
||||
continue;
|
||||
}
|
||||
if (receiverBounds.has(binding)) continue;
|
||||
if (activeBindings.has(binding)) {
|
||||
receiverBounds.set(binding, null);
|
||||
continue;
|
||||
}
|
||||
activeBindings.add(binding);
|
||||
const selected: JavaScriptSemanticBindingState[] = [];
|
||||
for (const initializer of binding.initializers) {
|
||||
const node = unwrapJavaScriptExpression(initializer.node).node;
|
||||
if (!t.isIdentifier(node) || state === undefined) continue;
|
||||
const dependency = resolveSemanticBindingState(
|
||||
state,
|
||||
node,
|
||||
node.name,
|
||||
);
|
||||
if (dependency !== undefined) selected.push(dependency);
|
||||
}
|
||||
dependencies.set(binding, selected);
|
||||
pending.push({ binding, finish: true });
|
||||
for (const dependency of selected)
|
||||
pending.push({ binding: dependency, finish: false });
|
||||
}
|
||||
return receiverBounds.get(root) ?? null;
|
||||
};
|
||||
// A single owner-specific bound serves every literal read in this body.
|
||||
const prototypeBounds = new Map<
|
||||
SemanticPrototypeOwner,
|
||||
ReceiverBound | null
|
||||
>();
|
||||
for (const owner of semanticPrototypeOwners) {
|
||||
let bound: ReceiverBound | null = { index: Infinity, ambiguous: false };
|
||||
for (const effect of state?.prototypeMutationSites ?? []) {
|
||||
if (effect.owner !== owner && effect.owner !== "Object") continue;
|
||||
const position = directMutationPosition(effect.node, parents);
|
||||
if (position === null || position.body !== body) {
|
||||
bound = null;
|
||||
break;
|
||||
}
|
||||
if (position.index < bound.index)
|
||||
bound = {
|
||||
index: position.index,
|
||||
node: effect.node,
|
||||
ambiguous: false,
|
||||
};
|
||||
else if (position.index === bound.index && effect.node !== bound.node)
|
||||
bound = { ...bound, ambiguous: true };
|
||||
}
|
||||
prototypeBounds.set(owner, bound);
|
||||
}
|
||||
const receiverAtRead = (input: t.Node, read: t.Node): boolean | null => {
|
||||
// Allocation provenance follows the same initializer DAG as receiver identity.
|
||||
const identity = globalReceiver(input);
|
||||
for (const owner of receiverPrototypeOwners.get(input) ?? []) {
|
||||
const bound = prototypeBounds.get(owner);
|
||||
if (bound === null) return null;
|
||||
if (bound !== undefined && bound.index !== Infinity) {
|
||||
const position = bindingReadPosition(read, parents);
|
||||
if (
|
||||
position.position === null ||
|
||||
position.repeatingLoop ||
|
||||
position.position.body !== body
|
||||
)
|
||||
return null;
|
||||
if (position.position.index > bound.index) return null;
|
||||
if (
|
||||
position.position.index === bound.index &&
|
||||
(bound.ambiguous ||
|
||||
bound.node === undefined ||
|
||||
state === undefined ||
|
||||
semanticEffectTiming(read, bound.node, state) !== "after")
|
||||
)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (identity !== false || state === undefined || !memberEffectsComplete)
|
||||
return identity;
|
||||
const root = unwrapJavaScriptExpression(input).node;
|
||||
if (!t.isIdentifier(root)) return identity;
|
||||
const binding = resolveSemanticBindingState(state, root, root.name);
|
||||
if (binding === undefined) return null;
|
||||
const bound = receiverBound(binding);
|
||||
if (bound === null) return null;
|
||||
if (bound.index === Infinity) return identity;
|
||||
const position = bindingReadPosition(read, parents);
|
||||
if (
|
||||
position.position === null ||
|
||||
position.repeatingLoop ||
|
||||
position.position.body !== body
|
||||
)
|
||||
return null;
|
||||
if (position.position.index < bound.index) return identity;
|
||||
if (position.position.index > bound.index || bound.ambiguous) return null;
|
||||
return bound.node !== undefined &&
|
||||
(semanticReadPrecedesEffectWithinActivation(
|
||||
read,
|
||||
bound.node,
|
||||
parents,
|
||||
) ||
|
||||
semanticEffectTiming(read, bound.node, state) === "after")
|
||||
? identity
|
||||
: null;
|
||||
};
|
||||
const active = new Set<t.Function>();
|
||||
const retainNamedRead = (
|
||||
name: string,
|
||||
parent: t.Node | null,
|
||||
index: number,
|
||||
): void => {
|
||||
const fn = functions.get(name);
|
||||
if (fn == null || active.has(fn)) return;
|
||||
const current = indexedReads.get(fn);
|
||||
if (current === null || current === undefined) return;
|
||||
// Indirect activations and live exports have no proven module timing.
|
||||
indexedReads.set(
|
||||
fn,
|
||||
active.size > 0 || t.isExportSpecifier(parent)
|
||||
? null
|
||||
: Math.min(current, index),
|
||||
);
|
||||
};
|
||||
const opaquePositions = retained.opaquePositions;
|
||||
const definitionKeys = new WeakMap<t.Node, t.Function>();
|
||||
let unknownPropertyRead: number | null = Infinity;
|
||||
for (let index = 0; index < body.length; index++) {
|
||||
const statement = body[index];
|
||||
if (statement === undefined) continue;
|
||||
traverseJavaScriptAst(statement, {
|
||||
skip: (node) =>
|
||||
state?.erasedSyntaxNodes.has(node) === true ||
|
||||
isErasedJavaScriptValueChild(node, parents.get(node) ?? null),
|
||||
enter(node, parent) {
|
||||
if (
|
||||
semanticMethodDefinitionExpression(parent ?? undefined, node) &&
|
||||
parent !== null &&
|
||||
t.isFunction(parent)
|
||||
) {
|
||||
active.delete(parent);
|
||||
definitionKeys.set(node, parent);
|
||||
}
|
||||
if (t.isFunction(node)) active.add(node);
|
||||
// Direct eval sees lexical module/CommonJS bindings too. Its
|
||||
// scope-owned sites are complete before ambient indexing starts.
|
||||
if (
|
||||
t.isCallExpression(node) &&
|
||||
state?.opaqueEffects.sites.has(node)
|
||||
) {
|
||||
if (unknownPropertyRead !== null)
|
||||
unknownPropertyRead =
|
||||
active.size > 0 ? null : Math.min(unknownPropertyRead, index);
|
||||
}
|
||||
if (
|
||||
classicScript &&
|
||||
(t.isCallExpression(node) ||
|
||||
t.isOptionalCallExpression(node) ||
|
||||
t.isNewExpression(node) ||
|
||||
t.isTaggedTemplateExpression(node))
|
||||
) {
|
||||
opaquePositions.set(node, active.size > 0 ? null : index);
|
||||
}
|
||||
if (classicScript) {
|
||||
for (const source of semanticPropertyReadSources(
|
||||
node,
|
||||
parent,
|
||||
state,
|
||||
)) {
|
||||
const projected =
|
||||
source.iterationSources !== undefined ||
|
||||
source.projection.length > 1;
|
||||
if (!projected && receiverAtRead(source.node, node) === false)
|
||||
continue;
|
||||
// Select the receiver through the shared property-reference owner.
|
||||
// Unknown/iterated sources retain possible activation.
|
||||
if (projected && source.iterationSources === undefined) {
|
||||
let receiver: t.Node | undefined = source.node;
|
||||
for (const key of source.projection.slice(0, -1)) {
|
||||
if (typeof key !== "string" && typeof key !== "number") {
|
||||
receiver = undefined;
|
||||
break;
|
||||
}
|
||||
if (receiver === undefined) break;
|
||||
let read = propertyReaders.get(receiver);
|
||||
if (read === undefined) {
|
||||
read = staticObjectPropertyReader(receiver);
|
||||
propertyReaders.set(receiver, read);
|
||||
}
|
||||
const selected = read(String(key));
|
||||
receiver =
|
||||
selected.status === "explicit" ? selected.value : undefined;
|
||||
if (receiver === undefined) break;
|
||||
}
|
||||
if (
|
||||
receiver !== undefined &&
|
||||
receiverAtRead(receiver, node) === false
|
||||
)
|
||||
continue;
|
||||
}
|
||||
const key = source.projection[0];
|
||||
if (!projected && typeof key === "string")
|
||||
retainNamedRead(key, parent, index);
|
||||
else if (unknownPropertyRead !== null)
|
||||
unknownPropertyRead =
|
||||
active.size > 0 ? null : Math.min(unknownPropertyRead, index);
|
||||
}
|
||||
}
|
||||
let name: string | null = null;
|
||||
if (
|
||||
t.isIdentifier(node) &&
|
||||
parent !== null &&
|
||||
(t.isReferenced(node, parent) ||
|
||||
(t.isAssignmentExpression(parent) &&
|
||||
parent.left === node &&
|
||||
semanticWriteSiteReadsFirst(parent)))
|
||||
) {
|
||||
const candidate = functions.get(node.name);
|
||||
const binding =
|
||||
state === undefined
|
||||
? undefined
|
||||
: resolveSemanticBindingState(state, node, node.name);
|
||||
if (
|
||||
candidate != null &&
|
||||
state !== undefined &&
|
||||
semanticResolutionBlocked(state, node, node.name)
|
||||
) {
|
||||
const current = indexedReads.get(candidate);
|
||||
if (current !== null && current !== undefined)
|
||||
indexedReads.set(
|
||||
candidate,
|
||||
active.size > 0 ? null : Math.min(current, index),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
state === undefined ||
|
||||
(candidate != null &&
|
||||
binding?.initializers.some(
|
||||
({ node: value }) => value === candidate,
|
||||
))
|
||||
)
|
||||
name = node.name;
|
||||
} else if (
|
||||
classicScript &&
|
||||
(t.isMemberExpression(node) || t.isOptionalMemberExpression(node))
|
||||
) {
|
||||
const receiver = unwrapJavaScriptExpression(node.object).node;
|
||||
const selected = semanticStaticPropertyKey(
|
||||
node.property,
|
||||
node.computed,
|
||||
);
|
||||
const ownMethod =
|
||||
selected !== null &&
|
||||
functions.has(selected) &&
|
||||
(ownMethodAtSource(receiver, selected) ||
|
||||
(state !== undefined &&
|
||||
t.isIdentifier(receiver) &&
|
||||
(() => {
|
||||
const binding = resolveSemanticBindingState(
|
||||
state,
|
||||
receiver,
|
||||
receiver.name,
|
||||
);
|
||||
if (
|
||||
binding === undefined ||
|
||||
receiverBound(binding)?.index !== Infinity
|
||||
)
|
||||
return false;
|
||||
return (
|
||||
binding.initializers.length === 1 &&
|
||||
binding.initializers[0]!.projection.length === 0 &&
|
||||
ownMethodAtSource(
|
||||
unwrapJavaScriptExpression(
|
||||
binding.initializers[0]!.node,
|
||||
).node,
|
||||
selected,
|
||||
)
|
||||
);
|
||||
})()));
|
||||
// Classic scripts expose hoisted functions through the global object.
|
||||
// Property names are not referenced identifiers; keep their read timing too.
|
||||
if (!ownMethod && receiverAtRead(receiver, node) !== false) {
|
||||
name = selected;
|
||||
if (name === null) {
|
||||
if (unknownPropertyRead !== null)
|
||||
unknownPropertyRead =
|
||||
active.size > 0
|
||||
? null
|
||||
: Math.min(unknownPropertyRead, index);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (name !== null) retainNamedRead(name, parent, index);
|
||||
},
|
||||
exit(node) {
|
||||
if (t.isFunction(node)) active.delete(node);
|
||||
const method = definitionKeys.get(node);
|
||||
if (method !== undefined) active.add(method);
|
||||
},
|
||||
});
|
||||
}
|
||||
// Unknown global keys share one timing fact across the module functions.
|
||||
for (const fn of functions.values()) {
|
||||
if (fn == null) continue;
|
||||
const current = indexedReads.get(fn);
|
||||
if (current !== null && current !== undefined)
|
||||
indexedReads.set(
|
||||
fn,
|
||||
unknownPropertyRead === null
|
||||
? null
|
||||
: Math.min(current, unknownPropertyRead),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Callee resolution can query timing during the ambient walk. Source reads
|
||||
// stay fixed; consume each newly observed opaque call once without rescanning.
|
||||
const sites = state?.opaqueEffects.globalSites ?? [];
|
||||
while (retained.opaqueSitesIndexed < sites.length) {
|
||||
const site = sites[retained.opaqueSitesIndexed++];
|
||||
const position =
|
||||
site === undefined ? undefined : retained.opaquePositions.get(site);
|
||||
if (position === undefined || retained.firstOpaqueRead === null) continue;
|
||||
retained.firstOpaqueRead =
|
||||
position === null ? null : Math.min(retained.firstOpaqueRead, position);
|
||||
}
|
||||
const read = retained.reads.get(declaration);
|
||||
const first =
|
||||
read === undefined || read === null || retained.firstOpaqueRead === null
|
||||
? null
|
||||
: Math.min(read, retained.firstOpaqueRead);
|
||||
return first !== undefined && Number.isFinite(first) ? first : null;
|
||||
};
|
||||
|
||||
export const semanticBindingAtRead = (
|
||||
binding: JavaScriptSemanticBindingState,
|
||||
read: t.Node,
|
||||
parents: WeakMap<t.Node, t.Node>,
|
||||
context: WeakMap<t.Node, t.Node> | JavaScriptSemanticAnalysisState,
|
||||
): SemanticBindingRead => {
|
||||
const state = "parentsByNode" in context ? context : undefined;
|
||||
const parents =
|
||||
state === undefined
|
||||
? (context as WeakMap<t.Node, t.Node>)
|
||||
: state.parentsByNode;
|
||||
if (
|
||||
binding.initializers.length === 0 &&
|
||||
binding.referenceInitializers.length === 0
|
||||
@@ -919,11 +1591,66 @@ export const semanticBindingAtRead = (
|
||||
binding,
|
||||
hasDefiniteInitializer,
|
||||
};
|
||||
const moduleConstBinding =
|
||||
binding.scopeId.startsWith("scope:program:") &&
|
||||
binding.declarationKinds.size === 1 &&
|
||||
binding.declarationKinds.has("const") &&
|
||||
!binding.mutable;
|
||||
const enclosingFunction = moduleConstBinding
|
||||
? bindingEnclosingFunction(read, parents)
|
||||
: undefined;
|
||||
let functionPosition: StatementPosition | null = null;
|
||||
if (enclosingFunction !== undefined) {
|
||||
const positions = initializerPositionCache(parents).functionPosition;
|
||||
const cached = positions.get(enclosingFunction);
|
||||
if (cached !== undefined) functionPosition = cached;
|
||||
else {
|
||||
const compute = (): StatementPosition | null => {
|
||||
if (t.isStatement(enclosingFunction))
|
||||
return statementPosition(enclosingFunction, parents);
|
||||
for (
|
||||
let owner = parents.get(enclosingFunction);
|
||||
owner !== undefined;
|
||||
owner = parents.get(owner)
|
||||
) {
|
||||
if (t.isFunction(owner)) return null;
|
||||
if (t.isStatement(owner)) return statementPosition(owner, parents);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
functionPosition = compute();
|
||||
positions.set(enclosingFunction, functionPosition);
|
||||
}
|
||||
}
|
||||
const firstFunctionRead =
|
||||
moduleConstBinding &&
|
||||
sourcePosition !== null &&
|
||||
functionPosition !== null &&
|
||||
sourcePosition.body === functionPosition.body &&
|
||||
t.isFunctionDeclaration(enclosingFunction)
|
||||
? moduleFunctionFirstRead(
|
||||
enclosingFunction,
|
||||
sourcePosition.body,
|
||||
parents,
|
||||
state,
|
||||
)
|
||||
: null;
|
||||
const moduleConstInitializedBeforeActivation =
|
||||
moduleConstBinding &&
|
||||
sourcePosition !== null &&
|
||||
functionPosition !== null &&
|
||||
sourcePosition.body === functionPosition.body &&
|
||||
(t.isFunctionDeclaration(enclosingFunction)
|
||||
? firstFunctionRead !== null && sourcePosition.index < firstFunctionRead
|
||||
: positionPrecedes(sourcePosition, functionPosition));
|
||||
const uncertainOrder =
|
||||
repeatingLoop ||
|
||||
sourcePosition === null ||
|
||||
position === null ||
|
||||
sourcePosition.body !== position.body;
|
||||
if (uncertainOrder && moduleConstInitializedBeforeActivation)
|
||||
// Initialization coverage does not order a capture in another activation.
|
||||
return { status: "unresolved", binding, hasDefiniteInitializer: true };
|
||||
if (uncertainOrder)
|
||||
return { status: "unresolved", binding, hasDefiniteInitializer };
|
||||
const selected = selectInitializers(binding, position, read, parents);
|
||||
@@ -1271,7 +1998,7 @@ export const semanticBindingHasPriorValueWrite = (
|
||||
): boolean => {
|
||||
if (binding.definitions.some(({ kind }) => kind === "function")) return true;
|
||||
const writes = state.bindingWrites.get(binding.bindingId) ?? [];
|
||||
const read = semanticBindingAtRead(binding, site, state.parentsByNode);
|
||||
const read = semanticBindingAtRead(binding, site, state);
|
||||
const retained =
|
||||
read.status === "ordered"
|
||||
? new Set([
|
||||
|
||||
Reference in new issue
Block a user