Files
reverse-engineering-anything/src/domain/javascript/javascriptSemanticReferenceSources.ts
T
ShuaixinHuang a2134b3a74 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
2026-11-05 16:52:19 +08:00

149 lines
5.3 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 {
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,
);
};