mirror of
https://github.com/morluto/rea
synced 2026-10-27 14:42:18 -04:00
fix(javascript): distinguish Map lookups from network endpoints
Resolve unchanged Map operations through shared binding, capture, mutation, and ambient escape facts. Reuse that proof in application and captured bundle analysis; unresolved get/delete receivers remain inferred candidates. Preserve repeated native operations without manufacturing receiver escapes. Retain uncertainty for real escapes, opaque eval, and changed constructor adder hooks. Converge stored aliases at the producing boundary. Adapt the JavaScript changes from #4863, excluding its Linux resource fix. Add captured-bundle parity, repeated-operation and constructor-hook coverage. Fixes #4853 Co-authored-by: abdooo2235 <abdalrhmantheiv15@gmail.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
createSemanticOpaqueGlobalObserver,
|
||||
indexSemanticOpaqueEffects,
|
||||
semanticOpaqueBindingEffects,
|
||||
semanticBindingCanBeRebound,
|
||||
@@ -169,7 +170,13 @@ function* indexSemanticAmbientWritesSteps(
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): Generator<void, void> {
|
||||
if (indexedAmbientWriteStates.has(state)) return;
|
||||
yield* indexJavaScriptAmbientWritesSteps(state.program, state);
|
||||
yield* indexJavaScriptAmbientWritesSteps(
|
||||
state.program,
|
||||
state,
|
||||
createSemanticOpaqueGlobalObserver(state),
|
||||
);
|
||||
if (state.opaqueEffects.globalSites.length > 0)
|
||||
clearSemanticValueCaches(state);
|
||||
indexedAmbientWriteStates.add(state);
|
||||
}
|
||||
|
||||
@@ -179,7 +186,12 @@ function* indexSemanticMemberMutationFactsSteps(
|
||||
): Generator<void, void> {
|
||||
if (indexedMemberMutationStates.has(state)) return;
|
||||
yield* indexSemanticAmbientWritesSteps(state);
|
||||
yield* collectSemanticMemberMutationsSteps(state.program, state);
|
||||
yield* collectSemanticMemberMutationsSteps(
|
||||
state.program,
|
||||
state,
|
||||
(site, reference, path) =>
|
||||
referenceEffectsMayHaveChanged(state, site, reference, path),
|
||||
);
|
||||
indexedMemberMutationStates.add(state);
|
||||
}
|
||||
|
||||
@@ -237,12 +249,12 @@ const visitSemanticReferenceEffectsAtSite = (
|
||||
if (!retain("escape", node, [])) return metadata;
|
||||
for (const node of state.bindingWrites.get(binding.bindingId) ?? [])
|
||||
if (!retain("binding-write", node)) return metadata;
|
||||
if (indexedMemberMutationStates.has(state)) {
|
||||
for (const effect of binding.memberWrites)
|
||||
if (!retain("member-write", effect.node, effect.path)) return metadata;
|
||||
for (const effect of binding.escapedPaths)
|
||||
if (!retain("escape", effect.node, effect.path)) return metadata;
|
||||
}
|
||||
// The mutation producer also queries its growing facts during convergence.
|
||||
// Completion stays explicit in metadata; callers cannot infer readiness.
|
||||
for (const effect of binding.memberWrites)
|
||||
if (!retain("member-write", effect.node, effect.path)) return metadata;
|
||||
for (const effect of binding.escapedPaths)
|
||||
if (!retain("escape", effect.node, effect.path)) return metadata;
|
||||
} else if (lookup.kind === "ambient" || lookup.kind === "uncertain-ambient") {
|
||||
for (const node of state.ambientBindingWrites.get(root.name) ?? [])
|
||||
if (!retain("ambient-write", node)) return metadata;
|
||||
@@ -250,6 +262,27 @@ const visitSemanticReferenceEffectsAtSite = (
|
||||
return metadata;
|
||||
};
|
||||
|
||||
/** Query current producer facts; public consumers additionally require completion. */
|
||||
const referenceEffectsMayHaveChanged = (
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
site: t.Node,
|
||||
referenceNode: t.Node,
|
||||
propertyPath?: JavaScriptSemanticPropertyPath,
|
||||
): boolean => {
|
||||
let changed = false;
|
||||
const metadata = visitSemanticReferenceEffectsAtSite(
|
||||
state,
|
||||
site,
|
||||
referenceNode,
|
||||
(effect) => {
|
||||
changed = effect.timing !== "after";
|
||||
return !changed;
|
||||
},
|
||||
propertyPath,
|
||||
);
|
||||
return metadata.resolution !== "resolved" || changed;
|
||||
};
|
||||
|
||||
/** Preserve the complete diagnostic contract for consumers needing every effect. */
|
||||
const semanticReferenceEffectsAtSite = (
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
@@ -477,18 +510,12 @@ export function* indexJavaScriptBindingsWithReceiversSteps(
|
||||
},
|
||||
referenceMayHaveChangedAt(site, referenceNode, propertyPath) {
|
||||
if (!indexedMemberMutationStates.has(state)) return true;
|
||||
let changed = false;
|
||||
const metadata = visitSemanticReferenceEffectsAtSite(
|
||||
return referenceEffectsMayHaveChanged(
|
||||
state,
|
||||
site,
|
||||
referenceNode,
|
||||
(effect) => {
|
||||
changed = effect.timing !== "after";
|
||||
return !changed;
|
||||
},
|
||||
propertyPath,
|
||||
);
|
||||
return metadata.resolution !== "resolved" || changed;
|
||||
},
|
||||
*referencesSteps() {
|
||||
references ??= yield* collectSemanticReferencesSteps(program, state);
|
||||
@@ -919,10 +946,12 @@ const createState = (
|
||||
return {
|
||||
program,
|
||||
opaqueEffects: {
|
||||
mayReferenceGlobalEval: false,
|
||||
sites: new WeakSet(),
|
||||
sloppySites: new WeakSet(),
|
||||
byScope: new Map(),
|
||||
children: new Map(),
|
||||
globalSites: [],
|
||||
},
|
||||
staticImportResolutionMode,
|
||||
valueResourceLimits: new Set(),
|
||||
@@ -945,10 +974,19 @@ const createState = (
|
||||
requiredModuleEffects: new Map(),
|
||||
ambientBindingWrites: new Map(),
|
||||
ambientWrites: new SemanticPropertyPathCoverage(),
|
||||
ambientEscapes: new SemanticPropertyPathCoverage(),
|
||||
ambientWriteEffects: [],
|
||||
};
|
||||
};
|
||||
|
||||
const globalEvalRoots = new Set([
|
||||
"eval",
|
||||
"globalThis",
|
||||
"global",
|
||||
"window",
|
||||
"self",
|
||||
]);
|
||||
|
||||
function* collectDefinitionsSteps(
|
||||
program: t.Program,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
@@ -962,7 +1000,10 @@ function* collectDefinitionsSteps(
|
||||
readonly scope: JavaScriptSemanticScopeState;
|
||||
readonly conditionallySkipped: boolean;
|
||||
}[] = [];
|
||||
const opaqueCandidates: { node: t.CallExpression; scopeId: string }[] = [];
|
||||
const opaqueCandidates: {
|
||||
node: t.CallExpression;
|
||||
scopeId: string;
|
||||
}[] = [];
|
||||
let needsPrototypeFacts = false;
|
||||
const functionBodies: {
|
||||
readonly body: t.BlockStatement;
|
||||
@@ -1040,6 +1081,10 @@ function* collectDefinitionsSteps(
|
||||
state,
|
||||
});
|
||||
bindInnerDeclaration(node, parent, scope, state);
|
||||
// Every supported global eval origin starts with one of these decoded
|
||||
// identifiers, including computed properties and destructured aliases.
|
||||
if (t.isIdentifier(node) && globalEvalRoots.has(node.name))
|
||||
state.opaqueEffects.mayReferenceGlobalEval = true;
|
||||
if (
|
||||
t.isCallExpression(node) &&
|
||||
t.isIdentifier(unwrapJavaScriptExpression(node.callee).node, {
|
||||
@@ -1096,7 +1141,7 @@ function* collectDefinitionsSteps(
|
||||
copyParameterToBody({ identifier, body, scope, state });
|
||||
}
|
||||
indexSemanticOpaqueEffects(opaqueCandidates, state);
|
||||
if (opaqueCandidates.length > 0) clearSemanticValueCaches(state);
|
||||
if (state.opaqueEffects.byScope.size > 0) clearSemanticValueCaches(state);
|
||||
}
|
||||
|
||||
type PrototypeOwner = "Object" | "Array" | "String";
|
||||
|
||||
Reference in New Issue
Block a user